Parche simulado de una función invocada por otra función en Python
Las pruebas unitarias son esenciales cuando escribimos código robusto. Nos ayudan a verificar la lógica de nuestra aplicación e inspeccionar todos los demás aspectos de los requisitos del código; sin embargo, obstáculos como la complejidad y las dependencias externas dificultan la escritura de casos de prueba de calidad.
En este punto, unittest.mock
, una biblioteca simulada de Python, nos ayuda a superar estos obstáculos. Hoy, aprenderemos sobre los objetos simulados, su importancia y varios códigos de ejemplo usando Python patch()
, que reemplazará temporalmente el objetivo con los objetos simulados.
Objetos simulados y su importancia
Los objetos simulados imitan el comportamiento de los objetos reales de manera controlada, por ejemplo, dentro del entorno de prueba, donde realizamos diferentes pruebas para garantizar que el código funcione como se espera. El proceso de usar objetos simulados se llama Mocking; es una poderosa herramienta para mejorar la calidad de nuestras pruebas.
Ahora, el punto es, ¿por qué tenemos que pasar por la burla? ¿Cuáles son las razones fundamentales para usarlo?
Varias causas muestran la importancia de usar burlas en Python. Echémosles un vistazo a continuación:
- Una razón principal es mejorar el comportamiento de nuestro código mediante el uso de objetos ficticios. Por ejemplo, podemos usarlos para probar solicitudes HTTP (una solicitud realizada por un cliente al host designado, que se ubica en un servidor) y asegurarnos de que causen una falla intermitente o no.
- Podemos reemplazar las solicitudes HTTP reales con los objetos simulados, lo que nos permite simular una interrupción del servicio externo y respuestas completamente exitosas de una manera predecible.
- La burla también es útil cuando es difícil probar las declaraciones
if
y los bloquesexcept
. Usando objetos simulados, podemos controlar el flujo de ejecución de nuestro código para llegar a dichas áreas (if
yexcept
) y mejorar la cobertura del código. - Otra razón que aumenta la importancia de usar objetos simulados de Python es comprender correctamente cómo podemos usar sus contrapartes en nuestro código.
Python Patch()
y sus usos
El unittest.mock
, una biblioteca de objetos simulados en Python, tiene un parche()
que reemplaza temporalmente el objetivo con el objeto simulado. Aquí, el objetivo puede ser una clase, un método o una función.
Para usar el parche, necesitamos entender cómo identificar un objetivo y llamar a la función patch()
.
Para reconocer el objetivo, asegúrese de que el objetivo sea importable, luego parchee el objetivo donde se utiliza, no de donde proviene. Podemos llamar a patch()
de tres formas; como decorador de una clase/función, como administrador de contexto o como inicio/detención manual.
El objetivo se reemplaza con el nuevo objeto cuando usamos patch()
como decorador de una clase/función o usamos patch()
en el administrador de contexto dentro de una declaración with
. En ambos escenarios, el parche se deshace cuando existe la instrucción with
o la función.
Vamos a crear un código de inicio y guardarlo en el archivo addition.py
, que importaremos para usar patch()
como decorador, administrador de contexto e inicio/parada manual.
Código de ejemplo de inicio (guardado en el archivo addition.py
):
def read_file(filename):
with open(filename) as file:
lines = file.readlines()
return [float(line.strip()) for line in lines]
def calculate_sum(filename):
numbers = read_file(filename)
return sum(numbers)
filename = "./test.txt"
calculate_sum(filename)
Contenido de test.txt
:
1
2
3
read_file()
toma filename
para leer líneas y convierte cada línea en tipo flotante. Devuelve una lista de estos números convertidos, que guardamos en la variable numbers
dentro de la función calculate_sum()
.
Ahora, calculate_sum()
suma todos los números en la lista de números
y los devuelve como una salida de la siguiente manera:
Producción :
6.0
Usa patch()
como decorador
Profundicemos paso a paso para aprender el uso de patch()
como decorador. El código fuente completo se proporciona al final de todos los pasos.
-
Importación de bibliotecas y módulos.
import unittest from unittest.mock import patch import addition
Primero, importamos la biblioteca
unittest
, luegopatch
desde el módulounittest.mock
. Después de eso, importamosadición
, nuestro código de inicio. -
Decora el método
test_calculate_sum()
.@patch('addition.read_file') def test_calculate_sum(self, mock_read_file): # ....
A continuación, decoramos el método de prueba
test_calculate_sum()
utilizando el decorador@patch
. Aquí, el objetivo es la funciónread_file()
del móduloaddition
.El
test_calculate_sum()
tiene un parámetro adicionalmock_read_file
debido al uso del decorador@patch
. Este parámetro adicional es una instancia deMagicMock
(podemos cambiar el nombre demock_read_file
a cualquier cosa que queramos).Dentro de
test_calculate_sum()
, elpatch()
reemplaza la funciónaddition.read_file()
con el objetomock_read_file
. -
Asigne una lista a
mock_read_file.return_value
.mock_read_file.return_value = [1, 2, 3]
-
Llame a
calculate_sum()
y pruébelo.result = addition.calculate_sum("") self.assertEqual(result, 6.0)
Ahora, podemos llamar a
calculate_sum()
y usarassertEqual()
para probar si la suma es6.0
o no.Aquí, podemos pasar cualquier
nombre de archivo
a la funcióncalculate_sum()
porque se invocará el objetomock_read_file
en lugar de la funciónaddition.read_file()
. -
Aquí está el código fuente completo.
Código de ejemplo (guardado en el archivo
test_sum_patch_decorator.py
)import unittest from unittest.mock import patch import addition class TestSum(unittest.TestCase): @patch("addition.read_file") def test_calculate_sum(self, mock_read_file): mock_read_file.return_value = [1, 2, 3] result = addition.calculate_sum("") self.assertEqual(result, 6.0)
Ejecutar una prueba:
python -m unittest test_sum_patch_decorator -v
Ejecute la prueba usando el comando anterior para obtener el siguiente resultado.
PRODUCCIÓN:
test_calculate_sum (test_sum_patch_decorator.TestSum) ... ok ---------------------------------------------------------------------- Ran 1 test in 0.001s OK
Use patch()
como administrador de contexto
Código de ejemplo (guardado en el archivo test_sum_patch_context_manager.py
):
import unittest
from unittest.mock import patch
import addition
class TestSum(unittest.TestCase):
def test_calculate_sum(self):
with patch("addition.read_file") as mock_read_file:
mock_read_file.return_value = [1, 2, 3]
result = addition.calculate_sum("")
self.assertEqual(result, 6)
Este código es similar al último ejemplo de código donde usamos patch()
como decorador, excepto por algunas diferencias discutidas aquí.
Ahora, no tenemos la línea de código @patch('addition.read_file')
, mientras que test_calculate_sum()
solo toma el parámetro self
(que es un parámetro predeterminado).
El with patch('addition.read_file') como mock_read_file
significa parchear addition.read_file()
utilizando el objeto mock_read_file
en el administrador de contexto.
En palabras simples, podemos decir que el patch()
reemplazará el objeto addition.read_file()
con el objeto mock_read_file
dentro del bloque with
.
Ahora, ejecute la prueba usando el siguiente comando.
python -m unittest test_sum_patch_context_manager -v
Producción :
test_calculate_sum (test_sum_patch_context_manager.TestSum) ... ok
----------------------------------------------------------------------
Ran 1 test in 0.001s
OK
Utilice patch()
para iniciar/detener manualmente
Código de ejemplo (guardado en el archivo test_sum_patch_manually.py
):
import unittest
from unittest.mock import patch
import addition
class TestSum(unittest.TestCase):
def test_calculate_sum(self):
patcher = patch("addition.read_file")
mock_read_file = patcher.start()
mock_read_file.return_value = [1, 2, 3]
result = addition.calculate_sum("")
self.assertEqual(result, 6.0)
patcher.stop()
Esta valla de código hace lo mismo que los dos ejemplos de código anteriores, pero aquí usamos patch()
manualmente. ¿Cómo? Entendámoslo a continuación.
Primero, importamos las bibliotecas requeridas. Dentro de test_calculate_sum()
, llamamos patch()
para iniciar un parche con la función objetivo read_file()
del módulo addition
.
Luego, creamos un objeto simulado para la función read_file()
. Después de eso, asigne la lista de números a mock_read_file.return_value
, llame a calculate_sum()
y pruebe los resultados usando assertEqual()
.
Finalmente, dejamos de aplicar parches llamando al método stop()
del objeto patcher
. Ahora, ejecute el siguiente comando para probar.
python -m unittest test_sum_patch_manually -v
Producción :
test_calculate_sum (test_sum_patch_manually.TestSum) ... ok
----------------------------------------------------------------------
Ran 1 test in 0.001s
OK