es solo un hecho de la vida, a medida que el código crece, eventualmente, tendrá que comenzar a agregar burlones a su suite de pruebas. Lo que comenzó como un lindo proyecto de dos clases ahora está hablando con servicios externos y ya no se puede probar cómodamente.

Es por eso que Python viene con unittest.mock, una parte poderosa de la biblioteca estándar para rellenar dependencias y burlarse de efectos secundarios.

sin Embargo, unittest.mock no es intuitivo.,

Me he encontrado muchas veces preguntándome por qué mi receta no funciona para un caso en particular, así que he reunido esta hoja de cheatsheet para ayudarme a mí mismo y a otros a obtener burlas que funcionan rápidamente.

puedes encontrar los ejemplos de código en el repositorio Github del artículo. Usaré Python 3.6, si estás usando 3.2 o inferior necesitarás usar el paquete PyPI mock.,

los ejemplos se escriben usandounittest.TestCase clases para simplificar su ejecución sin dependencias, pero puede escribirlos como funciones usando pytest casi directamente,unittest.mock funcionará bien. Si usted es un usuario pytest aunque le animo a echar un vistazo a la excelente biblioteca pytest-mock.

el punto central del módulo unittest.mock es, por supuesto, la clase Mock., La característica principal de un objeto Mock es que devolverá otra instancia Mock cuando:

  • acceda a uno de sus atributos
  • llamando al objeto en sí

Este es el comportamiento predeterminado, pero se puede sobrescribir de diferentes maneras. Por ejemplo, puede asignar un valor a un atributo en el Mock mediante:

  • asígnelo directamente, como haría con cualquier objeto Python.,
  • utilice el método configure_mock en una instancia.
  • o pase argumentos de palabra clave a la clase Mock en la creación.

Para anular las llamadas a la maqueta tendrá que configurar su return_value propiedad, también disponible como una palabra clave argumento en el Mock inicializador., El Mock siempre devolverá el mismo valor en todas las llamadas, esto, de nuevo, también se puede configurar utilizando el atributo side_effect:

  • Si desea devolver valores diferentes en cada llamada, puede asignar un iterable a side_effect.
  • Si desea generar una excepción al llamar al Mock, simplemente puede asignar el objeto exception a side_effect.,

con todas estas herramientas ahora podemos crear stubs para esencialmente cualquier objeto Python, que funcionará muy bien para las entradas a nuestro sistema. Pero, ¿qué pasa con los resultados?

si está haciendo un programa CLI para descargar todo el Internet, probablemente no desee descargar todo el Internet en cada prueba. En su lugar, sería suficiente afirmar que requests.download_internet (no es un método real) fue llamado apropiadamente. Mock le ofrece métodos prácticos para hacerlo.,

Nota: en nuestro ejemplo assert_called_once error, esto demuestra otro aspecto clave de Mock objetos, registrar todas las interacciones con ellos y entonces usted puede inspeccionar estas interacciones.

por ejemplo, puede usar call_count para recuperar el número de llamadas a Mock, y usar call_args o call_args_list para inspeccionar los argumentos de la última o todas las llamadas respectivamente.,

si esto es inconveniente en cualquier momento, puede usar el método reset_mock para borrar las interacciones grabadas, tenga en cuenta que la configuración no se restablecerá, solo las interacciones.

por último, permítanme presentarles a MagicMock, una subclase de Mock que implementa por defecto de la magia o de dunder métodos. Esto hace que MagicMock sea ideal para simular el comportamiento de la clase, por lo que es la clase predeterminada al aplicar parches.,

Ahora estamos listos para comenzar a burlarse y aislar la unidad bajo prueba. Aquí hay algunas recetas a tener en cuenta:

Patch on import

la forma principal de usar unittest.mock es parchear las importaciones en el módulo bajo prueba usando la función patch.

patch interceptará import las sentencias identificadas por una cadena (más sobre eso más adelante), y devolverá una instancia Mock que puede preconfigurar utilizando las técnicas que discutimos anteriormente.,

Imagina que queremos probar esta función muy sencilla:

tenga en cuenta que estamos importando os y llamadas getcwd para obtener el directorio de trabajo actual. Sin embargo, no queremos llamarlo en nuestras pruebas, ya que no es importante para nuestro código y el valor devuelto puede diferir entre los entornos en los que se ejecuta.

como se mencionó anteriormente, necesitamos suministrar patch con una cadena que represente nuestra importación específica., No queremos suministro simplemente os.getcwd ya que el parche es para todos los módulos, en lugar queremos suministrar el módulo de prueba del import de os , es decir, work.os . Cuando se importa el módulo patchfuncionará su magia y devolverá un Mock en su lugar.,

como alternativa, podemos usar el decorador versión de patch , tenga en cuenta que esta vez la prueba tiene un parámetro adicional: mocked_os que el Mock se inyecta en la prueba.,

patch remitirá los parámetros de palabra clave para el Mock clase, por lo que para configurar un return_value sólo tenemos que añadir como uno:

Burlarse de clases

Es muy común el parche de clases completamente o parcialmente., Helper "db"

  • Worker devuelve la ruta de acceso prevista suministrados por Helper
  • En orden a la prueba de Worker en completo aislamiento necesitamos parche el conjunto Helper clase:

    tenga en cuenta la doble return_value en el ejemplo, simplemente usando MockHelper.get_path.return_value no iba a funcionar ya que en el código que llamamos get_path en una instancia, no de la misma clase.,

    la sintaxis de encadenamiento es ligeramente confusa, pero recuerde MagicMock devuelve otro MagicMock en las llamadas __init__. Aquí estamos configurando cualquier instancia falsa Helper creada por MockHelper para devolver lo que esperamos en las llamadas a get_path que es el único método que nos importa en nuestra prueba.,

    Class speccing

    una consecuencia de la flexibilidad deMockes que una vez que nos hemos burlado de una clase Python no levantaráAttributeErrorya que simplemente devolverá nuevas instancias deMagicMock para básicamente todo. Esto suele ser algo bueno, pero puede conducir a un comportamiento confuso y potencialmente errores. Por ejemplo, la escritura de la siguiente prueba,

    silenciosamente pasar con ninguna advertencia de falta completamente el error en el assrt_called_once .,

    Además, si vamos a cambiar el nombre de Helper.get_path a Helper.get_folder, pero se olvidan de actualizar a la llamada Worker nuestras pruebas va a pasar:

    por Suerte Mock viene con una herramienta para evitar estos errores, speccing.

    En pocas palabras, preconfigura mocks para responder solo a los métodos que realmente existen en la clase spec., Hay varias formas de definir especificaciones, pero la más fácil es simplemente pasar autospec=True a la llamada patch, que configurará el Mock para que se comporte como el objeto que se está burlando, generando excepciones para los atributos faltantes y las firmas incorrectas según sea necesario., Por ejemplo:

    Parcial de la clase de burla

    Si usted está menos inclinado a prueba en completo aislamiento también puede parcialmente parche de una clase utilizando patch.object:

    Aquí patch.object es lo que nos permite configurar una burla versión de get_path sólo, dejando el resto del comportamiento de la virgen., Por supuesto, esto significa que la prueba ya no es lo que consideraría estrictamente una prueba unitaria, pero puede que esté de acuerdo con eso.

    burlarse de funciones integradas y variables de entorno

    en los ejemplos anteriores nos olvidamos de probar un aspecto particular de nuestra clase simple, la llamada print. Si en el contexto de su aplicación esto es importante, como un comando CLI por ejemplo, necesitamos hacer aserciones contra esta llamada.,

    print es, por supuesto, una función incorporada en Python, lo que significa que no necesitamos importarla en nuestro módulo, lo que va en contra de lo que discutimos anteriormente sobre la aplicación de parches en la importación., Todavía imagine que teníamos esta versión un poco más complicada de nuestra función:

    podemos escribir una prueba como la siguiente:

    tenga en cuenta algunas cosas:

    1. podemos simular print sin ningún problema y afirmando que hubo una llamada siguiendo la regla «patch on import». Esto sin embargo fue un cambio introducido en 3.,5, anteriormente necesitaba agregar create=True a la llamada patch a signal unittest.mockpara crear un Mock aunque ninguna importación coincida con el identificador.
    2. estamos usando patch.dictpara inyectar una variable de entorno temporal En os.environ, esto es extensible a cualquier otro diccionario que nos gustaría simular.,
    3. Estamos anidando varias llamadas a patch context manager pero solo usando as en la primera ya que es la que necesitamos usar para llamar a assert_called_once_with .,

    si no le gustan los gestores de contexto de anidamiento, también puede escribir las llamadas patch en el formulario decorador:

    nota sin embargo, el orden de los argumentos a la prueba coincide con el orden de apilamiento de los decoradores, y también quepatch.dict no inyecta un argumento.,este escenario muy común:

    Por supuesto, podría agregar un archivo de accesorio real, pero en casos reales puede que no sea una opción, en su lugar podemos simular la salida del administrador de contexto para ser unStringIO object:

    no hay nada especial aquí excepto el método mágico __enter__, solo tenemos que recordar la mecánica subyacente de los gestores de contexto y un uso inteligente de nuestro fiel MagicMock.,

    burlarse de atributos de clase

    hay muchas maneras de lograr esto, pero algunas son más infalibles que otras. Digamos que has escrito el siguiente código:

    puedes probar el código sin ninguna burla de dos maneras:

    1. Si el código bajo prueba accede al atributo a través deself.ATTRIBUTE, que es el caso en este ejemplo, simplemente puede establecer el atributo directamente en la instancia. Esto es bastante seguro ya que el cambio se limita a esta sola instancia.,
    2. alternativamente, también puede establecer el atributo en la clase importada en la prueba antes de crear una instancia. Sin embargo, esto cambia el atributo de clase que ha importado en su prueba, lo que podría afectar a las siguientes pruebas, por lo que debe recordar restablecerlo.

    el principal inconveniente de este enfoque no simulado es que si en algún momento cambia el nombre del atributo, sus pruebas fallarán y el error no señalará directamente este desajuste de nombres.,

    Para resolver que podemos hacer uso de patch.object en la clase importada que se quejan si la clase no tiene el atributo especificado.

    Aquí están algunas pruebas que usan cada técnica:

    ayudantes de clase de burla

    el siguiente ejemplo es la raíz de muchos problemas con respecto a monkeypatching usando Mock. Por lo general, aparece en bases de código más maduras que comienzan a hacer uso de frameworks y helpers en la definición de clases., Por ejemplo, imagine este hipotético Field class helper:

    Su propósito es envolver y mejorar un atributo en otra clase, un patrón bastante que comúnmente puede ver en Or o bibliotecas. No se preocupe demasiado con los detalles de esto, solo tenga en cuenta que hay un valor type y default pasado.,

    Ahora toma esta otra muestra de la producción código:

    Esta clase hace uso de la etiqueta Field clase mediante la definición de su country atributo como uno cuyo tipo es str y un default como el primer elemento de la etiqueta COUNTRIES constante. La lógica bajo prueba es que dependiendo del país se aplica un descuento.,

    Por lo cual podemos escribir la siguiente prueba:

    Pero eso NO iba a pasar.,

    vamos a caminar a través de la prueba:

    1. Primero se parches los países predeterminados en pricer para ser una lista con una sola entrada GB ,
    2. Esto debe hacer que el CountryPricer.country atributo predeterminado para esa entrada ya que la definición incluye default=COUNTRIES ,
    3. Luego instanciael CountryPricer y pide el precio con descuento para GB !

    Entonces, ¿qué está pasando?,

    la causa raíz de esto radica en el comportamiento de Python durante la importación, mejor descrito en el excelente Python fluido de Luciano Ramalho en el Capítulo 21:

    para las clases, la historia es diferente: en el momento de la importación, el intérprete ejecuta el cuerpo de cada clase, incluso el cuerpo de las clases anidadas en otras clases. La ejecución de un cuerpo de clase significa que se definen los atributos y métodos de la clase, y luego se construye el objeto de clase en sí.,

    aplicando esto a nuestro ejemplo:

    1. la instancia country el atributo Field se construye antes de que se ejecute la prueba en el momento de la importación,
    2. Python lee el cuerpo de la clase, pasando el COUNTRIES que se define en ese punto a la instancia Field,
    3. nuestro código de prueba se ejecuta, pero es demasiado tarde para parchear COUNTRIES y obtener una aserción adecuada.,

    de la descripción anterior puede intentar retrasar la importación del módulo hasta dentro de las pruebas, algo así como:

    esto, sin embargo, todavía no pasará comomock.patch importará el módulo y luego monkeypatch, dando como resultado la misma situación que antes.,

    para solucionar esto necesitamos adoptar el estado de la clase CountryPricer en el momento de la prueba y el parche default en la instancia ya inicializada Field:

    Esta solución no es ideal ya que requiere el conocimiento de los aspectos internos de Field que puede que no tenga o quiera usar si está utilizando una biblioteca externa, pero funciona en este caso obviamente simplificado.,

    Este problema de tiempo de importación es uno de los principales problemas que he encontrado al usar unittest.mock. Es necesario recordar durante su uso que en el tiempo de importación se ejecuta el código en el nivel superior de los módulos, incluidos los cuerpos de clase.

    si la lógica que está probando depende de cualquiera de esta lógica, es posible que deba reconsiderar cómo está utilizando patch en consecuencia.,

    terminando

    esta introducción y cheatsheet deberían hacerte burlarte con unittest.mock, pero te animo a leer la documentación a fondo, hay muchos trucos más interesantes para aprender.

    vale la pena mencionar que hay alternativas a unittest.mock, en particular la biblioteca pretend de Alex Gaynor en combinación con el accesorio pytest‘s monkeypatch. Como señala Alex, al usar estas dos bibliotecas, puede adoptar un enfoque diferente para hacer que la burla sea más estricta pero más predecible., Definitivamente un enfoque que vale la pena explorar, pero fuera del alcance de este artículo.

    unittest.mock es actualmente el estándar para burlarse en Python y lo encontrará en prácticamente todas las bases de código. Tener una sólida comprensión de ella le ayudará a escribir mejores pruebas más rápido.

    Articles

    Deja una respuesta

    Tu dirección de correo electrónico no será publicada. Los campos obligatorios están marcados con *