断言对mock方法的连续调用

Jon*_*han 141 python mocking

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

Pig*_*ras 144

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

来自文档:

assert_has_calls (calls,any_order = False)

断言已使用指定的调用调用了mock.将检查mock_calls列表的调用.

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

如果any_order为True,那么调用可以是任何顺序,但它们必须全部出现在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)
Run Code Online (Sandbox Code Playgroud)

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

  • Mock的早期版本使用了一个简单的元组,但事实证明它使用起来很尴尬.每个函数调用都会收到一个(args,kwargs)元组,所以要检查"foo(123)"是否被正确调用,你需要"断言mock.call_args ==((123,),{})",这是与"呼叫(123)"相比,满口 (11认同)
  • 有点奇怪,他们选择添加一个新的"调用"类型,他们也可以使用一个列表或元组... (7认同)
  • @jaapz 它是 `tuple` 的子类:`isinstance(mock.call(1), tuple)` 给出了 `True`。他们还添加了一些方法和属性。 (2认同)
  • @CodeWithPride它看起来更适合`side_effect` (2认同)

jpm*_*c26 90

通常,我不关心电话的顺序,只是他们发生了.在那种情况下,我结合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
Run Code Online (Sandbox Code Playgroud)

我发现这样做比传递给单个方法的大量调用更容易阅读和理解.

如果您确实关心订单或者您希望进行多次相同的呼叫,则assert_has_calls可能更合适.

编辑

自从我发布这个答案以来,我一直在考虑我的测试方法.我认为值得一提的是,如果您的测试变得复杂,您可能会进行不适当的测试或出现设计问题.模拟被设计用于在面向对象的设计中测试对象间通信.如果您的设计没有面向对象(如更多程序或功能),那么模拟可能完全不合适.您可能也在方法内部进行了太多操作,或者您可能正在测试最好不要进行模拟的内部详细信息.当我的代码不是面向对象的时候,我开发了这个方法中提到的策略,我相信我也在测试内部细节,这些内容最好不会被取消.


Jon*_*han 40

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

  • assert_has_calls()? (9认同)
  • `assert_has_calls`只检查预期的调用是否已完成,但如果只是那些调用则不会. (4认同)

Ped*_*rte 16

我总是要一次又一次地看这个,所以这是我的答案.


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

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

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))
   ...:  
Run Code Online (Sandbox Code Playgroud)

这是一些使用HeavyDuty该类的两个实例的代码 :

In [2]: def heavy_work():
   ...:     hd1 = HeavyDuty()
   ...:     hd1.do_work(13, 17)
   ...:     hd2 = HeavyDuty()
   ...:     hd2.do_work(23, 29)
   ...:    
Run Code Online (Sandbox Code Playgroud)


现在,这是一个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)
   ...:  
Run Code Online (Sandbox Code Playgroud)

我们正在嘲笑这个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
Run Code Online (Sandbox Code Playgroud)


如果我们修改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)]
Run Code Online (Sandbox Code Playgroud)


断言对函数的多个调用

与上面的对比,这里有一个示例,说明如何模拟对函数的多个调用:

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
Run Code Online (Sandbox Code Playgroud)


主要有两个不同之处.第一个是在模拟函数时,我们使用call而不是使用来设置我们的预期调用call.some_method.第二个就是我们所说assert_has_callsmock_work_function,而不是上mock_work_function.return_value.