检查多个模拟中的呼叫顺序

Sha*_*efe 29 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')

    a(*a_args)
    b(*b_args)
    c(*c_args)
Run Code Online (Sandbox Code Playgroud)

我想检查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
Run Code Online (Sandbox Code Playgroud)

检查每个单独的模拟被调用也很容易.如何检查呼叫相对于彼此的顺序?

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

但是这只能给我一些嘲笑的诅咒,而不是实际的嘲笑.我可以添加更多逻辑:

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

而这似乎完成了工作......虽然有更好的方法吗?感觉应该已经在API中已经有一些东西可以做到这一点,我错过了.

ale*_*cxe 30

定义一个Mock管理器并通过它来附加模拟attach_mock().然后检查mock_calls:

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

只是为了测试它是否有效,在main_routine()函数add中更改函数调用的顺序,看它是否抛出AssertionError.

请参阅跟踪调用顺序和更简洁的调用断言的更多示例

希望有所帮助.

  • 这个答案忠实于问题中的代码,但很难阅读,因为没有描述性名称。我在下面添加一个答案,清楚地表明“attach_mocks”是如何工作的。 (5认同)
  • 一个重要的通知 - 不要在补丁(...)中设置autospec = True.如果你设置autospec = True attach_mock没有正确wotk.您的示例中没有自动规范,但它通常存在于现实生活中的测试用例中. (3认同)
  • @AndreyBelyak 评论现在已经过时了 - 这是一个错误,现已修复(请参阅[此处](/sf/ask/3945726851/ -模拟/61628148#61628148)) (2认同)

lor*_*mer 7

我今天需要这个答案,但是问题中的示例代码确实很难阅读,因为调用args与管理器上以及测试范围内的模拟名称相同。这是有关此概念的官方文档,下面是非机器人的更清晰示例。为了示例,我正在修补的所有模块都是:

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

请注意,attach_mock在这种情况下您必须使用,因为模拟是由创建的patchpatch要通过attach_mock该代码工作,必须附加具有名称(包括由创建的名称)的模拟项。您没有使用attach_mock,如果你让你自己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
Run Code Online (Sandbox Code Playgroud)