Python simula call_args_list desempaquetando tuplas para afirmar argumentos

5 minutos de lectura

Tengo algunos problemas para lidiar con la tupla anidada que Mock.call_args_list devoluciones.

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

Me gustaría saber si hay una mejor manera de descomprimir ese call_args_list en el objeto simulado para hacer mi afirmación. Este ciclo funciona, pero parece que debe haber una forma más directa.

  • Creo que esto realmente depende de la afirmación que estés tratando de hacer. ¿Está tratando de verificar solo argumentos posicionales? ¿Está tratando de verificar los argumentos posicionales y de palabras clave? ¿Está tratando de verificar las palabras clave en sí o los valores pasados ​​a través de argumentos de palabras clave? por ejemplo, si solo desea verificar que el primer argumento posicional comienza con 'PASS'después self.assertTrue(call[0][0].startswith('Pass')) debería hacer el truco sin los 2 bucles internos.

    – mgilson

    23/09/2016 a las 21:10


Creo que muchas de las dificultades aquí están envueltas en el tratamiento del objeto “llamada”. Se puede considerar como una tupla con 2 miembros. (args, kwargs) y, por lo tanto, con frecuencia es bueno descomprimirlo:

args, kwargs = call

Una vez que se haya desempaquetado, puede hacer sus afirmaciones por separado para args y kwargs (ya que uno es una tupla y el otro 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))

Tenga en cuenta que a veces la brevedad no es útil (por ejemplo, si hay un error):

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)

  • @wim – Sí. He visto tu camino usado muchos veces, pero siempre sentí que era demasiado tratar de ser un tipo de marco de grabación/reproducción. Ahí son marcos de grabación/reproducción para pruebas unitarias de Python, pero nunca consideré realmente unittest.mock ser uno de ellos 🙂

    – mgilson

    23/09/2016 a las 21:52

  • Oh hombre args, kwargs = call es la clave de todo call_args_list hacks ¡Gracias!

    – I159

    17 de enero de 2020 a las 11:58

  • Si sabe que solo habrá un argumento posicional (message en este caso), puede descomprimirlo en un solo paso: (message, ), kwargs = call

    – Fush

    23 de junio de 2021 a las 7:25

avatar de usuario de wim
ingenio

Una forma más agradable podría ser construir las llamadas esperadas usted mismo y luego usar una afirmación directa:

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

Tenga en cuenta que las llamadas deben ser secuenciales, si no desea eso, anule el any_order kwarg a la afirmación.

También tenga en cuenta que está permitido que haya llamadas adicionales antes o después de las llamadas especificadas. Si no desea eso, deberá agregar otra afirmación:

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

Abordando el comentario de mgilson, aquí hay un ejemplo de creación de un objeto ficticio que puede usar para comparaciones de igualdad de comodines:

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

Y un ejemplo del modo de falla:

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

  • Esto solo funciona si OP realmente sabe cómo debería ser cada llamada exactamente. Según la pregunta, parece que OP solo sabe cómo se supone que debe verse la primera parte de cada parámetro (lo cual es extraño … pero bueno …)

    – mgilson

    23/09/2016 a las 21:16


  • Mmm, call es solo una tupla, por lo que aún se puede modificar para que funcione. Voy a editar para mostrar cómo …

    – Wim

    23/09/2016 a las 21:17

  • Es más que no quiero que a mi prueba le importe lo que viene después de la primera parte del parámetro, porque lo que viene después es muy detallado y cambia mucho, por lo que hacer que las pruebas lo afirmen se vuelve realmente tedioso.

    – nackjicholson

    23/09/2016 a las 21:20

  • Agregué un ejemplo para mostrar cómo hacer una coincidencia aproximada de la llamada. Sin embargo, como advertencia, si no puede predecir exactamente el resultado de una llamada en las pruebas, a menudo es una señal de advertencia de que hay algo que no se burla y que debería haber sido…

    – Wim

    23/09/2016 a las 21:33


¿Ha sido útil esta solución?