Burlarse en Python: unittest.mock, parche y los errores que hacen que las pruebas mientan
Los simulacros te permiten probar código sin una red, base de datos o reloj real. Usados descuidadamente, también dejan pasar el código roto. A continuación se explica cómo utilizar bien unittest.mock con cada ejemplo ejecutado.
- Una simulacro sustituye a un objeto real, como un cliente API, por lo que las pruebas se ejecutan rápidamente y sin conexión.
- Utilice
return_valuepara una respuesta fija yside_effectpara errores o una secuencia. - Parchea el nombre donde se usa, no donde está definido. Este es el error más común.
- Utilice
specoautospecpara que los errores tipográficos y los argumentos incorrectos fallen en lugar de pasar en silencio.
Su función llama a una API meteorológica. Su prueba no debe depender de Internet, una clave API o el clima de mañana. La función incorporada de Python unittest.mock te permite reemplazar esa llamada con un sustituto que devuelve exactamente lo que elijas.
Escribimos un pequeño módulo weather y nueve pruebas, todas ejecutadas con la prueba unitaria de Python 3.13. Los nueve aprobaron y los resultados impresos a continuación provienen de esa ejecución.
¿Por qué burlarse?
- Velocidad: sin llamadas de red ni configuración de base de datos.
- Confiabilidad: las pruebas no fallan porque un servicio de terceros no funciona.
- Control: puedes simular tiempos de espera, errores y datos impares bajo demanda.
- Aislamiento: usted prueba su lógica, no el servidor de otra persona.
Los simulacros pertenecen principalmente a pruebas unitarias. Las pruebas de integración aún deberían hablar de dependencias reales o realistas.
Conceptos básicos simulados
from unittest.mock import Mock
api = Mock()
api.get_price.return_value = 499
print(api.get_price("pen")) # 499
api.get_price.assert_called_once_with("pen")
print(api.get_price.call_args_list) # [call('pen')]
print(api.anything_at_all()) # <Mock name='mock.anything_at_all()' ...>
Un Mock registra cada llamada, para que puedas comprobar cómo se utilizó. Sin embargo, observe la última línea. Un simple Mock inventa cualquier atributo que le pidas. Esa flexibilidad es conveniente y también peligrosa, como veremos.
MagicMock es la misma idea pero también admite métodos “mágicos” como __len__ y __iter__. patch() usa MagicMock de forma predeterminada.
side_effect: errores y secuencias
side_effect controla lo que sucede en cada llamada. Configúrelo como una excepción para simular una falla:
api.fetch.side_effect = ConnectionError("down")
api.fetch() # raises ConnectionError
Configúrelo en una lista para devolver resultados diferentes en llamadas sucesivas. Esto es perfecto para probar la lógica de reintento:
def fetch_with_retry(api, attempts=3):
for _ in range(attempts):
try:
return api.fetch()
except ConnectionError:
continue
raise ConnectionError("all attempts failed")
api = Mock()
api.fetch.side_effect = [ConnectionError("busy"), {"ok": True}]
print(fetch_with_retry(api)) # {'ok': True}
print(api.fetch.call_count) # 2
La primera llamada falló, la segunda tuvo éxito y la prueba demostró que la función se reintentó exactamente una vez. Simular esto con un servicio real sería casi imposible.
patch(): reemplaza algo temporalmente
A menudo, la dependencia no se pasa. Se importa dentro del módulo. patch() lo cambia mientras dura una prueba y lo restaura después:
# weather.py
from http_client import get as requests_get
def report(city):
data = requests_get(f"https://api.example.com/weather?city={city}")
return f"{city}: {data['temp_c']}°C"
from unittest.mock import patch
with patch("weather.requests_get") as fake_get:
fake_get.return_value = {"temp_c": 31}
print(weather.report("Pune")) # Pune: 31°C
fake_get.assert_called_once_with(
"https://api.example.com/weather?city=Pune")
Puedes usar patch como administrador de contexto como este, o como decorador, que pasa el simulacro como argumento.
Parche donde se usa, no donde está definido
Este es el error que casi todo el mundo comete alguna vez. La función real get se encuentra en http_client, por lo que parece natural parchearla allí:
with patch("http_client.get") as fake_get:
fake_get.return_value = {"temp_c": 99}
weather.report("Pune")
# ConnectionError: no network in tests: https://api.example.com/weather?city=Pune
La función real se ejecutó. Cuando weather.py hizo from http_client import get as requests_get, creó su propio nombre apuntando a la función original. El parche http_client.get cambió un nombre diferente, por lo que weather nunca vio el simulacro. Nuestro http_client.get sube a propósito, por lo que el error fue sonoro. En un proyecto real, podría llamar silenciosamente a una API en vivo.
Regla: parchea el nombre en el módulo bajo prueba, escrito como "module_under_test.name". Si el módulo hizo import http_client y llama a http_client.get(), entonces parchear "http_client.get" es correcto, porque la búsqueda ocurre en el momento de la llamada.
especificación y especificación automática: hacer que las burlas sean estrictas
Un Mock simple acepta cualquier cosa, incluidos los errores tipográficos. Con una especificación, solo permite atributos que tiene la clase real:
api = Mock(spec=weather.WeatherClient)
api.get_temprature("Pune")
# AttributeError: Mock object has no attribute 'get_temprature'
loose = Mock()
loose.get_temprature("Pune") # returns a Mock, no error at all
El simulacro suelto aceptó felizmente el método mal escrito, por lo que una prueba con él podría pasar incluso aunque el código real fallara. autospec va más allá y verifica las firmas de los métodos:
with patch("weather.WeatherClient", autospec=True) as Fake:
client = Fake.return_value
client.get_temperature("Pune", "extra", "args")
# TypeError: too many positional arguments
Prefiere autospec=True o create_autospec() cuando te burles de tus propias clases. Mantiene las burlas honestas a medida que cambia el código real.
Fechas y horas burlonas
El código que depende de la fecha de hoy es difícil de probar. Envuelva el reloj en una pequeña función y parche que:
def today():
return datetime.date.today()
def is_weekend():
return today().weekday() >= 5
@patch("weather.today")
def test_date(self, fake_today):
fake_today.return_value = datetime.date(2026, 11, 7)
print(weather.is_weekend()) # True (a Saturday)
No es fácil parchear datetime.date.today directamente, porque los tipos integrados no permiten establecer sus atributos. Bibliotecas como Freezergun y Time-machine manejan esto de manera más completa.
Afirmaciones sobre simulacros
| Método | cheques |
|---|---|
assert_called() |
Llamado al menos una vez |
assert_called_once() |
Llamado exactamente una vez |
assert_called_with(...) |
La última convocatoria utilizó estos argumentos. |
assert_called_once_with(...) |
Exactamente una llamada, con estos argumentos. |
assert_any_call(...) |
Cualquier llamada utilizó estos argumentos. |
assert_not_called() |
Nunca llamé |
Cuando una afirmación falla, el mensaje es claro. Llamamos a assert_called_once() en un método que nunca se ejecutó:
AssertionError: Expected 'save' to have been called once. Called 0 times.
Las versiones anteriores de Python permiten que los métodos de aserción mal escritos, como assret_called_once, pasen silenciosamente. Las versiones modernas generan un error para los nombres que comienzan con assert o errores ortográficos comunes, pero una especificación sigue siendo la protección más fuerte.
pytest y burlas
Todo lo anterior también funciona en pytest. Muchos usuarios de pytest también usan el dispositivo monkeypatch incorporado para reemplazos simples, o el complemento pytest-mock, que proporciona un dispositivo mocker que deshace parches automáticamente. La regla del “parche donde se utiliza” se aplica a todos ellos. Consulte nuestro tutorial de pytest para conocer los accesorios y la configuración.
Cuando no burlarse
- No te burles de lo que estás probando. Si el simulacro hace todo el trabajo, la prueba no prueba nada.
- Evite burlarse de objetos de valor simple. Utilice objetos reales.
- No te burles de todo. Las pruebas llenas de simulaciones a menudo fallan cada vez que se refactoriza el código, incluso cuando el comportamiento es correcto.
- Mantenga algunas pruebas de integración reales. Los simulacros codifican sus suposiciones sobre un servicio, y esas suposiciones pueden ser erróneas.
Los simulacros también aumentan la cobertura de código fácilmente, ya que las rutas simuladas se ejecutan sin ningún comportamiento real detrás de ellos.
Errores comunes de burla
- Parcheando el nombre incorrecto. Cubierto anteriormente, y sigue siendo la causa número uno de “mi simulacro es ignorado”.
- Olvidar establecer valor_retorno. El código bajo prueba recibe un objeto simulado en lugar de datos. Puede pasar directamente a través de su función y realizar una prueba por accidente, por ejemplo, cuando solo verifica que un resultado sea verdadero.
- Afirmar en el simulacro en lugar del resultado. Verificar que se llamó a
save()es más débil que verificar lo que se guardó. Utiliceassert_called_once_with(...)con argumentos reales, o mejor, afirme el resultado de la función. - Parches con fugas. Llamar a
patcher.start()sinstop()deja la falsificación en su lugar para pruebas posteriores. Utilice el administrador de contexto, el decorador oaddCleanup(patcher.stop). - Decoradores apilados en el orden incorrecto. Con varios
@patchdecoradores, el de abajo se pasa como primer argumento. Mezclarlos silenciosamente configura el simulacro equivocado. - Burlarse de una biblioteca que no es de su propiedad en muchas pruebas. Envuélvelo en una pequeña clase propia y fúndete de eso. Luego, sus pruebas describen sus necesidades, no la API de la biblioteca.
Simulacros, talones y falsificaciones
Estas palabras a menudo se usan de manera vaga, pero las diferencias ayudan cuando se habla de pruebas. Un stub simplemente devuelve respuestas predeterminadas. Una simulacro también registra llamadas para que puedas verificar cómo se usó. Una falsa es una implementación funcional y simplificada, como un repositorio en memoria en lugar de una base de datos. Python Mock puede actuar como un talón o un simulacro. Las falsificaciones son clases ordinarias que usted mismo escribe y, a menudo, conducen a pruebas que sobreviven mejor a la refactorización, porque verifican el comportamiento en lugar de las llamadas exactas.
Una buena regla general es burlarse de los límites de su sistema: la red, el reloj, los números aleatorios, el sistema de archivos y los servicios pagos de terceros. Dentro de esos bordes, prefiera objetos reales. Las pruebas creadas de esta manera son rápidas y siguen ejerciendo el código que usted realmente escribió.
Conclusión
La burla es una herramienta precisa: reemplaza dependencias lentas o impredecibles, controla lo que devuelven y comprueba cómo las usó tu código. Dos hábitos previenen la mayoría de los problemas. Parchee el nombre en el módulo bajo prueba y use autospec para que sus simulacros rechacen las llamadas que el objeto real rechazaría.
Preguntas frecuentes
¿Qué es burlarse en Python?
La burla reemplaza un objeto real, como un cliente API o una base de datos, con uno falso que usted controla durante una prueba. Python incluye esto en el módulo unittest.mock.
¿Cuál es la diferencia entre Mock y MagicMock?
MagicMock es un simulacro que también admite métodos mágicos como __len__, __iter__ y __enter__. patch() crea un MagicMock de forma predeterminada.
¿Por qué mi parche no funciona?
Generalmente porque parchó donde se define el objeto en lugar de donde se usa. Parchee el nombre dentro del módulo bajo prueba.
¿Cuál es la diferencia entre valor_retorno y efecto_lateral?
return_value establece un resultado fijo. side_effect puede generar una excepción, devolver valores de una lista en orden o llamar a una función.
¿Qué hace la especificación automática?
Crea una simulación a partir del objeto real, por lo que los atributos faltantes y los argumentos incorrectos generan errores, tal como lo harían con el código real.
sigue aprendiendo
¿Necesitas ayuda con pruebas, WordPress o SEO?
Ayudamos a los equipos a enviar mejor software y hacer crecer sus sitios. Cuéntenos en qué está trabajando y le sugeriremos un siguiente paso claro.
Más guías sobre testing, IA y desarrollo moderno
¿Te gustó este artículo? Elige un tema y salta directo a nuestros tutoriales más recientes.
Explicación de las alucinaciones de la IA: por qué los modelos de lenguaje inventan cosas y cómo reducirlas
Se explicaron las alucinaciones de la IA: qué son, por qué los modelos de lenguaje las producen, tipos comunes, cómo reducirlas con conexión a tierr…
Leer artículo →DevOpsExplicación de las variables de entorno: archivos .env, secretos y configuración bien hechos
Variables de entorno explicadas con ejemplos de Python que ejecutamos: leerlas y configurarlas, por qué cada valor es una cadena, archivos .env, her…
Leer artículo →Desarrollo de softwareExplicación de la limitación de velocidad de API: algoritmos, encabezados y manejo de errores 429
La limitación de la tasa de API se explica con una simulación que ejecutamos: depósito de tokens, ventana fija, ventana deslizante, 429 respuestas, …
Leer artículo →Automatización de pruebasBurlarse en Python: unittest.mock, patch y los errores que hacen que las pruebas mientan
Burlarse en Python con unittest.mock, probado: Mock y return_value, side_effect, parches donde se usa un nombre, especificación y autospec, fechas d…
Leer artículo →Pruebas de softwareExplicación de la cobertura del código: cobertura de línea versus sucursal y lo que realmente significa 100%
Cobertura del código explicada con una ejecución real de cobertura.py: cobertura de línea, rama y condición, lectura de un informe, por qué una cobe…
Leer artículo →Bases de datos y SQLDELETE vs TRUNCATE vs DROP en SQL: diferencias, reversión y cuándo usar cada uno
DELETE vs TRUNCATE vs DROP explicado con SQL probado: qué elimina cada uno, reversión, activadores, restablecimiento de identidad, velocidad y en qu…
Leer artículo →Bases de datos y SQLVistas SQL explicadas: cree, use y evite los errores, con ejemplos
Vistas SQL explicadas con consultas que ejecutamos: creación de una vista, resultados en vivo, ocultación de columnas, rendimiento, vistas actualiza…
Leer artículo →Desarrollo de softwareInterfaz TypeScript versus tipo: diferencias reales, probadas con el compilador
Interfaz TypeScript versus tipo, verificado con el compilador real: qué pueden hacer ambos, qué puede hacer solo uno, fusión de declaraciones, exten…
Leer artículo →Desarrollo de softwarePython *args y **kwargs explicados: funciones flexibles con ejemplos claros
Python *args y **kwargs explicados con el código que ejecutamos: empaquetado y desempaquetado, orden de parámetros, argumentos solo de palabras clav…
Leer artículo →Desarrollo de softwareFunciones Lambda de Python: sintaxis, usos reales y cuándo no utilizarlas
Funciones lambda de Python explicadas con el código que ejecutamos: sintaxis, clasificación con clave, mapa y filtro, cierres, trampa de enlace tard…
Leer artículo →Inteligencia artificial¿Qué son los agentes de IA? Cómo funcionan, con un bucle de agente simple que puedes ejecutar
¿Qué son los agentes de IA? Una guía sencilla sobre el bucle del agente, las herramientas, la memoria y las barreras de seguridad, con un pequeño bu…
Leer artículo →DevOpsMonitoreo versus observabilidad: la verdadera diferencia, con registros, métricas y seguimientos
Explicación del monitoreo versus observabilidad: fallas conocidas y desconocidas, registros, métricas y seguimientos, un recorrido por incidentes de…
Leer artículo →DevOpsDocker Volumes versus Bind Mounts: persistir los datos del contenedor de la manera correcta
Se explican los volúmenes de Docker frente a los montajes de enlace: por qué desaparecen los datos del contenedor, volúmenes con nombre, montajes de…
Leer artículo →Automatización de pruebasTDD vs BDD: diferencias clave, una función creada en ambos sentidos y cuándo usar cada una
TDD vs BDD explicado con una característica de descuento construida en ambos sentidos: refactor rojo-verde con el código que ejecutamos, escenarios …
Leer artículo →Desarrollo de softwareGit merge vs rebase: diferencias, ejemplos y cuándo usar cada uno
Git merge vs rebase explicado con salida real del log, una tabla comparativa, la regla de oro del rebase y un flujo de trabajo seguro para equipos.
Leer artículo →Desarrollo de softwareList comprehension en Python: sintaxis, ejemplos y cuándo evitarla
List comprehension en Python explicada: sintaxis, filtros, if-else, bucles anidados, tiempos medidos y cuándo es mejor un bucle for normal.
Leer artículo →