Python 检查多个模拟的调用顺序
我有三个函数要测试调用顺序 假设在module.py中,我有以下内容Python 检查多个模拟的调用顺序,python,function,mocking,python-mock,Python,Function,Mocking,Python Mock,我有三个函数要测试调用顺序 假设在module.py中,我有以下内容 # module.py def a(*args): # do the first thing def b(*args): # do a second thing def c(*args): # do a third thing def main_routine(): a_args = ('a') b_args = ('b') c_args = ('c')
# module.py
def a(*args):
# do the first thing
def b(*args):
# do a second thing
def c(*args):
# do a third thing
def main_routine():
a_args = ('a')
b_args = ('b')
c_args = ('c')
a(*a_args)
b(*b_args)
c(*c_args)
我想检查b在a之后调用,在c之前调用。因此,为a、b和c中的每一个获取模拟很容易:
# tests.py
@mock.patch('module.a')
@mock.patch('module.b')
@mock.patch('module.c')
def test_main_routine(c_mock, b_mock, a_mock):
# test all the things here
检查每个单独的mock是否被调用也很容易。我如何检查呼叫相对于彼此的顺序
call_args_list
不起作用,因为它是为每个模拟单独维护的
我尝试使用副作用记录每个呼叫:
calls = []
def register_call(*args):
calls.append(mock.call(*args))
return mock.DEFAULT
a_mock.side_effect = register_call
b_mock.side_effect = register_call
c_mock.side_effect = register_call
但这只提供了调用mock时使用的参数,而不是调用所针对的实际mock。我可以补充一点逻辑:
# tests.py
from functools import partial
def register_call(*args, **kwargs):
calls.append(kwargs.pop('caller', None), mock.call(*args, **kwargs))
return mock.DEFAULT
a_mock.side_effect = partial(register_call, caller='a')
b_mock.side_effect = partial(register_call, caller='b')
c_mock.side_effect = partial(register_call, caller='c')
这似乎完成了任务。。。还有更好的办法吗?我觉得API中应该已经有一些东西可以做到这一点,但我没有。定义一个
Mock
管理器,并通过将Mock附加到它。然后检查模拟调用
:
@patch('module.a')
@patch('module.b')
@patch('module.c')
def test_main_routine(c, b, a):
manager = Mock()
manager.attach_mock(a, 'a')
manager.attach_mock(b, 'b')
manager.attach_mock(c, 'c')
module.main_routine()
expected_calls = [call.a('a'), call.b('b'), call.c('c')]
assert manager.mock_calls == expected_calls
为了测试它是否工作,请更改main_routine()
function add中函数调用的顺序,查看它是否抛出AssertionError
请参阅上的更多示例
希望有帮助。我今天需要这个答案,但问题中的示例代码确实很难阅读,因为调用参数与管理器上的模拟名称相同,并且在测试范围内,下面是非机器人的一个更清晰的例子。我正在修补的所有模块都是为了示例而构建的:
@patch('module.file_reader')
@patch('module.json_parser')
@patch('module.calculator')
def test_main_routine(mock_calculator, mock_json_parser, mock_file_reader):
manager = Mock()
# First argument is the mock to attach to the manager.
# Second is the name for the field on the manager that holds the mock.
manager.attach_mock(mock_file_reader, 'the_mock_file_reader')
manager.attach_mock(mock_json_parser, 'the_mock_json_parser')
manager.attach_mock(mock_calculator, 'the_mock_calculator')
module.main_routine()
expected_calls = [
call.the_mock_file_reader('some file'),
call.the_mock_json_parser('some json'),
call.the_mock_calculator(1, 2)
]
assert manager.mock_calls == expected_calls
注意,在这种情况下,您必须使用attach_mock
,因为您的mock是由补丁创建的。必须通过attach\u mock
附加带有名称的mock,包括由patch
创建的mock,此代码才能工作。如果您制作自己的mock
对象而没有名称,则不必使用attach\u mock
:
def test_main_routine(mock_calculator, mock_json_parser, mock_file_reader):
manager = Mock()
mock_file_reader = Mock()
mock_json_parser = Mock()
mock_calculator = Mock()
manager.the_mock_file_reader = mock_file_reader
manager.the_mock_json_parser = mock_json_parser
manager.the_mock_calculator = mock_calculator
module.main_routine()
expected_calls = [
call.the_mock_file_reader('some file'),
call.the_mock_json_parser('some json'),
call.the_mock_calculator(1, 2)
]
assert manager.mock_calls == expected_calls
如果要在缺少订单或预期调用时显示clear assertion failed消息,请使用以下断言行
self.assertListEqual(manager.mock_calls, [
call.the_mock_file_reader('some file'),
call.the_mock_json_parser('some json'),
call.the_mock_calculator(1, 2)
])
一个更干净的解决方案是将函数封装到一个类中,然后在测试中模拟该类。这将消除进行任何修补的需要(始终是一个加号)
在测试文件中,创建一个模拟包装器类,然后在调用wrapper.main_方法
时将模拟包装器作为参数插入(注意,这不会实例化该类)
好处:
- 无需修补
- 在测试中,您只需要键入被调用方法的名称一次(而不是2-3次)
- 使用
assert\u has\u calls
而不是将mock\u calls
属性与调用列表进行比较
- 可以设置为常规的
check\u for\u调用
函数(见下文)
一个重要的注意事项-不要在修补程序(…)内设置autospec=True。如果设置autospec=True,则连接\u mock不正确。您的示例中没有autospec,但它通常存在于实际测试用例中。此答案忠实于问题中的代码,但由于没有描述性名称,因此很难阅读。我在下面添加了一个答案,清楚地说明了attach\u mocks
的工作原理。@AndreyBelyak评论现在已经过时了-这是一个bug,并且已经修复(请参阅)
# module.py
class Wrapper:
def a(self, *args):
pass
def b(self, *args):
pass
def c(self, *args):
pass
def main_routine(self):
a_args = ('arg for a',)
b_args = ('arg for b',)
c_args = ('arg for c',)
self.a(*a_args)
self.b(*b_args)
self.c(*c_args)
# module_test.py
from unittest.mock import MagicMock, call
from module import Wrapper
def test_main_routine():
mock_wrapper = MagicMock()
Wrapper.main_routine(mock_wrapper)
expected_calls = [call.a('arg for a'),
call.b('arg for b'),
call.c('arg for c')]
mock_wrapper.assert_has_calls(expected_calls)
# module_better_test.py
from unittest.mock import MagicMock, call
from module import Wrapper
def test_main_routine():
expected_calls = [call.a('arg for a'),
call.b('arg for b'),
call.c('arg for c')]
check_for_calls('main_routine', expected_calls)
def check_for_calls(method, expected_calls):
mock_wrapper = MagicMock()
getattr(Wrapper, method)(mock_wrapper)
mock_wrapper.assert_has_calls(expected_calls)