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


У меня возникли некоторые проблемы с вложенным кортежем, который 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 на макет объекта, чтобы сделать мое утверждение. Эта петля работает, но кажется, что должен быть более прямой путь вперед.

2 16

2 ответа:

Я думаю, что многие из трудностей здесь связаны с обработкой объекта "вызов". Его можно рассматривать как кортеж с 2 членами (args, kwargs), и поэтому часто приятно распаковать его:

args, kwargs = call

Как только он распакован, вы можете сделать свои утверждения отдельно для args и kwargs (так как один является кортежем, а другой-диктом)

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)

Более приятным способом может быть создание ожидаемых вызовов вашего "я", а затем использование прямого утверждения:

>>> 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: []