Подтвердить что ты не робот

Проверка порядка звонков по нескольким макетам

У меня есть три функции, которые я пытаюсь проверить порядок вызовов.

Скажем, что в модуле 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)

Я хочу проверить, что b вызывается после a и до c. Так что получить макет для каждого из, 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

Проверка того, что каждый из них называется так же просто, тоже. Как проверить порядок вызовов относительно друг друга?

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

Но это только дает мне аргументы, с которыми были вызваны издевательства, но не фактический макет, который был вызван. Я могу добавить немного больше логики:

# 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 уже должно быть что-то, что может сделать это, что мне не хватает.

4b9b3361

Ответ 1

Определите менеджера Mock и добавьте в него mocks через 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

Чтобы проверить, что он работает, измените порядок вызовов функций в функции main_routine() add, см., что он выбрасывает AssertionError.

См. дополнительные примеры в Порядок отслеживания вызовов и менее подробные утверждения по запросу

Надеюсь, что это поможет.

Ответ 2

Мне нужен был этот ответ сегодня, но пример кода в вопросе действительно трудно прочитать, потому что аргументы вызова совпадают с именами насмешек на менеджере и в объеме теста. Здесь официальная документация по этой концепции, а ниже приведен более понятный пример для не-роботов. Все модули, которые я исправляю, сделаны для примера:

@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 потому что ваши макеты были созданы patch. Моды с именами, включая созданные patch, должны быть прикреплены через 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