25. Evitar dependencias externas en pruebas unitarias

25.1 Introducción

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.

25.2 Qué es una dependencia externa

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:

  • Base de datos.
  • API externa.
  • Sistema de archivos.
  • Reloj del sistema.
  • Servicio de correo.
  • Pasarela de pago.
  • Variables de entorno.
  • Configuración global.

25.3 Por qué complican las pruebas unitarias

Las dependencias externas pueden introducir problemas:

  • Lentitud: acceder a red, disco o base de datos tarda más que ejecutar lógica en memoria.
  • Inestabilidad: la dependencia puede no estar disponible.
  • Datos cambiantes: el resultado puede depender de información externa modificable.
  • Diagnóstico difícil: una falla puede estar en la unidad o en la dependencia.
  • Configuración extra: la prueba requiere preparar entorno, credenciales o archivos.
Si una prueba unitaria falla porque una API externa no respondió, no está dando información clara sobre la unidad.

25.4 Ejemplo problemático con base de datos

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.

25.5 Separar consulta y lógica

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.

25.6 Ejemplo problemático con API externa

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.

25.7 Pasar datos externos como entrada

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.

25.8 Ejemplo problemático con archivos

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.

25.9 Separar procesamiento de lectura

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.

25.10 Ejemplo problemático con fecha actual

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.

25.11 Pasar la fecha como dependencia controlada

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.

25.12 Servicios de correo y pagos

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:

  • Qué mensaje debería prepararse.
  • Qué importe debería cobrarse.
  • Si corresponde o no notificar.
  • Qué datos deberían enviarse a una dependencia controlada.

La integración real con esos servicios corresponde a otro nivel de prueba.

25.13 Separar decisión de efecto externo

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.

25.14 Cuándo una dependencia externa sí debe probarse

Evitar dependencias externas en pruebas unitarias no significa ignorarlas. Significa probarlas en el nivel adecuado.

Ejemplos:

  • La lógica de descuento se prueba unitariamente.
  • La consulta real a base de datos se prueba con integración.
  • El flujo completo de compra se prueba con end-to-end.
  • La conexión con un proveedor externo puede probarse con pruebas específicas de contrato o integración.

Cada tipo de prueba responde una pregunta distinta.

25.15 Señales de que una prueba dejó de ser unitaria

Una prueba probablemente ya no es unitaria si:

  • Necesita una base de datos real.
  • Hace llamadas de red.
  • Depende de archivos grandes o rutas del sistema.
  • Requiere credenciales.
  • Tarda mucho por recursos externos.
  • Falla cuando no hay conexión.
  • Necesita levantar gran parte de la aplicación.

Puede seguir siendo una prueba útil, pero tal vez pertenece a integración o end-to-end.

25.16 Introducción a dobles de prueba

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:

  • Un stub puede devolver una respuesta controlada.
  • Un mock puede ayudar a verificar una interacción.
  • Un fake puede simular una implementación simple en memoria.

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.

25.17 Tabla de dependencias y alternativas

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.

25.18 Diseñar para aislar

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:

  • Lógica de negocio.
  • Entrada y salida de datos.
  • Comunicación con servicios externos.
  • Persistencia.
  • Formato y presentación.

Cuando estas responsabilidades están mezcladas, las pruebas unitarias se vuelven difíciles. Separarlas mejora tanto el diseño como la testeabilidad.

25.19 Lista de comprobación

Antes de aceptar una prueba como unitaria, revisa:

  • ¿Depende de base de datos real?
  • ¿Hace llamadas de red?
  • ¿Lee o escribe archivos innecesariamente?
  • ¿Depende del reloj real?
  • ¿Requiere credenciales o configuración externa?
  • ¿Falla por motivos ajenos a la unidad?
  • ¿La lógica podría separarse y probarse con datos controlados?

25.20 Qué debes recordar de este tema

  • Las pruebas unitarias deben minimizar dependencias externas.
  • Bases de datos, APIs, archivos, reloj y servicios externos pueden volver las pruebas lentas y frágiles.
  • Conviene separar lógica de negocio de acceso a recursos externos.
  • Pasar datos controlados mejora repetibilidad.
  • No se ignoran las dependencias externas; se prueban en el nivel adecuado.
  • Los dobles de prueba ayudan a reemplazar dependencias reales cuando es necesario.
  • El diseño testeable separa responsabilidades.

25.21 Conclusión

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.