Python appel simulé args liste déballage tuples pour assertion sur les arguments

j'ai du mal à m'occuper du nested tuple qui Mock.call_args_list retourne.

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'))

je voudrais savoir s'il y a une meilleure façon de déballer cette call_args_list sur l'objet mock afin de faire mon affirmation. Cette boucle fonctionne, mais il se sent comme il doit y avoir plus simple.

16
demandé sur nackjicholson 2016-09-24 00:05:12

2 réponses

je pense que beaucoup des difficultés ici sont liées au traitement de l'objet "call". Il peut être considéré comme un tuple avec 2 membres (args, kwargs) et donc il est souvent agréable de le décompresser:

args, kwargs = call

une fois déballé, vous pouvez faire vos assertions séparément pour args et kwargs (puisque l'un est un tuple et l'autre un 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))

notez que parfois la terseness n'est pas utile (par exemple s'il y a une erreur):

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)
26
répondu mgilson 2016-09-23 21:20:48

une meilleure façon pourrait être de construire les appels attendus votre auto puis utiliser une assertion directe:

>>> 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)

notez que les appels doivent être séquentiels, si vous ne le voulez pas, alors outrepassez le any_order kwarg à l'affirmation.

notez aussi qu'il est permis de faire des appels supplémentaires avant ou après le spécifié appels. Si vous ne voulez pas, vous aurez besoin d'ajouter une autre assertion:

>>> assert f.call_count == len(expected_calls)

réponse au commentaire de mgilson, voici un exemple de création d'un objet factice que vous pouvez utiliser pour le générique de comparaison d'égalité:

>>> 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)

et un exemple de mode d'échec:

>>> 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: []
8
répondu wim 2016-09-23 21:30:04