An Introduction to Burling in Python

How to Run Unit Tests in Python Without Testing Your Patience

La mayoría de las veces, el software que escribimos interactúa directamente con lo que etiquetaríamos como servicios «sucios». En términos sencillos: servicios que son cruciales para nuestra aplicación, pero cuyas interacciones tienen efectos secundarios deseados pero no deseados, es decir, no deseados en el contexto de una ejecución de prueba autónoma.,Facebook Facebook por ejemplo: tal vez estamos escribiendo una aplicación social y queremos probar nuestra nueva función ‘Publicar en Facebook’, pero no queremos publicar en Facebook cada vez que ejecutamos nuestro conjunto de pruebas.

La Biblioteca Python unittestincluye un subpaquete llamado unittest.mock—o si lo declara como una dependencia, simplemente mock —que proporciona medios extremadamente poderosos y útiles para burlarse y eliminar estos efectos secundarios no deseados.,

Nota: mock se incluye recientemente en la biblioteca estándar a partir de Python 3.3; las distribuciones anteriores tendrán que usar la biblioteca falsa descargable a través de PyPI.

llamadas al sistema vs.burlarse de Python

para darle otro ejemplo, y uno que ejecutaremos para el resto del artículo, considere las llamadas al sistema., No es difícil ver que estos son candidatos principales para burlarse: ya sea que esté escribiendo un script para expulsar una unidad de CD, un servidor web que elimina archivos de caché anticuados de /tmp, o un servidor de socket que se une a un puerto TCP, todas estas llamadas tienen efectos secundarios no deseados en el contexto de sus pruebas unitarias.

como desarrollador, le importa más que su biblioteca llame con éxito a la función del sistema para expulsar un CD en lugar de experimentar su bandeja de CD abierta cada vez que se ejecuta una prueba.,

como desarrollador, le importa más que su biblioteca llame con éxito a la función del sistema para expulsar un CD (con los argumentos correctos, etc.).) a diferencia de experimentar realmente su bandeja de CD abierta cada vez que se ejecuta una prueba. (O peor aún, varias veces, ya que varias pruebas hacen referencia al código de expulsión durante una sola prueba unitaria!)

del mismo modo, mantener sus pruebas unitarias eficientes y de rendimiento significa mantener la mayor cantidad de «código lento» fuera de las ejecuciones de prueba automatizadas, es decir, el sistema de archivos y el acceso a la red.,

para nuestro primer ejemplo, refactorizaremos un caso de prueba estándar de Python del formulario original a uno usando mock. Demostraremos cómo escribir un caso de prueba con simks hará que nuestras pruebas sean más inteligentes, rápidas y capaces de revelar más sobre cómo funciona el software.

una función de eliminación simple

todos necesitamos eliminar archivos de nuestro sistema de archivos de vez en cuando, así que escribamos una función en Python que hará que sea un poco más fácil para nuestros scripts hacerlo.,

#!/usr/bin/env python# -*- coding: utf-8 -*-import osdef rm(filename): os.remove(filename)

obviamente, nuestro método rm en este momento no proporciona mucho más que el método subyacente os.remove, pero nuestra base de código mejorará, lo que nos permitirá agregar más funcionalidad aquí.

vamos a escribir un caso de prueba tradicional, es decir, sin burlas:

nuestro caso de prueba es bastante simple, pero cada vez que se ejecuta, se crea un archivo temporal y luego se elimina., Además, no tenemos forma de probar si nuestro método rm pasa correctamente el argumento a la llamada os.remove. Podemos suponer que lo hace basado en la prueba anterior, pero mucho queda por desear.

refactorización con Python se burla

vamos a refactorizar nuestro caso de prueba usando mock:

con estos refactores, hemos cambiado fundamentalmente la forma en que funciona la prueba. Ahora, tenemos un insider, un objeto que podemos usar para verificar la funcionalidad de otro.,

posibles trampas para burlarse de Python

Una de las primeras cosas que debería sobresalir es que estamos usando el decorador de métodomock.patchpara burlarse de un objeto ubicado enmymodule.os, e inyectando ese simulacro en nuestro método de caso de prueba. ¿No tendría más sentido simplemente burlarse de os en sí mismo, en lugar de la referencia a él en mymodule.os?

bueno, Python es algo así como una serpiente furtiva cuando se trata de importar y administrar módulos., En tiempo de ejecución, el módulo mymodule tiene su propio os que se importa a su propio ámbito local en el módulo. Por lo tanto, si simulamos os, no veremos los efectos del simulacro en el módulo mymodule.

el mantra a seguir repitiendo es este:

simula un elemento donde se usa, no de donde viene.,

Si necesita simular el módulo tempfilepara myproject.app.MyElaborateClass, probablemente necesite aplicar el mock a myproject.app.tempfile, ya que cada módulo mantiene sus propias importaciones.

con esa trampa fuera del camino, sigamos burlándonos.

agregar validación a’rm’

el método rm definido anteriormente es bastante simplificado. Nos gustaría que validara que existe una ruta y es un archivo antes de intentar eliminarlo a ciegas., Vamos a refactorizar rm ser un poco más inteligente:

#!/usr/bin/env python# -*- coding: utf-8 -*-import osimport os.pathdef rm(filename): if os.path.isfile(filename): os.remove(filename)

Grandes. Ahora, ajustemos nuestro caso de prueba para mantener la cobertura.

nuestro paradigma de pruebas ha cambiado por completo. Ahora podemos verificar y validar la funcionalidad interna de los métodos sin ningún efecto secundario.

File-Removal as a Service with Mock Patch

hasta ahora, solo hemos estado trabajando con el suministro de simuladores para funciones, pero no para métodos en objetos o casos en los que la simulación es necesaria para enviar parámetros. Cubramos primero los métodos de objetos.,

comenzaremos con un refactor del método rm en una clase de servicio. Realmente no hay una necesidad justificable, per se, de encapsular una función tan simple en un objeto, pero al menos nos ayudará a demostrar conceptos clave en mock. Vamos a refactorizar:

notarás que no ha cambiado mucho en nuestro caso de prueba:

genial, así que ahora sabemos que RemovalService funciona según lo planeado., Vamos a crear otro servicio que lo declare como una dependencia:

dado que ya tenemos cobertura de prueba en el método RemovalService, no vamos a validar la funcionalidad interna del método rmen nuestras pruebas de UploadService. Más bien, simplemente probaremos (sin efectos secundarios, por supuesto) que UploadService llama al método RemovalService.rm, que sabemos «just works™» de nuestro caso de prueba anterior.

Hay dos maneras de hacer esto:

  1. Mock out the RemovalService.rm method itself.,
  2. proporcione una instancia simulada en el constructor de UploadService.

como ambos métodos son a menudo importantes en las pruebas unitarias, revisaremos ambos.

Opción 1: Métodos de instancia de burla

la bibliotecamock tiene un decorador de métodos especial para burlar métodos y propiedades de instancia de objeto, el @mock.patch.object decorador:

¡genial! Hemos validado que UploadService llama con éxito al método rm de nuestra instancia. ¿Notas algo interesante ahí?, El mecanismo de parcheo reemplazó el método rmde todas las instancias RemovalService en nuestro método de prueba. Eso significa que podemos inspeccionar las instancias por sí mismas. Si desea ver más, intente colocar un punto de interrupción en su código de burla para obtener una buena idea de cómo funciona el mecanismo de parches.

Mock Patch Pitfall: Decorator Order

cuando se utilizan varios decoradores en sus métodos de prueba, el orden es importante, y es un poco confuso. Básicamente, al asignar los decoradores a los parámetros del método, trabaje hacia atrás., Considere este ejemplo:

observe cómo nuestros parámetros se corresponden con el orden inverso de los decoradores? Eso es en parte debido a la forma en que funciona Python. Con varios decoradores de métodos, Este es el orden de ejecución en pseudocódigo:

patch_sys(patch_os(patch_os_path(test_something)))

dado que el parche a sys es el parche más externo, se ejecutará en último lugar, lo que lo convierte en el último parámetro en los argumentos del método de prueba real. Tome nota de esto bien y use un depurador cuando ejecute sus pruebas para asegurarse de que los parámetros correctos se están inyectando en el orden correcto.,

Opción 2: crear instancias simuladas

en lugar de burlarse del método de instancia específico, podríamos suministrar una instancia Burlada a UploadService con su constructor. Prefiero la opción 1 anterior, ya que es mucho más precisa, pero hay muchos casos en los que la opción 2 podría ser eficiente o necesaria. Vamos a refactorizar nuestra prueba de nuevo:

en este ejemplo, ni siquiera hemos tenido que parchear ninguna funcionalidad, simplemente creamos una especificación automática para la clase RemovalService, y luego inyectamos esta instancia en nuestra UploadService para validar la funcionalidad.,

el método mock.create_autospec crea una instancia funcionalmente equivalente a la clase proporcionada. Lo que esto significa, en términos prácticos, es que cuando se interactúa con la instancia devuelta, generará excepciones si se usa de manera ilegal. Más específicamente, si se llama a un método con el número incorrecto de argumentos, se producirá una excepción. Esto es extremadamente importante a medida que ocurren los refactores. A medida que una biblioteca cambia, las pruebas se rompen y eso se espera. Sin usar una especificación automática, nuestras pruebas seguirán pasando a pesar de que la implementación subyacente está rota.,

Escollo: El mock.Mock y mock.MagicMock Clases

El mock biblioteca también incluye dos clases importantes sobre los que la mayoría de los internos de la funcionalidad se basa en: mock.Mock y mock.MagicMock. Cuando se le da la opción de usar una instancia mock.Mock, una instancia mock.MagicMock, o una auto-spec, siempre favorezca el uso de una auto-spec, ya que ayuda a mantener sus pruebas sanas para cambios futuros., Esto se debe a que mock.Mocky mock.MagicMock aceptan todas las llamadas a métodos y asignaciones de propiedades independientemente de la API subyacente. Considere el siguiente caso de uso:

class Target(object): def apply(value): return valuedef method(target, value): return target.apply(value)

podemos probar esto con una instancia mock.Mock como esta:

esta lógica parece sana, pero Modifiquemos el método Target.apply para tomar más parámetros:

class Target(object): def apply(value, are_you_sure): if are_you_sure: return value else: return None

vuelva a ejecutar su prueba, y encontrará que todavía pasa. Esto se debe a que no está construido contra su API real., Es por eso que siempre debe usar el método create_autospec y el parámetro autospec con los decoradores @patch y @patch.object.facebook facebook API Call

ejemplo de simulación de Python: burlarse de una llamada a la API de Facebook

para terminar, vamos a escribir un ejemplo de simulación de python más aplicable en el mundo real, uno que mencionamos en la introducción: publicar un mensaje en Facebook. Escribiremos una buena clase wrapper y un caso de prueba correspondiente.,

Aquí está nuestro caso de prueba, que comprueba que publicamos el mensaje sin publicar realmente el mensaje:

como hemos visto hasta ahora, es realmente simple comenzar a escribir pruebas más inteligentes con mock en Python.

conclusión

La biblioteca de Python mock, si es un poco confusa para trabajar, es un cambio de juego para las pruebas unitarias. Hemos demostrado casos de uso comunes para comenzar a usar mock en pruebas unitarias, y esperamos que este artículo ayude a los desarrolladores de Python a superar los obstáculos iniciales y escribir código excelente y probado.,

Share

Deja una respuesta

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