4. Dobles de prueba
Llegamos al punto que dejamos pendiente. Probar tu código es fácil cuando solo depende de sí mismo. El problema aparece cuando entra en juego el mundo exterior: una base de datos, una API, el reloj del sistema, un generador de números aleatorios. Cosas lentas, frágiles o impredecibles.
La solución es sustituir esas dependencias por dobles de prueba: piezas falsas que imitan a las reales pero que tú controlas. Igual que un doble de acción sustituye al actor en las escenas peligrosas.
El error de fondo: probar implementaciones en vez de abstracciones
Un patrón muy común, y equivocado, es llamar directamente a la dependencia real dentro del test:
import requests
def test_get_current_weather():
response = requests.get("https://api.open-meteo.com/v1/forecast?latitude=35&longitude=139")
data = response.json()
assert data["current_weather"]["temperature"] > 0
¿Qué pasa si la API no está disponible? ¿Si cambia el formato de la respuesta? ¿Si de verdad la temperatura baja de 0? El test se vuelve frágil y poco fiable, y cualquier cambio en la API te obliga a tocar todos los tests que dependen de ella.
La clave está en depender de una abstracción, no de la implementación concreta. Defines un contrato (una interfaz) que describe qué hace la dependencia, sin atarte a cómo lo hace:
from typing import Protocol
class WeatherGateway(Protocol):
def get_temperature(self, latitude: float, longitude: float) -> float | None:
...
En producción usas la implementación real, que habla con la API. En los tests usas un doble. El código que consume la dependencia no nota la diferencia, porque ambos cumplen el mismo contrato.
{: .advice } Este patrón se llama inyección de dependencias: en lugar de crear la dependencia dentro de la función, se la pasas desde fuera. Así puedes cambiarla en los tests sin tocar el código de producción. Es portable a cualquier lenguaje.
Los tres dobles más usados
- Mock: un objeto falso que registra si fue llamado y con qué argumentos. Lo usas cuando quieres verificar que algo ocurrió, por ejemplo "se guardó el usuario".
- Stub: devuelve respuestas fijas que tú preparas. Lo usas cuando solo te importa el dato que entra, por ejemplo "la API devuelve 30 grados".
- Fake: una implementación falsa pero funcional, como una base de datos en memoria en vez de la real.
En Python tienes unittest.mock en la biblioteca estándar. Con create_autospec fabricas un doble que respeta la firma de la abstracción, así el test se rompe si cambias el contrato:
from unittest.mock import create_autospec
def test_recommends_swimming_when_hot():
# Given: un doble que siempre devuelve calor
gateway = create_autospec(WeatherGateway)
gateway.get_temperature.return_value = 30.0
recommender = ActivityRecommender(gateway)
# When
result = recommender.recommend(latitude=40.4, longitude=-3.7)
# Then
assert result == "Swimming"
Nunca tocamos la API real. El test es rápido, determinista y no depende de la red.
Wrappers: domar lo impredecible
Hay cosas que cambian solas y arruinan un test: la fecha y hora, los números aleatorios, los hashes. Si tu función llama a datetime.now(), mañana el resultado será otro.
La técnica es la misma: envuelve ese valor variable en algo que puedas sustituir. En los tests le das un valor fijo:
class FixedClock:
def __init__(self, fixed_time):
self._fixed_time = fixed_time
def now(self):
return self._fixed_time
Ahora tu código pide la hora al reloj que le inyectas, y en los tests le pasas un FixedClock que siempre devuelve el mismo instante. La prueba deja de depender del calendario.
{: .advice } La idea de fondo se repite en todo el tema: aísla lo externo y lo impredecible detrás de una abstracción, y reemplázalo por un doble en los tests. Cambia la sintaxis según el lenguaje, no la idea.
Actividad 1
Tienes una función send_welcome_email(user, mailer) que envía un correo de bienvenida a través de mailer.
1. Define una abstracción Mailer con un método send(to, subject, body).
2. Escribe un test con un mock que verifique que send se llamó una vez con el destinatario correcto.
3. No envíes ningún correo real.
Actividad 2
Tienes un juego de dados que gana si sale un 6.
1. Aísla la generación del número aleatorio detrás de una abstracción.
2. Con un doble que devuelve siempre 6, prueba el caso ganador.
3. Con un doble que devuelve siempre 2, prueba el caso perdedor.
Este trabajo está bajo una licencia Attribution-NonCommercial-NoDerivatives 4.0 International.
Desafíos de programación atemporales y multiparadigmáticos
Te encuentras ante un librillo de actividades, divididas en 2 niveles de dificultad. Te enfrentarás a los casos más comunes que te puedes encontrar en pruebas técnicas o aprender conceptos elementales de programación.
Comprar el libro¿Me invitas a un café?
Así sigo escribiendo sin publicidad ni muros de pago.
¡Claro, te invito!
Comentarios
Todavía no hay ningún comentario.