断言对模拟方法的连续调用
Mock 有一个很有用的 方法叫做 assert_called_with()
。不过,按照我的理解,这个方法只检查方法的 最后一次 调用。
如果我有一段代码,它连续调用了被模拟的方法 3 次,每次使用不同的参数,我该如何验证这 3 次调用及其具体参数呢?
4 个回答
通常情况下,我不太在乎调用的顺序,只要它们发生过就行。在这种情况下,我会把 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
可能更合适。
编辑
自从我发布这个回答以来,我重新思考了我对测试的整体方法。我觉得值得提一下,如果你的测试变得这么复杂,可能是因为你测试得不太合适,或者你的设计有问题。Mocks(模拟对象)是为了测试面向对象设计中对象之间的通信而设计的。如果你的设计不是面向对象的(比如更偏向过程式或函数式),那么使用模拟可能完全不合适。你可能在方法内部处理的事情太多,或者你可能在测试一些内部细节,而这些细节最好不要用模拟来测试。我在我的代码还不是很面向对象的时候,发展出了这个方法提到的策略,我相信当时我也在测试一些最好不使用模拟的内部细节。
assert_has_calls
是解决这个问题的另一种方法。
根据文档:
assert_has_calls (calls, any_order=False)
这个方法用来检查模拟对象(mock)是否被调用过指定的调用(calls)。它会查看模拟调用列表(mock_calls)来确认。
如果
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
你可以使用 Mock.call_args_list
这个属性 来比较之前方法调用时传入的参数。结合使用 Mock.call_count
这个属性,你就可以完全掌控这些调用了。