Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

What is the best way to mock os.system for unit test (PyTest)

I have a Python script that does multiple os.system calls. Asserting against the series of them as a list of strings will be easy (and relatively elegant).

What isn't so easy is intercepting (and blocking) the actual calls. In the script in question, I could abstract os.system in the SUT (*) like so:

os_system = None

def main():
    return do_the_thing(os.system)

def do_the_thing(os_sys):
    global os_system
    os_system = os_sys

    # all other function should use os_system instead of os.system

My test invokes my_script.do_the_thing() instead of my_script.main() of course (leaving a tiny amount of untested code).

Alternate option: I could leave the SUT untouched and replace os.system globally in the test method before invoking main() in the SUT.

That leaves me with new problems in that that's a global and lasting change. Fine, so I'd use a try/finally in the same test method, and replace the original before leaving the test method. That'd work whether the test method passes or fails.

Is there a safe and elegant setup/teardown centric way of doing this for PyTest, though?

Additional complications: I want to do the same for stdout and stderr. Yes, it really is a main() script that I am testing.

  • SUT == System Under Test
like image 492
paul_h Avatar asked Sep 15 '17 11:09

paul_h


People also ask

Does pytest support mocking?

In pytest , mocking can replace the return value of a function within a function. This is useful for testing the desired function and replacing the return value of a nested function within that desired function we are testing.

How do you mock a file path in Python?

That said, if you really want to mock, you can do that easily with Python's unittest. mock library: import unittest. mock # production code file; note the default parameter def make_hello_world(path, open_func=open): with open_func(path, 'w+') as f: f.

What is the difference between mock and MagicMock?

With Mock you can mock magic methods but you have to define them. MagicMock has "default implementations of most of the magic methods.". If you don't need to test any magic methods, Mock is adequate and doesn't bring a lot of extraneous things into your tests.


1 Answers

The Python 3 (>= 3.3) standard library has a great tutorial about Mock in the official documentation. For Python 2, you can use the backported library: Mock on PyPi.

Here is a sample usage. Say you want to mock the call to os.system in this function:

import os


def my_function(src_dir):
    os.system('ls ' + src_dir)

To do that, you can use the unittest.mock.patch decorator, like this:

import unittest.mock


@unittest.mock.patch('os.system')
def test_my_function(os_system):
    # type: (unittest.mock.Mock) -> None
    my_function("/path/to/dir")
    os_system.assert_called_once_with('ls /path/to/dir')

This test function will patch the os.system call during its execution. os.system is restored at the end.

Then, there are several "assert" method to check the calls, the parameters, and the results. You can also check that an exception is raised in certain circonstances.

like image 194
Laurent LAPORTE Avatar answered Oct 19 '22 23:10

Laurent LAPORTE