Mock in English means "imitation", "fake". A module with this name helps to greatly simplify the tests of modules on Python.
The principle of its operation is simple: if you need to test a function, then everything that does not apply to it itself (for example, reading from disk or from the network) can be replaced with mock-ups. At the same time, the tested functions do not need to be adapted for tests: Mock replaces objects in other modules, even if the code does not accept them as parameters. That is, you can test without any adaptation to the tests.
Such behavior is no longer an inflatable rocket launcher, but a whole inflatable earth around which test rocket and airplanes can fly.
')

On Habré, the
Mock package was mentioned in only
one comment . In principle, we sometimes have to substitute objects in other modules, which is called the monkey patch. But unlike hand patches, Mock can make very complex substitutions, with packs and call chains, and get away with it, leaving no side effects.
The package takes less than 1 megabyte and is installed very simply:
$ pip install mock
or
$ easy_install mock
And now it can be used.
Function substitution
Let's say our function considers something, and very long:
from itertools import permutations def real(name): if len(name) < 10: raise ValueError('String too short to calculate statistics.') y = 0 for i in permutations(xrange(len(name)), 10): y += i print y
An example is contrived and primitive, but something similar can occur: large calculations are done inside, which I would not like to repeat in the test. And in this example, I would like to enter a shorter string (so that the number of repetitions in permutations, len (name), was less), but this is prohibited. It would be possible to split the function into two, make the call to permutations outside, and transfer its output to the function, but it can be done differently.
Instead of rewriting the code, we can simply patch the permutations function at the time of the call, specify only a certain output and call some code:
from mock import patch import itertools # : , name = ' ' >>> with patch('itertools.permutations') as perm_mock: ... perm_mock.return_value = xrange(3) ... real(name) 1 3 6
Note: print is called instead of
42! / (42 - 10)! just 3, that is, the cycle ran through xrange (3), which we substituted.
In addition, after exiting the context
with, the function itertools.permutations returned to its normal state.
Debug info
Suppose you need to check what happens to the object that we passed to the function. Whether in the sequence, with those parameters, methods are invoked, or those attributes are called. For you just need to run into it an object Mock, which will record everything that happens.
A similar life example: when Buran’s aerodynamic tests were going on, the whole ship was not blown through a pipe (these do not exist) and were not launched into the atmosphere. A special prototype of the BTS-002 flew in the air, and a retrofitted Tu-154 in a body kit that echoed Buran’s aerodynamics was used for landing tests.

The Mock object has several attributes with call information:
- called - whether the object was called at all
- call_count - the number of calls
- call_args - last call arguments
- call_args_list - a list of all arguments
- method_calls - arguments of calls to nested methods and attributes (about them below)
- mock_calls is the same, but together for the object itself and for nested
In our example above, we can verify that the real () function correctly calls permutations. To verify more precisely, for example, in automatic tests, you can call one of the
assert_ * methods :
perm_mock.assert_called_with(xrange(len(name)), 10)
Syntactic sugar
For unit tests in Django, it’s useful that the patch works as a decorator:
@patch('itertools.permutations') def test(ip): ip.return_value = range(5) print list(itertools.permutations(xrange(10), 10)) >>> test() [0, 1, 2, 3, 4]
Attribute and Call Chains
Sometimes in Django you need to do something with the files, and it is better to do without saving them to a disk or other storage. In the usual way, we would inherit from the File class and rewrite some properties, which would be cumbersome. But in Mock, you can immediately describe both attributes and call chains:
mock_file = Mock() mock_file.name = 'my_filename.doc' mock_file.__iter__.return_value = [' 1', ' 2', ' 3'] stat = mock_file.stat.return_value stat.size, stat.access_time = 1000, datetime(2012, 1, 1)
Here is how much test code has been saved. By the way, when passing an object as an argument or expecting an object from a function, it is useful to give them names to distinguish:
>>> mock_a = Mock(name=' ') >>> mock_a <Mock name=' ' id='169542476'>
How do these attribute chains work?
>>> m = Mock() >>> m <Mock id='167387660'> >>> m.any_attribute <Mock name='mock.any_attribute' id='167387436'> >>> m.any_attribute <Mock name='mock.any_attribute' id='167387436'> >>> m.another_attribute <Mock name='mock.another_attribute' id='167185324'>
As you can see, accessing the attribute produces another instance of the Mock class, and repeated reference to the same attribute again gives the same instance. An attribute can be anything, including a function. Finally, any layout can be called (say, instead of a class):
>>> m() <Mock name='mock()' id='167186284'> >>> m() is m False
This will be a different instance, but if called again, the instance will be the same. So we can assign some properties to these objects, and then pass this object to the code under test, and they will be read there.
If we assign a value to an attribute, then no surprises: the next time we receive it, we’ll get this value:
>>> m.any_attribute <Mock name='mock.any_attribute' id='167387436'> >>> m.any_attribute = 5 >>> m.any_attribute 5
It is useful to read in the
class documentation , what are the attributes of his relatives, so that name clashes do not happen.
How to limit layout flexibility
As you can see, you can access any layout attribute without causing an AttributeError error. This convenience has a downside: what if we change the API, for example, rename a method, and the function that works with our class will access the previous method? The code doesn't actually work, and the test runs without errors. To do this, you can specify an object specification in the
spec parameter (either to the Mock class or to the patch), and the layout will cause an error when accessing non-existent properties:
class Tanchik(object): model = 'T80' def shoot(self, target): print '!' def tank_sugar(target): print '%s ' % tank.model tank.shoot(target) return tank ================== import tanks @patch('tanks.Tanchik', spec=tanks.Tanchik)
Now if we rename the model or shoot at the tanchik, but forget to fix tank_sugar, the test will fail.
How to make the layout smarter
Well, let's say, Mock can replace the necessary objects with unnecessary ones and replace the output. Is it possible to replace the function with something more complicated than the value (return_value)? There are 2 ways:
- if you need to override a lot of methods on an instance or class, we inherit from the Mock class
- if you need to replace only one call (the method or the class itself), use side_effect .
def simple_function(args): do_something_useful() with patch('module.BigHeavyClass', side_effect=simple_function) as mock_class: another_class.take(mock_class)
By the way, you do not need to write a control output in simple_function, because, as mentioned above, at the end of the code in the mock_class object you can read method_calls.
Substitution of built-in functions
Mock by itself cannot replace the functions built into the language (len, iter), but it can make a
layout with the necessary “magic” functions . For example, here we make a file layout:
>>> mock = Mock() >>> mock_bz2module.open.return_value.__enter__ = Mock(return_value='foo') >>> mock_bz2module.open.return_value.__exit__ = Mock(return_value=False) >>> with mock_bz2module.open('test.bz2') as m: ... assert m == 'foo'
For many similar cases when you need to emulate a standard object (list, file, number), there is a
MagicMock class with a set of values ​​suitable for tests.
Where Mock is not working
The author of the module itself, Michael Fourd, says that the principle is where the layouts are needed, and where not, the simple: if it is easier to test with the layouts, they should be used, and if it is more difficult with them, they should be abandoned.
It so happens that you need to test a bunch of two modules, new and old, and in a bunch of many cross calls. We need to look carefully: if we gradually begin to rewrite the behavior of the whole module, it's time to stop - the test code is tightly tied to the module code, and with every change in the working code, we will have to change the tests. In addition, you should not try to write a whole module layout instead of the old one.
In my personal experience, Mock may conflict with debuggers, for example, there was
an infinite recursion in
PuDB .
IPDB worked fine, so we ran the project tests with IPDB, and just the code on PuDB.
findings
Mock layouts can be set up anywhere and in any way. Your code will not have to be customized for testing, which means faster development, and, perhaps, faster prototyping.
You can throw away all unnecessary, all time and resources from tests, leaving only the code that needs to be checked to work.
Layout settings where you need hard (spec), where you need flexible, and patches do not leave behind a trace.
Links