Especial Data Lover: Cómo hacer mejores tests en Python usando Mock
por David Jaramillo Saldarriaga en Feb 8, 2022 5:00:52 PM
Para algunos empezar a programar es una de las aventuras más fascinantes y aprender un lenguaje nuevo es como adquirir un superpoder. Algo así como obtener una gema del infinito.
Nos empezamos a familiarizar con el entorno, la sintaxis del lenguaje y sus aplicaciones. Todo es risas y diversión hasta que tenemos nuestro primer proyecto por el que nos van a pagar y leemos el requerimiento: “se debe entregar el proyecto con tests”
Paso 1: ¿Qué son los tests?
Los tests son código que escribimos para comprobar que lo que estamos programando funciona como debería. Existen muchos tipos de test, este post está relacionado a unit test o pruebas unitarias, que se refieren a la comprobación individual de funciones y métodos.
¿Cómo se hacen tests en Python?
Python trae por defecto la librería unittest que incluye todos los métodos y módulos necesarios para hacer las validaciones en nuestro código. A continuación te muestro un ejemplo de un test para una función que suma dos números
Ahora todo sigue siendo risas y diversión, porque nos decimos por dentro “no hay problema, no se ve difícil” y acudimos al ejemplo anterior de la función Suma y creemos que vamos a estar bien.
Pero, por ejemplo: ¿qué pasa si la función que queremos testear hace una consulta a una API? Nuestro test podría fallar si la API no está disponible, si falla la conexión e incluso si la API fue actualizada causando que la respuesta sea distinta.
Nuestras pruebas unitarias no deben estar ligadas a esta clase de situaciones, ya que el objetivo de esta clase de tests es comprobar que un segmento específico del código funcione correctamente.
Paso 2: ¿Qué son los mocks?
Los mocks son objetos “dummy” (o de muestra) con los que podemos simular objetos cuyo funcionamiento es más complejo. No es recomendable utilizar el objeto real como parte de la prueba.
¿Cómo así?
Veamos el siguiente ejemplo:
En el superproyecto con el que vas a empezar a ganar tus primeros dólares como desarrollador de Python, debes hacer una petición (request, en lenguaje developer) a la API de nationalize.io con el fin de retornar la nacionalidad más probable de un nombre.
Este es una muestra de la consulta a la API, para este caso, la nacionalidad más probable para el nombre “David” es US.
Supongamos que ya tenemos un entorno virtual y algunas librerías instaladas: (ver curso de python) y tienes la siguiente función:
Con este código obtenemos un nombre como parámetro y retornamos su nacionalidad. Más adelante lo explicaré con más detalle.
Ahora bien, el objeto requests es un buen ejemplo de cuando no es recomendable incluir un elemento en los test: Su respuesta puede variar por condiciones del entorno como estado de la conexión a internet, respuesta de la API, entre otros. Todo esto puede hacer que el test falle, así la lógica de tu algoritmo esté bien.
En este caso el test se debe centrar en que hace el código con la respuesta correcta y no si requests funciona en ese momento en particular.
Paso 4: ¿Cómo usar un mock?
Por fin, vamos a hacer algo divertido, utilizaremos un mock como simulación de un requests para verificar que todo esté ok con nuestra lógica.
Creamos nuestro archivo de test <code>…tests/test_get_nacionality.py </code>
Incluimos las librerías que vamos a emplear:
- Con MagicMock y patch crearemos nuestro objeto de pruebas.
- TestCase es la clase de la que extenderemos nuestros test.
- get_nacionality _by_name es la función que queremos testear.
En la definición de nuestro test usamos:
- Un decorador: línea que empieza con @ y que sirve para cambiar el comportamiento de una función.
- Patch: es el que nos permite reemplazar el comportamiento de un objeto con un mock. En este caso el objeto get del módulo requests.
- Un parámetro mock: aquí lo llamamos mock_requests. Básicamente, este parámetro es el objeto retornado por patch para simular el objeto que le hayamos indicado
En resumen: Al pasar requests y su método get al decorador patch le indicamos a python: hey cuando veas un get de la librería requests no lo ejecutes, más bien remplaza su comportamiento con el parámetro mock_requests
Paso 5: ¿Cómo sabe el mock qué hacer?
Buena pregunta. Y la respuesta es que no lo sabe. Vamos a decirle qué tiene que hacer.
Veamos nuevamente la función que queremos testear:
En esta función hacemos el llamado a la API, guardamos la respuesta en texto plano, puesto que es la forma más general de hacerlo y procesamos el resultado de acuerdo a nuestras necesidades. En este caso, transformamos el texto en json para después retornar el código de país de la nacionalidad más probable.
Como nuestro mock es sobre el método get, debemos indicarle el atributo text con un valor semejante al de la respuesta de la API.
Aquí es donde usamos MagicMock, que nos permite crear un objeto de prueba en el que podemos definir atributos, valores a retornar, entre otras cualidades, para así evitar crear un objeto real. Nosotros simularemos el objeto de respuesta que entrega requests.get y definiremos su atributo text
Hasta aquí parece que todo se puso confuso, entonces hagamos un recuento:
- Con el decorador patch indicamos el método que no queremos que nuestro test ejecute de verdad sino que simule su comportamiento y lo manejamos con el parámetro mock_requests
- Con MagicMock indicamos las características de la respuesta que nos entrega el método get. En este caso un objeto que cuenta con un atributo text que es el que nos interesa
Y nos falta lo más simple, indicarle a mock_requests cuál es la respuesta que debe retornar, llamar la función get_nacionality_by_name y probar el resultado:
El test completo que acabamos de hacer se ve así:
Con esto ya podremos correr nuestro test independiente del objeto requests real. Recuerda: estamos probando la lógica de la función, no si realiza bien la consulta.
Paso 6: ¿Cómo correr los tests?
Desde el directorio principal del proyecto, puedes correr el archivo de test con:
python -m unittest tests/test_get_nacionality.py
Y si llegas a tener muchos archivos de test y quieres ejecutarlos todos, puedes usar:
python -m unittest discover tests
Gracias a David, ahora ya sabes cómo hacer tests de código con mayor complejidad para que puedas escribir código aún más profesional y ser el mejor data lover.
Agradecemos a Platzi por difundir el contenido de David https://platzi.com/blog/tests-python-usando-mock/, también les dejamos su usuario platzi https://platzi.com/p/davidjaras/
- agosto 2024 (2)
- julio 2024 (3)
- junio 2024 (2)
- mayo 2024 (6)
- abril 2024 (4)
- marzo 2024 (5)
- febrero 2024 (5)
- enero 2024 (4)
- diciembre 2023 (3)
- octubre 2023 (4)
- septiembre 2023 (2)
- agosto 2023 (4)
- julio 2023 (3)
- junio 2023 (4)
- mayo 2023 (1)
- abril 2023 (4)
- marzo 2023 (3)
- diciembre 2022 (1)
- octubre 2022 (2)
- septiembre 2022 (7)
- agosto 2022 (4)
- julio 2022 (8)
- junio 2022 (8)
- mayo 2022 (8)
- abril 2022 (9)
- marzo 2022 (13)
- febrero 2022 (12)
- enero 2022 (12)
- diciembre 2021 (13)
- noviembre 2021 (12)
- octubre 2021 (14)
- septiembre 2021 (14)
- agosto 2021 (11)
- julio 2021 (12)
- junio 2021 (13)
- mayo 2021 (14)
- abril 2021 (13)
- marzo 2021 (11)
- febrero 2021 (8)
- enero 2021 (6)
- diciembre 2020 (6)
- noviembre 2020 (9)
- octubre 2020 (7)
- septiembre 2020 (6)
- agosto 2020 (6)
- julio 2020 (5)
- junio 2020 (5)
- mayo 2020 (17)
- abril 2020 (3)
- marzo 2020 (4)
- febrero 2020 (3)
- enero 2020 (6)
- diciembre 2019 (6)
- noviembre 2019 (10)
- octubre 2019 (5)
- septiembre 2019 (5)
- agosto 2019 (7)
- julio 2019 (3)
- junio 2019 (6)
- mayo 2019 (4)
- abril 2019 (1)
- marzo 2019 (2)
- enero 2019 (1)
- diciembre 2018 (4)
- noviembre 2018 (10)
- octubre 2018 (4)
- septiembre 2018 (4)
- agosto 2018 (3)
- julio 2018 (3)
- junio 2018 (4)
- mayo 2018 (3)
- abril 2018 (2)
- diciembre 2017 (2)
- septiembre 2017 (1)
- junio 2017 (1)
- mayo 2017 (1)
- abril 2017 (1)
- marzo 2017 (1)
- agosto 2016 (1)
- abril 2016 (1)
- marzo 2016 (1)
- febrero 2016 (1)
- noviembre 2015 (1)
- octubre 2015 (2)
- agosto 2015 (1)
- abril 2015 (1)
- marzo 2015 (1)
- febrero 2015 (2)
- enero 2015 (1)
- diciembre 2014 (1)
- noviembre 2014 (1)
- julio 2014 (3)
- junio 2014 (4)
- mayo 2014 (8)
- abril 2014 (9)
- marzo 2014 (7)
- febrero 2014 (6)
- enero 2014 (9)
- diciembre 2013 (9)
- noviembre 2013 (7)
- octubre 2013 (5)
- septiembre 2013 (7)
- agosto 2013 (11)
- julio 2013 (14)
- junio 2013 (12)
- mayo 2013 (6)
- abril 2013 (4)
- marzo 2013 (7)
- febrero 2013 (4)
- diciembre 2012 (1)