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

306 投票
4 回答
248409 浏览
提问于 2025-04-17 00:41

Mock 有一个很有用的 方法叫做 assert_called_with()。不过,按照我的理解,这个方法只检查方法的 最后一次 调用。
如果我有一段代码,它连续调用了被模拟的方法 3 次,每次使用不同的参数,我该如何验证这 3 次调用及其具体参数呢?

4 个回答

182

通常情况下,我不太在乎调用的顺序,只要它们发生过就行。在这种情况下,我会把 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(模拟对象)是为了测试面向对象设计中对象之间的通信而设计的。如果你的设计不是面向对象的(比如更偏向过程式或函数式),那么使用模拟可能完全不合适。你可能在方法内部处理的事情太多,或者你可能在测试一些内部细节,而这些细节最好不要用模拟来测试。我在我的代码还不是很面向对象的时候,发展出了这个方法提到的策略,我相信当时我也在测试一些最好不使用模拟的内部细节。

307

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

75

你可以使用 Mock.call_args_list 这个属性 来比较之前方法调用时传入的参数。结合使用 Mock.call_count 这个属性,你就可以完全掌控这些调用了。

撰写回答