断言连续调用模拟方法

模拟有一个有用的assert_called_with()方法 。 不过,据我所知,这只会检查最后一次调用方法。
如果我有连续三次调用模拟方法的代码,每次使用不同的参数,我如何使用它们的特定参数来声明这三个调用?

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

从文档:

assert_has_calls (调用,any_order = False)

断言模拟已经被指定的调用调用。 mock_calls列表被检查的调用。

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

如果any_order是真的,那么这些调用可以以任何顺序,但是它们都必须出现在mock_calls中。

例:

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

资料来源: http : //www.voidspace.org.uk/python/mock/mock.html#mock.Mock.assert_has_calls

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

编辑

自从我发布这个答案以来,我一直在重新思考我的testing方法。 我认为值得一提的是,如果您的testing变得复杂,您可能会进行不适当的testing或者有devise问题。 Mocks被devise用于在面向对象devise中testing对象间通信。 如果你的devise没有被反对(如更多的程序或function),模拟可能是完全不合适的。 您可能在方法内部还有太多的事情要做,或者您可能正在testing内部细节,这些细节最好不要被解决。 当我的代码不是非常面向对象的时候,我开发了这个方法中提到的策略,我相信我也在testing内部的细节,这些细节本来是最好的。

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

我总是必须一次又一次地看这个,所以这是我的答案。

在同一个类的不同对象上声明多个方法调用

假设我们有一个重型class(我们想模拟):

 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函数的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来嘲笑MockHeavyDuty类。 要断言来自每个HeavyDuty实例的方法调用,我们必须引用MockHeavyDuty.return_value.assert_has_calls ,而不是MockHeavyDuty.assert_has_calls 。 另外,在expected_calls列表中,我们必须指定我们感兴趣的哪个方法名称。 所以我们的列表是由call.do_work调用的,而不是简单的call

行使testing案例告诉我们它是成功的:

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

如果我们修改了heavy_work函数,testing失败并产生一个有用的错误信息:

 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