Python module test code that calls python functions at OS / Module level

I have a python / script module that executes some of these

  • At different nested levels inside the script, I take command line inputs, check them, apply reasonable defaults
  • I also check if multiple directories exist

The above are just two examples. I am trying to figure out what is the best "strategy" to test this. What I did was that I built wrapper functions around raw_input and os.path.exists in my module, and then in my test I redefine these two functions to accept input from a list of arrays or do some mockery behavior. This approach has the following disadvantages.

  • Wrapper functions just exist for testing, and it pollutes the code.
  • I must remember that each time use the wrapper function in the code, and not just call os.path.exists or raw_input

Any brilliant suggestions?

+5
source share
3 answers

The short answer lies in the monkey patch of these system calls.

There are some good examples in the answer How to display a redirected stdin in Python?

Here is a simple example for raw_input() using lambda , which drops the prompt and returns what we want.

System test

 $ cat ./name_getter.py #!/usr/bin/env python class NameGetter(object): def get_name(self): self.name = raw_input('What is your name? ') def greet(self): print 'Hello, ', self.name, '!' def run(self): self.get_name() self.greet() if __name__ == '__main__': ng = NameGetter() ng.run() $ echo Derek | ./name_getter.py What is your name? Hello, Derek ! 

Test case:

 $ cat ./t_name_getter.py #!/usr/bin/env python import unittest import name_getter class TestNameGetter(unittest.TestCase): def test_get_alice(self): name_getter.raw_input = lambda _: 'Alice' ng = name_getter.NameGetter() ng.get_name() self.assertEquals(ng.name, 'Alice') def test_get_bob(self): name_getter.raw_input = lambda _: 'Bob' ng = name_getter.NameGetter() ng.get_name() self.assertEquals(ng.name, 'Bob') if __name__ == '__main__': unittest.main() $ ./t_name_getter.py -v test_get_alice (__main__.TestNameGetter) ... ok test_get_bob (__main__.TestNameGetter) ... ok ---------------------------------------------------------------------- Ran 2 tests in 0.000s OK 
+2
source

Solution1: I would do something like this, this works:

 def setUp(self): self._os_path_exists = os.path.exists os.path.exists = self.myTestExists # mock def tearDown(self): os.path.exists = self._os_path_exists 

This is not so nice.

Solution2: Restructuring the code was not an option, as you said, right? It would be worse to understand and unintuitive.

+2
source

Johnnysweb is what you need to do, but instead of riding yourself, you can import and use mock . Mock is specifically designed for unit testing and makes it extremely easy to do what you are trying to do. It is built into Python 3.3.

For example, if you want to run unit test, which replaces the os.path.isfile file and always returns True:

 try: from unittest.mock import patch except ImportError: from mock import patch class SomeTest(TestCase): def test_blah(): with patch("os.path.isfile", lambda x: True): self.assertTrue(some_function("input")) 

This can save a lot of template code, and it is quite readable.

If you need something more complex, for example, replacing supbroccess.check_output, you can create a simple helper function:

 def _my_monkeypatch_function(li): x,y = li[0], li[1] if x == "Reavers": return "Gorram" if x == "Inora": return "Shiny!" if x == y: return "The Ballad of Jayne" def test_monkey(): with patch("subprocess.check_output", _my_monkeypatch_function): assertEquals(subprocess.check_output(["Mudder","Mudder"]), "The Ballad of Jayne") 
+1
source

Source: https://habr.com/ru/post/943526/


All Articles