断言对模拟方法的连续调用

Mock有有用的assert_called_with()方法。然而,据我所知,这只检查最后的对方法的调用 如果我有连续3次调用模拟方法的代码,每次都有不同的参数,我如何使用它们特定的参数断言这3次调用?< / p >

189719 次浏览

可以使用Mock.call_args_list属性将参数与之前的方法调用进行比较。这与Mock.call_count属性一起应该给你完全的控制权。

通常,我不关心电话的顺序,只关心它们发生了什么。在这种情况下,我将assert_any_call与关于call_count的断言结合起来。

>>> import mock
>>> m = mock.Mock()
>>> m(1)
<Mock name='mock()' id='37578160'>
>>> m(2)
<Mock name='mock()' id='37578160'>
>>> m(3)
<Mock name='mock()' id='37578160'>
>>> m.assert_any_call(1)
>>> m.assert_any_call(2)
>>> m.assert_any_call(3)
>>> assert 3 == m.call_count
>>> m.assert_any_call(4)
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
File "[python path]\lib\site-packages\mock.py", line 891, in assert_any_call
'%s call not found' % expected_string
AssertionError: mock(4) call not found

我发现这样做比将大量调用传递到单个方法中更容易阅读和理解。

如果你确实关心顺序,或者你希望有多个相同的调用,assert_has_calls可能更合适。

编辑

自从我发布了这个答案,我重新思考了我的测试方法。我认为值得一提的是,如果您的测试变得如此复杂,那么您可能测试不当或存在设计问题。模拟是为测试面向对象设计中的对象间通信而设计的。如果您的设计不是面向对象的(更偏向于过程性或功能性),mock可能是完全不合适的。方法内部也可能有太多事情要做,或者您可能正在测试最好不要模拟的内部细节。当我的代码不是非常面向对象的时候,我开发了这个方法中提到的策略,而且我相信我还在测试最好不要模仿的内部细节。

assert_has_calls是解决这个问题的另一种方法。

从文档中可以看出:

assert_has_calls (电话,any_order = False)

断言mock已被 使用指定的调用调用。检查mock_calls列表 调用。< / p >

如果any_order为False(默认值),则调用必须是顺序的。 在指定的调用之前或之后可以有额外的调用

如果any_order为True,则调用可以是任何顺序,但它们必须是

例子:

>>> from unittest.mock import call, Mock
>>> mock = Mock(return_value=None)
>>> mock(1)
>>> mock(2)
>>> mock(3)
>>> mock(4)
>>> calls = [call(2), call(3)]
>>> mock.assert_has_calls(calls)
>>> calls = [call(4), call(2), call(3)]
>>> mock.assert_has_calls(calls, any_order=True)

来源:https://docs.python.org/3/library/unittest.mock.html#unittest.mock.Mock.assert_has_calls

我总是要一次又一次地查阅这个问题,所以这是我的答案。

< br >

对同一类的不同对象断言多个方法调用

假设我们有一个重载类(我们想要模拟它):

In [1]: class HeavyDuty(object):
...:     def __init__(self):
...:         import time
...:         time.sleep(2)  # <- Spends a lot of time here
...:
...:     def do_work(self, arg1, arg2):
...:         print("Called with %r and %r" % (arg1, arg2))
...:

下面是一些使用HeavyDuty类的两个实例的代码:

In [2]: def heavy_work():
...:     hd1 = HeavyDuty()
...:     hd1.do_work(13, 17)
...:     hd2 = HeavyDuty()
...:     hd2.do_work(23, 29)
...:

< br >

现在,下面是heavy_work函数的测试用例:

In [3]: from unittest.mock import patch, call
...: def test_heavy_work():
...:     expected_calls = [call.do_work(13, 17),call.do_work(23, 29)]
...:
...:     with patch('__main__.HeavyDuty') as MockHeavyDuty:
...:         heavy_work()
...:         MockHeavyDuty.return_value.assert_has_calls(expected_calls)
...:

我们正在用MockHeavyDuty模拟HeavyDuty类。要断言来自每个HeavyDuty实例的方法调用,我们必须引用MockHeavyDuty.return_value.assert_has_calls,而不是MockHeavyDuty.assert_has_calls。此外,在expected_calls的列表中,我们必须指定我们感兴趣的断言调用的方法名。因此,我们的列表由对call.do_work的调用组成,而不是简单的call

测试用例表明它是成功的:

In [4]: print(test_heavy_work())
None

< br >

如果修改heavy_work函数,测试会失败并产生有用的错误消息:

In [5]: def heavy_work():
...:     hd1 = HeavyDuty()
...:     hd1.do_work(113, 117)  # <- call args are different
...:     hd2 = HeavyDuty()
...:     hd2.do_work(123, 129)  # <- call args are different
...:


In [6]: print(test_heavy_work())
---------------------------------------------------------------------------
(traceback omitted for clarity)


AssertionError: Calls not found.
Expected: [call.do_work(13, 17), call.do_work(23, 29)]
Actual: [call.do_work(113, 117), call.do_work(123, 129)]

< br >

断言对一个函数的多次调用

为了与上面的对比,这里有一个例子,展示了如何模拟对一个函数的多次调用:

In [7]: def work_function(arg1, arg2):
...:     print("Called with args %r and %r" % (arg1, arg2))


In [8]: from unittest.mock import patch, call
...: def test_work_function():
...:     expected_calls = [call(13, 17), call(23, 29)]
...:     with patch('__main__.work_function') as mock_work_function:
...:         work_function(13, 17)
...:         work_function(23, 29)
...:         mock_work_function.assert_has_calls(expected_calls)
...:


In [9]: print(test_work_function())
None
< p > < br > 有两个主要的区别。第一个是在模拟函数时,我们使用call而不是call.some_method来设置预期的调用。第二个是我们在mock_work_function上调用assert_has_calls,而不是在mock_work_function.return_value