8

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

+0

Я думаю, что это действительно зависит от того, что утверждение вы тр чтобы сделать. Вы пытаетесь проверить только позиционные аргументы? Вы пытаетесь проверить позиционные и ключевые слова? Вы пытаетесь проверить сами ключевые слова или значения, переданные с помощью аргументов ключевого слова? например если вы хотите только проверить, что первый позиционный аргумент начинается с '' PASS'', тогда 'self.assertTrue (вызов [0] [0] .startswith ('Pass'))' должен делать трюк без внутренних 2 циклов , – mgilson

ответ

11

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

args, kwargs = call 

После того, как он распаковывается, то вы можете сделать ваши утверждения отдельно для арг и 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) 
+0

На самом деле мне нравится ваше решение лучше. Меньшая логика в тестовом коде ... – wim

+0

@wim - Да.Я видел, как ваш путь использовался _many_ раз, но всегда казалось, что он слишком сильно пытается стать типом записи/воспроизведения. Там _are_ записи/воспроизведения фреймворки для python unit-тестов, но я никогда не считал 'unittest.mock' одним из них :-) – mgilson

5

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

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

Это работает только в том случае, если OP действительно знает, что каждый вызов должен выглядеть как _exactly_. Основываясь на вопросе, похоже, что OP только знает, как должна выглядеть первая часть каждого параметра (что странно ... но эй ...) – mgilson

+0

Хм, 'звонок' - это всего лишь кортеж, чтобы он все еще мог быть модифицированным для работы. Я отредактирую, чтобы показать, как ... – wim

+0

Более того, я не хочу, чтобы мой тест заботился о том, что происходит после первой части параметра, потому что то, что происходит после, действительно многословно и сильно меняется, поэтому, это становится очень утомительным. – nackjicholson

Смежные вопросы