#python #pytest
#python #pytest
Вопрос:
Я пишу несколько тестов для функции, которая передает экземпляр коллекций.Счетчик другой функции, которую мне нужно издеваться. Вот небольшой пример, представляющий мой код:
# 'bar' module
def callback(counter):
"""Do something with the *counter*."""
pass
def update():
"""Update counter and pass it to callback."""
counter = collections.Counter()
for _ in range(3):
counter.update(["foo"])
callback(counter)
Следующий тест не проходит, поскольку тот же экземпляр счетчика был изменен и передан функции ‘обратного вызова’. Следовательно, утверждение будет передаваться только тогда, когда все вызовы принимают последнее значение счетчика, которое collections.Counter({"foo": 3})
@pytest.fixture()
def mocked_callback(mocker):
return mocker.patch("bar.callback")
def test_update(mocker, mocked_callback):
update()
assert mocked_callback.call_args_list == [
mocker.call(collections.Counter({"foo": 1})),
mocker.call(collections.Counter({"foo": 2})),
mocker.call(collections.Counter({"foo": 3})),
]
Кто-нибудь знает хороший способ проанализировать точное состояние измененного объекта, когда он передается в издевательскую функцию?
Комментарии:
1. Вы уверены, что издеваетесь над правильной функцией? Значение
__name__
int в тестовом примере, вероятно, может отличаться от имени модуля, который вы пытаетесь протестировать…2. Здесь в коде есть пара ошибок (упомянутая
__name__
и путаницаcallback
иdo_something
), но основная проблема заключается в том, что вызовы ссылаются на dict, который обновляется во время вызовов, так что после вызовов все вызовы указывают на один и тот же (окончательный) dict{"foo": 3}
(not{"foo": 1}
) . Я не уверен, как это решить…3. Спасибо, я исправил опечатку, которую вы заметили. Также мои реальные тесты не смешиваются в том же модуле, что и тестируемый код, я просто использовал
__name__
для предоставления примера для копирования 🙂4. @MrBeanBremen Это именно та проблема, которую я пытаюсь решить, да. Прямо сейчас мой единственный обходной путь — шпионить
collections.Counter.update
вместо проверки аргументов обратного вызова, но я чувствую, что должен быть лучший способ..
Ответ №1:
Эта проблема фактически упоминается в документации unittest.
Другая ситуация встречается редко, но может вас укусить, когда ваш макет вызывается с изменяемыми аргументами. call_args и call_args_list хранят ссылки на аргументы. Если аргументы изменены тестируемым кодом, вы больше не можете делать утверждения о том, какие значения были при вызове макета.
Предложенный обходной путь, похоже, работает отлично!
@pytest.fixture()
def mocked_callback(mocker):
class _CopyingMock(mocker.MagicMock):
"""Extended Mocker to copy arguments on each call."""
def __call__(self, *args, **kwargs):
args = copy.deepcopy(args)
kwargs = copy.deepcopy(kwargs)
return super(_MagicMock, self).__call__(*args, **kwargs)
return mocker.patch("bar.callback", _CopyingMock())
Есть заявки на включение этой функции в основную библиотеку:
Комментарии:
1. Приятно! На самом деле я думал об этом как об обходном пути (только что написал соответствующий комментарий), но я не знал, что это есть в документации.
2. На самом деле я собирался спросить о репозитории pytest-mock, когда я наткнулся на аналогичный вопрос , вот где я нашел ссылку 🙂