Inicio / Artículos / Más allá del porcentaje de cobertura: probando los fallos que realmente enfrentan los usuarios

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.

920 palabras

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.
  • Deje que los errores de producción enriquezcan el conjunto de pruebas. Cuando aparezca un defecto, escriba una prueba que lo reproduzca y falle antes de intentar corregirlo; luego modifique el código hasta que pase la prueba. Con el tiempo, el conjunto de pruebas acumulará los casos límite que realmente se presentan en su sistema, y no aquellos que solo imaginó alguien.
  • 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.
  • Transiciones de estado objetivo, manejo de errores e integración entre tus propios módulos.
  • Convierte cada error detectado primero en una prueba fallida y luego corrígelo.
  • Registra los modos de fallo contra los que protege tu conjunto de pruebas, y considera la cifra de cobertura como una indicación complementaria.
  • Lecturas relacionadas