5. Beneficios y límites de las pruebas unitarias

5.1 Introducción

Las pruebas unitarias son una herramienta muy valiosa, pero no son una solución mágica. Entender sus beneficios y sus límites permite usarlas con criterio y evitar expectativas equivocadas.

Cuando se aplican bien, ayudan a detectar errores temprano, protegen comportamientos importantes y dan confianza al modificar código. Cuando se aplican mal, pueden convertirse en una carga: pruebas frágiles, difíciles de leer o que fallan por motivos poco relevantes.

En este tema veremos qué aportan realmente las pruebas unitarias, qué problemas no resuelven y cómo reconocer cuándo están siendo útiles.

5.2 Beneficio principal: retroalimentación rápida

El beneficio más visible de las pruebas unitarias es la retroalimentación rápida. Una buena suite unitaria puede ejecutarse en pocos segundos y avisar si una regla dejó de funcionar.

Esto cambia la forma de trabajar. En lugar de esperar a probar manualmente toda la aplicación, podemos verificar muchas unidades pequeñas durante el desarrollo.

Cuanto antes aparece una falla, más cerca estamos del cambio que pudo haberla causado.

La rapidez no es un detalle menor. Si una prueba tarda demasiado, se ejecuta menos. Si se ejecuta menos, detecta problemas más tarde.

5.3 Detectar errores cerca de su origen

Una prueba unitaria falla cerca del código que verifica. Si una función de cálculo tiene un error, una prueba unitaria de esa función puede mostrarlo directamente.

def calcular_iva(precio):
    return precio * 0.20


def test_calcular_iva_del_21_por_ciento():
    assert calcular_iva(1000) == 210

En este ejemplo, la prueba falla porque la función calcula 20% en lugar de 21%. El diagnóstico es directo: el problema está en la regla de cálculo.

Si ese mismo error se detectara recién en una pantalla de facturación completa, tendríamos que revisar más capas antes de llegar a la causa.

5.4 Proteger contra regresiones

Una regresión ocurre cuando algo que funcionaba deja de funcionar después de un cambio. Las pruebas unitarias ayudan a evitar que comportamientos importantes se rompan sin aviso.

Por ejemplo, si una regla de descuento ya fue implementada y probada, sus pruebas quedan como protección para futuras modificaciones.

def aplicar_descuento(precio, porcentaje):
    return precio - (precio * porcentaje / 100)


def test_descuento_cero_no_cambia_el_precio():
    assert aplicar_descuento(800, 0) == 800


def test_descuento_del_25_por_ciento():
    assert aplicar_descuento(800, 25) == 600

Si alguien cambia la función y rompe uno de esos casos, la prueba lo detecta. Ese es uno de los usos más prácticos de una suite unitaria.

5.5 Dar confianza al refactorizar

Refactorizar significa mejorar la estructura interna del código sin cambiar su comportamiento observable. Es una actividad necesaria para mantener un proyecto saludable, pero puede ser riesgosa si no tenemos pruebas.

Las pruebas unitarias dan una base de confianza: antes de refactorizar, ejecutamos las pruebas; después del cambio, las ejecutamos otra vez. Si siguen pasando, tenemos evidencia de que los comportamientos cubiertos se conservaron.

Esto no garantiza que todo el sistema esté perfecto, pero reduce mucho el riesgo al modificar código existente.

5.6 Documentación ejecutable

Una prueba unitaria bien nombrada y bien escrita funciona como un ejemplo ejecutable del comportamiento esperado.

def puede_votar(edad):
    return edad >= 16


def test_persona_de_16_anios_puede_votar():
    assert puede_votar(16) == True


def test_persona_de_15_anios_no_puede_votar():
    assert puede_votar(15) == False

Estas pruebas comunican la regla con ejemplos concretos. Una persona que lee el código entiende que el límite está en 16 años. Además, la documentación se ejecuta: si la regla cambia o se rompe, las pruebas lo muestran.

5.7 Mejorar el diseño del código

Las pruebas unitarias suelen empujar hacia un diseño más claro. Para probar una unidad con facilidad, necesitamos que tenga entradas controlables, salidas observables y dependencias razonables.

Si una función es difícil de probar porque hace demasiadas cosas, esa dificultad puede revelar un problema de diseño.

Comparación simple:

Código difícil de probar Código más testeable
Mezcla cálculo, base de datos y envío de correo. Separa cálculo, persistencia y notificación.
Depende directamente de la fecha actual del sistema. Recibe la fecha como parámetro o dependencia controlada.
Usa variables globales modificables. Trabaja con datos explícitos.

5.8 Facilitar el trabajo en equipo

En un equipo, varias personas modifican código al mismo tiempo. Las pruebas unitarias ayudan a detectar si un cambio rompe una regla que otra persona esperaba conservar.

También sirven como comunicación técnica. Una prueba con nombre claro explica una expectativa sin depender de una conversación informal.

Por ejemplo, una prueba llamada test_cliente_inactivo_no_puede_acceder_a_promocion comunica una regla de negocio concreta. Si alguien cambia esa regla, deberá actualizar la prueba o discutir si el comportamiento esperado cambió.

5.9 Reducir pruebas manuales repetitivas

Muchas verificaciones pequeñas no necesitan repetirse manualmente una y otra vez. Si una regla puede comprobarse directamente con una prueba unitaria, conviene automatizarla.

Esto libera tiempo para pruebas manuales de mayor valor, como exploración, revisión de experiencia de usuario o análisis de escenarios nuevos.

Automatizar reglas unitarias no elimina la necesidad de probar la aplicación completa, pero evita usar la interfaz como única forma de verificar cada detalle interno.

5.10 Beneficios resumidos

Beneficio Qué aporta
Rapidez Permite ejecutar pruebas con frecuencia.
Diagnóstico Ayuda a localizar errores cerca de su origen.
Regresión Protege comportamientos ya implementados.
Refactoring Da confianza al mejorar la estructura del código.
Documentación Expresa reglas mediante ejemplos ejecutables.
Diseño Favorece unidades con responsabilidades claras.

5.11 Primer límite: no prueban la aplicación completa

Una prueba unitaria verifica una unidad. Por definición, no comprueba que toda la aplicación funcione de principio a fin.

Una función puede calcular correctamente un total, pero la pantalla podría mostrarlo mal. Una validación puede funcionar, pero el formulario podría no llamarla. Un servicio puede devolver el resultado correcto, pero la base de datos podría guardar otro formato.

Por eso las pruebas unitarias deben complementarse con pruebas de integración, de interfaz, end-to-end y otras según el riesgo del sistema.

5.12 Segundo límite: no detectan problemas de integración

Dos unidades pueden funcionar perfectamente por separado y fallar cuando trabajan juntas. Esto ocurre cuando hay diferencias en formatos, contratos, configuraciones o expectativas.

Ejemplo conceptual:

  • Una función devuelve una fecha como texto en formato DD/MM/AAAA.
  • Otra función espera recibir la fecha en formato AAAA-MM-DD.
  • Cada una puede pasar sus pruebas unitarias, pero la integración falla.

Este tipo de problema requiere pruebas de integración. Las unitarias ayudan, pero no alcanzan para verificar contratos entre componentes.

5.13 Tercer límite: no validan experiencia de usuario

Las pruebas unitarias no pueden decir si una pantalla es clara, si el mensaje visible ayuda al usuario, si el flujo es cómodo o si el diseño es accesible.

Una función puede devolver el mensaje correcto y aun así la interfaz puede mostrarlo en un lugar poco visible, con mal contraste o en un momento inoportuno.

Para esos aspectos se necesitan pruebas de interfaz, revisiones de usabilidad, accesibilidad, pruebas exploratorias o pruebas con usuarios según el contexto.

5.14 Cuarto límite: no garantizan ausencia de errores

Ningún conjunto de pruebas puede demostrar que un programa no tiene errores. Las pruebas unitarias verifican los casos que escribimos, no todos los casos posibles.

Si no pensamos en un caso borde, la suite puede pasar aunque el defecto exista. Por ejemplo, una función de división puede estar probada con valores normales y fallar con divisor cero si nadie escribió ese caso.

def dividir(a, b):
    return a / b


def test_dividir_10_por_2():
    assert dividir(10, 2) == 5

Esta prueba es correcta para el caso elegido, pero no dice nada sobre qué ocurre cuando b vale cero. Las pruebas son tan buenas como los casos que seleccionamos.

5.15 Quinto límite: pueden dar falsa confianza

Una suite con muchas pruebas puede parecer tranquilizadora, pero si esas pruebas verifican poco o están mal diseñadas, pueden dar una falsa sensación de seguridad.

Ejemplo de prueba débil:

def test_calculo():
    calcular_total(1000, 21)

La prueba ejecuta código, pero no verifica el resultado. Puede pasar aunque el cálculo sea incorrecto.

Una prueba útil expresa una expectativa:

def test_calcular_total_con_impuesto_del_21_por_ciento():
    assert calcular_total(1000, 21) == 1210

La cantidad de pruebas importa menos que la calidad de las verificaciones.

5.16 Sexto límite: tienen costo de mantenimiento

Las pruebas unitarias también son código. Deben leerse, ejecutarse, actualizarse y mantenerse. Si están mal diseñadas, pueden dificultar los cambios en lugar de ayudarlos.

Las pruebas se vuelven costosas cuando:

  • Repiten mucha preparación innecesaria.
  • Dependen de detalles internos que cambian con frecuencia.
  • Tienen nombres confusos.
  • Verifican demasiadas cosas a la vez.
  • Fallan de manera intermitente.
  • No queda claro qué comportamiento protegen.

El objetivo no es tener pruebas a cualquier precio, sino pruebas que aporten más valor del costo que generan.

5.17 Cuándo una prueba unitaria aporta valor

Una prueba unitaria suele aportar valor cuando cumple varias de estas condiciones:

  • Verifica una regla importante.
  • Protege un caso que podría romperse en el futuro.
  • Documenta un comportamiento que no es obvio.
  • Ayuda a diagnosticar una falla con rapidez.
  • Se ejecuta rápido.
  • Es fácil de leer.
  • No se rompe por detalles internos irrelevantes.

Una prueba así se convierte en una inversión técnica. Su valor aparece cada vez que alguien modifica el código y necesita confianza.

5.18 Cuándo una prueba unitaria puede no convenir

No todo merece una prueba unitaria aislada. Puede no convenir cuando:

  • El código no tiene lógica propia y solo delega directamente en otra pieza.
  • La prueba repetiría exactamente la implementación sin verificar una regla real.
  • El comportamiento solo tiene sentido al integrarse con otro componente.
  • El costo de preparar el caso es mayor que el valor de la comprobación.
  • La unidad está tan mal separada que primero conviene mejorar el diseño.

Esto no significa ignorar riesgos. Significa elegir el nivel de prueba adecuado y evitar pruebas unitarias que no aportan información útil.

5.19 Beneficios y límites en una misma mirada

Las pruebas unitarias ayudan a... Pero no reemplazan...
Verificar reglas pequeñas. Pruebas de integración entre componentes.
Detectar errores temprano. Pruebas de flujos completos.
Dar confianza al refactorizar. Revisión de requisitos y validación del negocio.
Documentar ejemplos de comportamiento. Documentación funcional cuando sea necesaria.
Reducir verificaciones manuales repetitivas. Pruebas exploratorias y revisión de experiencia de usuario.

5.20 Qué debes recordar de este tema

  • Las pruebas unitarias dan retroalimentación rápida.
  • Ayudan a detectar errores cerca de su origen.
  • Protegen contra regresiones y facilitan el refactoring.
  • Pueden funcionar como documentación ejecutable.
  • No prueban la aplicación completa.
  • No reemplazan pruebas de integración, end-to-end ni revisión de experiencia de usuario.
  • Una suite grande no garantiza calidad si las pruebas no verifican comportamientos relevantes.

5.21 Conclusión

Las pruebas unitarias son más útiles cuando se entienden como una herramienta de retroalimentación rápida, protección de comportamiento y mejora del diseño. Su valor no está en existir, sino en verificar reglas importantes de manera clara y mantenible.

Al mismo tiempo, tienen límites. No aseguran que toda la aplicación funcione, no validan la experiencia de usuario y no reemplazan otros niveles de prueba.

En el próximo tema veremos cuándo conviene escribir pruebas unitarias, para decidir mejor en qué situaciones aportan más valor.