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


当前回答

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

从文档中可以看出:

assert_has_calls (calls, any_order=False) 断言mock已经完成 使用指定的调用调用。检查mock_calls列表 的电话。 如果any_order为False(默认值),则调用必须是顺序的。 在指定的调用之前或之后可以有额外的调用。 如果any_order为True,则调用可以以任何顺序进行,但必须如此 所有都出现在mock_calls中。

例子:

>>> 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

其他回答

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

从文档中可以看出:

assert_has_calls (calls, any_order=False) 断言mock已经完成 使用指定的调用调用。检查mock_calls列表 的电话。 如果any_order为False(默认值),则调用必须是顺序的。 在指定的调用之前或之后可以有额外的调用。 如果any_order为True,则调用可以以任何顺序进行,但必须如此 所有都出现在mock_calls中。

例子:

>>> 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

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


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

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

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)
   ...:    


下面是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的列表中,我们必须指定我们感兴趣的断言调用的方法名。所以我们的清单上都是要拨打的电话。Do_work,而不是简单地调用。

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

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

如果我们修改了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)]

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

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

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

有两个主要的区别。第一个是在模拟一个函数时,我们使用call来设置预期的调用,而不是使用call.some_method。第二个是我们在mock_work_function上调用assert_has_calls,而不是在mock_work_function.return_value上调用。

通常,我不关心电话的顺序,只关心它们发生了什么。在这种情况下,我将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可能更合适。

Edit

Since I posted this answer, I've rethought my approach to testing in general. I think it's worth mentioning that if your test is getting this complicated, you may be testing inappropriately or have a design problem. Mocks are designed for testing inter-object communication in an object oriented design. If your design is not objected oriented (as in more procedural or functional), the mock may be totally inappropriate. You may also have too much going on inside the method, or you might be testing internal details that are best left unmocked. I developed the strategy mentioned in this method when my code was not very object oriented, and I believe I was also testing internal details that would have been best left unmocked.

你可以使用Mock。Call_args_list属性将参数与以前的方法调用进行比较。还有莫克。Call_count属性应该给予您完全的控制权。