Una prueba determinística produce el mismo resultado cada vez que se ejecuta, siempre que el código no haya cambiado. Esto es fundamental para confiar en una suite automatizada.
En este tema veremos cómo controlar fechas, aleatoriedad y dependencias externas para evitar pruebas que pasan o fallan por factores ajenos al comportamiento que queremos verificar.
Una prueba puede volverse inestable si depende de elementos que cambian entre ejecuciones:
Crea app/promociones.py:
from datetime import date
def es_promocion_de_fin_de_anio():
hoy = date.today()
return hoy.month == 12 and hoy.day == 31
Esta función depende de la fecha real. Una prueba directa podría pasar solo el 31 de diciembre.
Una forma simple de hacer la función testeable es recibir la fecha como parámetro:
def es_promocion_de_fin_de_anio(fecha):
return fecha.month == 12 and fecha.day == 31
Ahora la prueba controla el dato:
from datetime import date
from app.promociones import es_promocion_de_fin_de_anio
def test_es_promocion_de_fin_de_anio_con_31_de_diciembre_devuelve_true():
assert es_promocion_de_fin_de_anio(date(2026, 12, 31)) is True
def test_es_promocion_de_fin_de_anio_con_30_de_diciembre_devuelve_false():
assert es_promocion_de_fin_de_anio(date(2026, 12, 30)) is False
Ejecuta:
python -m pytest tests/test_promociones.py
Estas pruebas no dependen del día real en que se ejecutan.
Otra opción es separar la obtención de la fecha actual:
from datetime import date
def obtener_fecha_actual():
return date.today()
def es_promocion_hoy():
hoy = obtener_fecha_actual()
return hoy.month == 12 and hoy.day == 31
Esto permite reemplazar obtener_fecha_actual durante la prueba.
Podemos usar monkeypatch para controlar la fecha:
from datetime import date
import app.promociones as promociones
def test_es_promocion_hoy_con_fecha_controlada(monkeypatch):
monkeypatch.setattr(
promociones,
"obtener_fecha_actual",
lambda: date(2026, 12, 31),
)
assert promociones.es_promocion_hoy() is True
La prueba reemplaza temporalmente la función que obtiene la fecha real.
Una función que usa aleatoriedad puede producir resultados distintos:
import random
def generar_codigo():
return random.randint(1000, 9999)
Probar un valor exacto sería incorrecto si no controlamos la fuente aleatoria.
Podemos recibir una semilla o un generador:
import random
def generar_codigo(seed=None):
generador = random.Random(seed)
return generador.randint(1000, 9999)
Prueba:
from app.codigos import generar_codigo
def test_generar_codigo_con_misma_semilla_devuelve_mismo_valor():
assert generar_codigo(seed=1234) == generar_codigo(seed=1234)
A veces alcanza con verificar propiedades del resultado:
def test_generar_codigo_devuelve_numero_de_cuatro_digitos():
codigo = generar_codigo(seed=1234)
assert 1000 <= codigo <= 9999
La prueba no depende de un número específico, pero sigue verificando una regla útil.
También podemos reemplazar una función aleatoria:
import random
from app.codigos import generar_codigo
def test_generar_codigo_con_randint_controlado(monkeypatch):
monkeypatch.setattr(random, "randint", lambda minimo, maximo: 1234)
assert generar_codigo() == 1234
Esta técnica debe usarse con cuidado y solo cuando el diseño no permite inyectar la dependencia más fácilmente.
Una dependencia externa puede ser una API, una base de datos, un archivo remoto o cualquier recurso fuera del control directo de la prueba.
En este curso no entraremos todavía en testing de APIs REST, pero sí podemos practicar la idea de reemplazar dependencias externas por funciones controladas.
Crea app/cotizaciones.py:
def obtener_cotizacion_dolar():
raise RuntimeError("Servicio externo no disponible en pruebas")
def convertir_pesos_a_dolares(monto_pesos):
cotizacion = obtener_cotizacion_dolar()
return monto_pesos / cotizacion
La función obtener_cotizacion_dolar representa una dependencia externa.
En la prueba, reemplazamos la dependencia por un valor controlado:
import app.cotizaciones as cotizaciones
def test_convertir_pesos_a_dolares_con_cotizacion_controlada(monkeypatch):
monkeypatch.setattr(cotizaciones, "obtener_cotizacion_dolar", lambda: 1000)
assert cotizaciones.convertir_pesos_a_dolares(5000) == 5
La prueba no llama a ningún servicio real y siempre usa la misma cotización.
Otra opción es pasar la dependencia como parámetro:
def convertir_pesos_a_dolares(monto_pesos, obtener_cotizacion):
cotizacion = obtener_cotizacion()
return monto_pesos / cotizacion
Prueba:
def test_convertir_pesos_a_dolares_con_funcion_inyectada():
def cotizacion_fija():
return 1000
assert convertir_pesos_a_dolares(5000, cotizacion_fija) == 5
Este diseño suele ser más fácil de probar porque no necesita modificar el módulo durante la prueba.
Una prueba no debe depender de que otra se haya ejecutado antes. Cada prueba debe preparar sus propios datos y dependencias.
Incorrecto:
resultado_global = None
def test_prepara_resultado():
global resultado_global
resultado_global = 10
def test_usa_resultado():
assert resultado_global == 10
La segunda prueba depende de la primera. Eso rompe el aislamiento.
Si una función devuelve elementos sin orden garantizado, la prueba no debe asumir un orden arbitrario.
def test_lista_de_roles_sin_importar_orden():
roles = ["admin", "editor", "lector"]
assert set(roles) == {"admin", "editor", "lector"}
Si el orden sí es parte del comportamiento, entonces corresponde probarlo como una regla explícita.
Si una prueba depende de algo lento o externo, conviene revisarla y posiblemente marcarla:
import pytest
@pytest.mark.lento
def test_proceso_costoso_controlado():
assert True
Pero marcar una prueba como lenta no soluciona la fragilidad. Primero debemos intentar controlar sus dependencias.
Crea app/tickets.py con una función generar_ticket. Debe recibir una función que devuelva la fecha y una función que devuelva un número. El ticket debe tener formato YYYYMMDD-NUMERO.
Luego crea pruebas controlando ambas dependencias para verificar que el resultado sea repetible.
Archivo app/tickets.py:
def generar_ticket(obtener_fecha, obtener_numero):
fecha = obtener_fecha()
numero = obtener_numero()
return f"{fecha:%Y%m%d}-{numero}"
Prueba:
from datetime import date
from app.tickets import generar_ticket
def test_generar_ticket_con_dependencias_controladas_devuelve_ticket_esperado():
def fecha_fija():
return date(2026, 5, 10)
def numero_fijo():
return 1234
assert generar_ticket(fecha_fija, numero_fijo) == "20260510-1234"
Ejecuta:
python -m pytest tests/test_tickets.py
Antes de continuar con el próximo tema, verifica lo siguiente:
monkeypatch o inyección de dependencias cuando corresponde.python -m pytest.En este tema vimos cómo escribir pruebas determinísticas controlando fechas, aleatoriedad y dependencias externas. Una suite confiable debe fallar por problemas reales del código, no por factores cambiantes del entorno.
En el próximo tema veremos reintentos, esperas y pruebas frágiles: cuándo usarlos y cuándo evitarlos.