Más allá del porcentaje de cobertura: probando los fallos que realmente enfrentan los usuarios
Por qué un alto porcentaje de cobertura de código puede ocultar modos de fallo no probados, los tres tipos de pruebas superficiales que fomenta y cómo escribir pruebas que protejan el comportamiento real.
Una solicitud de integración con todas las pruebas como exitosas y un informe de cobertura superior al 98% parece segura. Pero entonces una API devuelve null cuando la interfaz esperaba un objeto, una solicitud se resuelve en el orden incorrecto debido a una conexión móvil lenta, o alguien hace doble clic en el botón de envío, y la interfaz frontal se convierte en una pantalla en blanco. La pregunta posterior siempre es la misma: ¿cómo se produjo este fallo si el archivo estaba cubierto? Este artículo explica qué mide realmente la cobertura, los patrones de pruebas que la inflan sin añadir protección, y cómo orientar el conjunto de pruebas hacia los fallos que realmente importan.
Qué mide la cobertura y qué no mide
Una herramienta de cobertura registra qué líneas, ramas y funciones se ejecutaron mientras se realizaban las pruebas. Eso es todo. No sabe si alguna afirmación verificó el resultado, si las entradas se asemejaban al tráfico real o si el código funciona correctamente cuando una dependencia falla. Ejecutarse y verificarse son dos propiedades diferentes, y la cobertura solo informa sobre la primera.
Una analogía útil es una inspección de edificios en la que alguien recorre cada habitación con una linterna. Se visitó cada habitación, pero nadie probó el techo durante una tormenta. Una alta cobertura indica que las pruebas accedieron al código, pero no que lo pusieran a prueba realmente.
Tres patrones que inflan la cobertura sin añadir seguridad
Cuando el porcentaje se convierte en el objetivo, la gente optimiza para alcanzarlo. El resultado son pruebas que satisfacen a la herramienta con el mínimo esfuerzo. Tres patrones aparecen una y otra vez.
La ilusión del camino feliz
Imagínese un ayudante que analiza la entrada del usuario y actualiza el estado. Su prueba funciona con una cadena limpia y bien formada, verifica la salida esperada y logra una cobertura total de ramas. Lo que nunca prueba es una cadena vacía, caracteres inusuales, un argumento undefined, una respuesta lenta o JSON mal formado. Cada línea se ejecutó, pero no se probaron las entradas que causan incidentes en producción.
El componente sobre-mockeado
Aquí, cada llamada a la API, proveedor de contexto e elemento hijo anidado se reemplaza por un stub. El conjunto de pruebas finaliza en pocos milisegundos y abarca todas las ramas de renderizado. Sin embargo, en producción, el endpoint real devuelve una estructura ligeramente diferente a la asumida por la simulación, o una biblioteca modifica cómo emite eventos tras una actualización. Las pruebas siguen pasando porque solo se basan en las propias suposiciones, y la primera integración real tiene lugar en el navegador del usuario.
Pruebas sin afirmaciones significativas
La forma más débil se observa bajo requisitos estrictos, como un umbral del 90% obligatorio en todo el equipo. Las pruebas llaman a funciones únicamente para registrar ejecuciones y realizan pocas o ninguna afirmación. El informe muestra color verde aunque no existe ninguna verificación entre el código y futuros cambios en la lógica.
Cómo probar el comportamiento en lugar de las líneas
Las pruebas que detectan errores antes de que lo hagan los usuarios se centran en lo que hace el sistema, no en qué líneas afecta.
- Prueben los estados y transiciones, no las funciones individuales. A los usuarios no les importa que se haya ejecutado una función auxiliar. Lo que les interesa es lo que sucede cuando la red falla a mitad del envío de un formulario, o cuando intentan salir de la página mientras aún se está cargando un archivo. Cubran los indicadores de carga, los estados de error, las reintentos y los límites de error.
- Preferan la integración cuando sea práctico. Simular verdaderos límites externos, como una pasarela de pago de terceros, es razonable. Simular sus propias funciones auxiliares internas o su propia capa de datos suele ocultar los errores que existen entre ellas. Dejen que los componentes y módulos funcionen juntos en la medida en que lo permitan los costos.
Una técnica relacionada y bien establecida es la prueba por mutación, que altera intencionadamente el código (cambiando una condición, eliminando una línea) y verifica si alguna prueba falla. Las mutaciones que sobreviven señalan directamente el código que se ejecuta pero no se verifica, lo cual es exactamente lo que la cobertura de pruebas no puede mostrar. Para detalles a nivel de componentes, consulte estos antipatrones de pruebas en React que generan una confianza falsa.
Usar la cobertura como señal, no como objetivo
La cobertura no es inútil. Un porcentaje bajo en un módulo crítico constituye una advertencia real, y un informe puede revelar rutas de código que nadie prueba en absoluto. El problema surge cuando el porcentaje se convierte en el objetivo, ya que eso premia la cantidad sobre el rigor.
Un conjunto de pruebas eficiente con una cobertura de alrededor del 65 %, centrado en flujos de trabajo riesgosos, reglas empresariales complejas y recuperación ante fallos, evitará muchos más incidentes que un conjunto frágil con un 95 % de cobertura, basado únicamente en rutas sin problemas y pruebas simuladas. Antes de añadir una prueba, una pregunta mejor que “¿qué líneas no están cubiertas?” es “¿qué fallo realista podría detectar esta prueba?”
Puntos clave
- La cobertura indica qué código se ejecutó, no si su comportamiento fue verificado.
- Las entradas basadas en rutas sin problemas, las pruebas simuladas intensivas y las pruebas sin afirmaciones elevan el porcentaje sin reducir el riesgo.
Lecturas relacionadas
- Más allá del tamaño del bundle: encontrar lo que realmente hace lenta a tu aplicación web — Por qué reducir kilobytes rara vez soluciona un problema de lentitud, y cómo rastrear el tiempo real de espera en servidores, procesos en cascada, sistemas de carga dinámica, scripts y imágenes de terceros.
- Más allá del P95: Medir la latencia que realmente experimentan tus usuarios — Por qué un P95 saludable puede coexistir con un producto lento, cómo el tiempo de espera en colas y la dispersión de solicitudes se ocultan en los paneles de control, y cómo el cronometraje por paso pone fin a las acusaciones relacionadas con la latencia.