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 patch
funcionará 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 deMock
es que una vez que nos hemos burlado de una clase Python no levantaráAttributeError
ya 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:
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.mock
para crear un Mock
aunque ninguna importación coincida con el identificador.
estamos usando patch.dict
para inyectar una variable de entorno temporal En os.environ
, esto es extensible a cualquier otro diccionario que nos gustaría simular.,
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:
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.,
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:
Primero se parches los países predeterminados en pricer
para ser una lista con una sola entrada GB
,
Esto debe hacer que el CountryPricer.country
atributo predeterminado para esa entrada ya que la definición incluye default=COUNTRIES
,
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:
la instancia country
el atributo Field
se construye antes de que se ejecute la prueba en el momento de la importación,
Python lee el cuerpo de la clase, pasando el COUNTRIES
que se define en ese punto a la instancia Field
,
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
Navegación de entradas