[PYTHON] Touch the mock and stub

Overview

In this sentence

explain. In Python3, MagicMock is included in the standard module unittest.mock. In Python2, you can use it by installing the mock package with pip install mock.

Play with MagicMock

Start the Python interpreter and create a MagicMock object

$ python3.6
Python 3.6.0 (default, Feb 27 2017, 00:03:01)
[GCC 4.2.1 Compatible Apple LLVM 8.0.0 (clang-800.0.42.1)] on darwin
Type "help", "copyright", "credits" or "license" for more information.
>>> from unittest.mock import MagicMock
>>> f = MagicMock()

I assume that this f is a function and try to call it

>>> f(1)
<MagicMock name='mock()' id='4381867704'>

I can call it even though I didn't define it as a function. that's strange. The return value is also MagicMock, but I do not care about that and I will call it further

>>> f(2)
<MagicMock name='mock()' id='4381867704'>
>>> f(1, 2, 3)
<MagicMock name='mock()' id='4381867704'>

I could call it freely even if I changed the value of the argument and the number of arguments. I'm surprised. If you try to evaluate f.call_args_list here,

>>> f.call_args_list
[call(1), call(2), call(1, 2, 3)]

I've called it three times since I created MagicMock. You can see that it is recorded with the arguments. it's interesting.

This is not the only feature of MagicMock. Let's create a MagicMock instance again, set the value to f.return_value, and then call f.

>>> f = MagicMock()
>>> f.return_value = 5
>>> f(1, 2)
5
>>> f(1)
5
>>> f()
5

No matter what argument you give, the set value will be returned. In this way you can replace the return value of the function with any value you like. In this case as well, how the call was made is recorded.

>>> f.call_args_list
[call(1, 2), call(1), call()]

There are other features, but let's leave this out for now and see how they can help.

Record of call

Suppose you have a function push_if_even () that takes an integer, calls push () if it's even, and does nothing if it's odd. Specifically, the code is as follows.

def push():
    pass

def push_if_even(x):
    """Receives an integer, push if even()Call"""
    if x % 2 == 0:
        push()

Now suppose you want to write a test to see if this code is implemented correctly. As we saw earlier, MagicMock keeps a record of what was called as a function, so we take advantage of that. You can test it by doing the following:

from unittest.mock import MagicMock

push = MagicMock()
push_if_even(1)
assert len(push.call_args_list) == 0

push = MagicMock()
push_if_even(0)
assert len(push.call_args_list) == 1

print("success")

You can also write as follows

push = MagicMock()
push_if_even(1)
push.asset_not_called()

push = MagicMock()
push_if_even(0)
push.assert_called_once()

Let's take a look at what happens when assert fails.

>>> from unittest.mock import MagicMock
>>> push = MagicMock()
>>> push.assert_called_once()
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "/usr/local/Cellar/python3/3.6.0/Frameworks/Python.framework/Versions/3.6/lib/python3.6/unittest/mock.py", line 795, in assert_called_once
    raise AssertionError(msg)
AssertionError: Expected 'mock' to have been called once. Called 0 times.
>>> push()
<MagicMock name='mock()' id='4356157392'>
>>> push.assert_not_called()
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "/usr/local/Cellar/python3/3.6.0/Frameworks/Python.framework/Versions/3.6/lib/python3.6/unittest/mock.py", line 777, in assert_not_called
    raise AssertionError(msg)
AssertionError: Expected 'mock' to not have been called. Called 1 times.

Return value control

A function that returns True if today is Sunday, False otherwise ʻis_sunday`

from datetime import date

def today():
    return date.today()

def is_sunday():
    return today().weekday() == 6

Suppose you want to test that is working properly. If today isn't Sunday, you can call it once and see that it returns False, then wait for the next Sunday and call it again to see that it returns True. However, it will take days to finish the test. If you can't wait for that, use MagicMock's ability to control the return value.

from unittest.mock import MagicMock

today = MagicMock()
today.return_value = date(2020, 2, 1)
assert is_sunday() == False

today = MagicMock()
today.return_value = date(2020, 2, 2)
assert is_sunday() == True

print("success")

If so, you can test it in an instant. It's convenient.

Summary

After seeing the nature of MagicMock,

I implemented the test code by utilizing the function of. Testing for functions that affect or are affected by the environment is more difficult to write than testing simple functions, but you can mock the impact on the environment or stub the impact from the environment. We've seen how easy it is to create reproducible test code by replacing it.

In this example, I simply overwrote the function, but the mock module has a handy thing called patch, which makes it easy to replace the function only during the test and then restore it. Use it when writing tests.

Recommended Posts

Touch the mock and stub
Mock urllib2 and unittest
I'll inherit and override the class
The story of Python and the story of NaN
Run Pylint and read the results
Let's touch on the Go language
Installing and uninstalling the egg package
Touch the object of the neural network
Judge the extension and download the image
Understanding and implementing the Tonelli-Shanks algorithm (1)
The story of making a sound camera with Touch Designer and ReSpeaker