Python mock call_args_list распаковывает кортежи для утверждения по аргументам
У меня возникают проблемы с вложенным кортежем, который возвращает Mock.call_args_list
.
def test_foo(self):
def foo(fn):
fn('PASS and some other stuff')
f = Mock()
foo(f)
foo(f)
foo(f)
for call in f.call_args_list:
for args in call:
for arg in args:
self.assertTrue(arg.startswith('PASS'))
Я хотел бы знать, есть ли лучший способ распаковать этот call_args_list на макет объекта, чтобы сделать мое утверждение. Этот цикл работает, но кажется, что должен быть более прямой путь.
Ответы
Ответ 1
Я думаю, что многие из трудностей здесь завернуты в обработку объекта "вызова". Его можно рассматривать как кортеж с 2 членами (args, kwargs)
, и поэтому его часто приятно распаковывать:
args, kwargs = call
После его распаковки вы можете сделать свои утверждения отдельно для args и kwargs (так как один является кортежем, а другой a dict)
def test_foo(self):
def foo(fn):
fn('PASS and some other stuff')
f = Mock()
foo(f)
foo(f)
foo(f)
for call in f.call_args_list:
args, kwargs = call
self.assertTrue(all(a.startswith('PASS') for a in args))
Обратите внимание, что иногда сжатие не помогает (например, если есть ошибка):
for call in f.call_args_list:
args, kwargs = call
for a in args:
self.assertTrue(a.startswith('PASS'), msg="%s doesn't start with PASS" % a)
Ответ 2
Лучше всего создать надежные вызовы, а затем использовать прямое утверждение:
>>> from mock import call, Mock
>>> f = Mock()
>>> f('first call')
<Mock name='mock()' id='31270416'>
>>> f('second call')
<Mock name='mock()' id='31270416'>
>>> expected_calls = [call(s + ' call') for s in ('first', 'second')]
>>> f.assert_has_calls(expected_calls)
Обратите внимание, что вызовы должны быть последовательными, если вы не хотите, чтобы затем переопределить any_order
kwarg к утверждению.
Также обратите внимание, что это разрешено для дополнительных вызовов до или после
указанных вызовов. Если вы этого не хотите, вам нужно добавить еще одно утверждение:
>>> assert f.call_count == len(expected_calls)
Обращаясь к комментарию к mgilson, здесь приведен пример создания фиктивного объекта, который вы можете использовать для сопоставлений равенства шаблонов:
>>> class AnySuffix(object):
... def __eq__(self, other):
... try:
... return other.startswith('PASS')
... except Exception:
... return False
...
>>> f = Mock()
>>> f('PASS and some other stuff')
<Mock name='mock()' id='28717456'>
>>> f('PASS more stuff')
<Mock name='mock()' id='28717456'>
>>> f("PASS blah blah don't care")
<Mock name='mock()' id='28717456'>
>>> expected_calls = [call(AnySuffix())]*3
>>> f.assert_has_calls(expected_calls)
И пример режима сбоя:
>>> Mock().assert_has_calls(expected_calls)
AssertionError: Calls not found.
Expected: [call(<__main__.AnySuffix object at 0x1f6d750>),
call(<__main__.AnySuffix object at 0x1f6d750>),
call(<__main__.AnySuffix object at 0x1f6d750>)]
Actual: []