问题:断言对模拟方法的后续调用
模拟有一个有用的assert_called_with()
方法。但是,据我了解,这仅检查对方法的最后一次调用。
如果我有连续3次调用该模拟方法的代码,每次使用不同的参数,那么该如何用其特定的参数来断言这3次调用?
回答 0
assert_has_calls
是解决此问题的另一种方法。
从文档:
assert_has_calls (calls,any_order = False)
断言已使用指定的调用调用了该模拟。检查嘲笑列表中是否有呼叫。
如果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
回答 1
通常,我不关心呼叫的顺序,只关心它们的发生。在这种情况下,我结合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
可能更合适。
编辑
自从我发布了这个答案以来,我就重新考虑了一般的测试方法。我认为值得一提的是,如果您的测试变得如此复杂,则可能是测试不合适或存在设计问题。模拟设计用于在面向对象的设计中测试对象之间的通信。如果您的设计不是面向对象的(如在更多过程或功能上),则该模拟可能完全不合适。您可能还会在方法内部进行过多操作,或者您可能正在测试最好不要进行内部模拟的内部细节。当我的代码不是非常面向对象时,我开发了此方法中提到的策略,并且我相信我也在测试内部细节,而这些细节最好不要假装。
回答 2
您可以使用该Mock.call_args_list
属性将参数与以前的方法调用进行比较。结合Mock.call_count
属性应该可以完全控制您。
回答 3
我总是不得不一次又一次地看一下这个,所以这是我的答案。
在同一个类的不同对象上声明多个方法调用
假设我们有一个重载类(我们要模拟):
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)
...:
我们正在用嘲笑HeavyDuty
课堂MockHeavyDuty
。要断言来自每个HeavyDuty
实例MockHeavyDuty.return_value.assert_has_calls
(而不是)的方法调用MockHeavyDuty.assert_has_calls
。另外,在列表中,expected_calls
我们必须指定对断言调用感兴趣的方法名称。因此,我们的清单由对的调用组成call.do_work
,而不是简单的call
。
行使测试用例可以证明它是成功的:
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
。第二个就是我们所说assert_has_calls
的mock_work_function
,而不是上mock_work_function.return_value
。