Una prueba unitaria debe verificar una unidad pequeña de código. Para lograrlo, conviene evitar dependencias externas como bases de datos reales, APIs, archivos del sistema, servicios de correo, pasarelas de pago o el reloj real del sistema.
Cuando una prueba depende de elementos externos, puede volverse lenta, frágil y difícil de diagnosticar. Si falla, no siempre sabemos si falló la unidad o la dependencia.
En este tema veremos por qué conviene aislar la lógica y cómo separar el comportamiento que queremos probar de recursos externos.
Una dependencia externa es cualquier recurso o sistema que está fuera de la unidad que queremos probar y que puede afectar el resultado de la prueba.
Ejemplos comunes:
Las dependencias externas pueden introducir problemas:
Supongamos una función que calcula si un usuario recibe descuento, pero consulta directamente una base de datos.
def usuario_tiene_descuento(usuario_id):
usuario = base_de_datos.buscar_usuario(usuario_id)
return usuario.tipo == "vip"
Para probar esta función necesitamos una base de datos configurada, un usuario cargado y conexión disponible. Eso se parece más a una prueba de integración que a una prueba unitaria.
Una mejora es separar la regla de negocio de la consulta externa.
def usuario_tiene_descuento_por_tipo(tipo_usuario):
return tipo_usuario == "vip"
def test_usuario_vip_tiene_descuento():
assert usuario_tiene_descuento_por_tipo("vip") == True
def test_usuario_comun_no_tiene_descuento():
assert usuario_tiene_descuento_por_tipo("comun") == False
Ahora la lógica puede probarse unitariamente sin base de datos. La consulta a la base se puede probar en una prueba de integración.
Una función que llama directamente a una API externa puede fallar por red, credenciales, límites de uso o cambios del servicio.
def convertir_a_dolares(monto):
cotizacion = api_monedas.obtener_cotizacion("USD")
return monto / cotizacion
Si la API no responde, la prueba falla aunque la fórmula sea correcta. Además, la cotización puede cambiar y volver variable el resultado.
Podemos probar la regla de conversión pasando la cotización como dato.
def convertir_a_dolares(monto, cotizacion):
return monto / cotizacion
def test_convertir_a_dolares():
assert convertir_a_dolares(1000, 250) == 4
La prueba verifica la lógica de conversión. La obtención real de cotización queda fuera de esta prueba unitaria.
Leer archivos reales en una prueba unitaria puede generar dependencia de rutas, permisos y contenido externo.
def contar_lineas_de_archivo(ruta):
archivo = open(ruta)
lineas = archivo.readlines()
archivo.close()
return len(lineas)
Esta unidad depende de que el archivo exista. Si el archivo cambia o no está disponible, la prueba falla por una razón externa.
Podemos separar el procesamiento de las líneas del acto de leer el archivo.
def contar_lineas(lineas):
return len(lineas)
def test_contar_lineas():
lineas = ["primera", "segunda", "tercera"]
assert contar_lineas(lineas) == 3
La lógica se prueba con datos en memoria. La lectura real del archivo puede quedar para otro tipo de prueba.
Usar directamente la fecha actual vuelve la prueba dependiente del día de ejecución.
from datetime import date
def cupon_vigente(fecha_vencimiento):
return date.today() <= fecha_vencimiento
Una prueba que hoy pasa podría fallar mañana. Eso rompe la repetibilidad.
def cupon_vigente(fecha_actual, fecha_vencimiento):
return fecha_actual <= fecha_vencimiento
def test_cupon_vigente_antes_del_vencimiento():
fecha_actual = date(2026, 6, 1)
fecha_vencimiento = date(2026, 12, 31)
assert cupon_vigente(fecha_actual, fecha_vencimiento) == True
La prueba controla la fecha actual. No depende del reloj real del sistema.
Servicios como correo y pagos no deberían ejecutarse realmente en una prueba unitaria. Una prueba unitaria no debe enviar correos reales ni cobrar tarjetas reales.
Lo que sí podemos probar unitariamente es la decisión previa:
La integración real con esos servicios corresponde a otro nivel de prueba.
En lugar de enviar un correo dentro de una regla, podemos separar la decisión.
def debe_enviar_confirmacion(estado_pedido):
return estado_pedido == "aprobado"
def test_pedido_aprobado_debe_enviar_confirmacion():
assert debe_enviar_confirmacion("aprobado") == True
def test_pedido_pendiente_no_debe_enviar_confirmacion():
assert debe_enviar_confirmacion("pendiente") == False
La prueba no envía correos. Verifica la regla que decide si corresponde enviar.
Evitar dependencias externas en pruebas unitarias no significa ignorarlas. Significa probarlas en el nivel adecuado.
Ejemplos:
Cada tipo de prueba responde una pregunta distinta.
Una prueba probablemente ya no es unitaria si:
Puede seguir siendo una prueba útil, pero tal vez pertenece a integración o end-to-end.
Cuando una unidad necesita colaborar con una dependencia, podemos usar dobles de prueba: objetos controlados que reemplazan temporalmente a la dependencia real.
Por ahora alcanza con entender la idea general:
En los próximos temas veremos estos conceptos con más detalle inicial, sin profundizar tanto como en el curso específico de Mocking y Stubs.
| Dependencia | Problema en prueba unitaria | Alternativa |
|---|---|---|
| Base de datos | Lentitud y datos cambiantes. | Separar lógica o usar datos en memoria. |
| API externa | Red, disponibilidad y respuestas variables. | Pasar datos controlados o usar stub. |
| Archivo | Rutas, permisos y contenido externo. | Probar procesamiento con datos en memoria. |
| Fecha actual | Resultado cambia con el tiempo. | Pasar fecha como parámetro. |
| Correo o pago | Efectos reales no deseados. | Separar decisión y efecto externo. |
Evitar dependencias externas no es solo una técnica de pruebas; también influye en el diseño. El código fácil de probar suele separar:
Cuando estas responsabilidades están mezcladas, las pruebas unitarias se vuelven difíciles. Separarlas mejora tanto el diseño como la testeabilidad.
Antes de aceptar una prueba como unitaria, revisa:
Evitar dependencias externas en pruebas unitarias permite mantener una suite rápida, repetible y fácil de diagnosticar. Si una prueba falla, queremos que la causa esté cerca de la unidad probada, no en una API, una base de datos o un archivo externo.
La estrategia principal es separar la lógica que queremos verificar de los efectos externos. Cuando la colaboración con una dependencia es necesaria, podemos usar dobles de prueba.
En el próximo tema introduciremos el concepto general de dobles de prueba.