Cobertura de pruebas: qué medir y qué no

Qué es la cobertura de pruebas y qué mide realmente
La cobertura de pruebas es una métrica que indica qué porcentaje del código fuente se ejecuta cuando corren tus pruebas automatizadas. En términos prácticos, una herramienta de cobertura instrumenta el código, observa qué líneas o estructuras se activan durante la ejecución de la suite y produce un número: por ejemplo, un 78% de cobertura de líneas. Ese número describe una relación mecánica entre lo que existe y lo que se recorre.
Es fundamental entender qué mide y qué no mide. La cobertura confirma que una porción de código fue ejecutada, pero no dice nada sobre si el resultado se verificó correctamente. Puedes tener una prueba que invoca una función, recorre todas sus líneas y aumenta la cobertura sin incluir una sola aserción sobre el valor devuelto. En ese caso el número sube, pero la prueba no protege contra regresiones.
Por eso conviene tratar la cobertura como un indicador de exposición, no de validación. Nos dice dónde han pasado las pruebas, no si esas pruebas comprueban el comportamiento esperado. Con esa distinción clara, la cobertura se convierte en una herramienta útil para orientar decisiones, en lugar de una cifra que da falsa tranquilidad.
Tipos de cobertura: líneas, ramas, funciones y condiciones
No toda la cobertura mide lo mismo, y confundir tipos lleva a interpretaciones erróneas. La cobertura de líneas cuenta cuántas líneas ejecutables se recorren; es la más común y la más fácil de inflar, porque una sola prueba lineal puede recorrer muchas líneas sin tocar la lógica condicional.
La cobertura de funciones o métodos indica cuántas funciones fueron llamadas al menos una vez. Es útil para detectar código completamente huérfano de pruebas, pero es tosca: llamar a una función no significa haber probado sus distintos caminos.
La cobertura de ramas es más exigente y más informativa. Mide si cada bifurcación de una condición (el verdadero y el falso de un if, cada caso de un switch) se ha ejercitado. Un método con un condicional puede tener 100% de líneas pero solo 50% de ramas si nunca probaste el caso falso.
La cobertura de condiciones va un paso más allá y examina cada subexpresión booleana dentro de una condición compuesta. En una expresión como 'a && b', comprobar que ambas combinaciones relevantes se evalúan revela huecos que la cobertura de ramas oculta. Para la mayoría de equipos, priorizar la cobertura de ramas ofrece un buen equilibrio entre esfuerzo y señal útil, reservando el análisis de condiciones para módulos críticos con lógica compleja.
Los límites de la cobertura: por qué un 100% no garantiza calidad
Alcanzar el 100% de cobertura es técnicamente posible y a menudo engañoso. La cobertura mide ejecución, no corrección. Una suite puede recorrer todo el código sin verificar apenas nada, o verificando cosas triviales que nunca fallan en la práctica.
Hay tres huecos que ningún porcentaje de cobertura captura. El primero son las aserciones ausentes o débiles: código ejecutado sin comprobaciones significativas. El segundo son los casos límite no considerados: puedes cubrir una función de división al 100% sin probar nunca el divisor cero, porque el camino que recorriste no incluía ese valor. El tercero es el comportamiento no funcional: rendimiento, concurrencia, seguridad o accesibilidad, dimensiones que la cobertura de código ignora por completo.
Además, hay código correcto que resulta caro o imposible de cubrir sin aportar valor real, como manejadores de errores de infraestructura que dependen del entorno. Perseguir ese último tramo de cobertura suele consumir tiempo que rendiría más en pruebas de escenarios reales. Un 80% con aserciones sólidas y casos límite bien elegidos protege mucho más que un 100% de líneas recorridas mecánicamente.
El riesgo de convertir la cobertura en un objetivo (ley de Goodhart)
La ley de Goodhart advierte que cuando una medida se convierte en objetivo, deja de ser una buena medida. La cobertura de pruebas es un caso de manual. En cuanto un equipo impone un umbral obligatorio, por ejemplo 'nada se fusiona por debajo del 90%', aparecen comportamientos que suben el número sin mejorar la calidad.
Estos son los patrones típicos: pruebas sin aserciones que simplemente invocan código, pruebas que comprueban detalles de implementación en lugar de comportamiento, y exclusiones artificiales de archivos difíciles de cubrir para maquillar el promedio. También aparece el desánimo: desarrolladores que escriben pruebas frágiles solo para desbloquear una fusión, generando mantenimiento futuro sin protección real.
El umbral no es intrínsecamente malo, pero un número aislado invita a optimizar el número. La alternativa es tratar la cobertura como una conversación, no como una barrera automática. En lugar de rechazar un cambio por bajar un punto, conviene preguntar qué zonas nuevas quedaron sin probar y si eso importa. Ese matiz distingue un equipo que mide para aprender de uno que mide para cumplir.
Cómo usar la cobertura como guía para detectar zonas sin probar
El mejor uso de la cobertura no es el número global, sino el detalle: los informes que muestran qué líneas y ramas específicas nunca se ejecutan. Ahí la métrica pasa de ser un juicio a ser un mapa. Revisar las zonas rojas de un informe de cobertura sobre un módulo crítico suele revelar caminos de error, ramas de validación o condiciones límite que se olvidaron.
Una técnica práctica es la cobertura diferencial: en lugar de mirar el proyecto entero, observa únicamente la cobertura del código modificado en un cambio concreto. Si añades una función con lógica de negocio y ninguna de sus ramas nuevas está cubierta, esa es una señal clara y accionable, independientemente del promedio global.
Otra aplicación es priorizar por riesgo. No todo el código merece la misma atención: un motor de cálculo de precios o una capa de autenticación justifican perseguir ramas descubiertas, mientras que código generado o de andamiaje aporta poco valor al cubrirse. Usa la cobertura para hacer visibles esas zonas y decidir conscientemente cuáles atacar, en lugar de tratar cada línea sin cubrir como una deuda equivalente.
Métricas complementarias que dan contexto a la cobertura
La cobertura cobra sentido cuando se acompaña de otras señales. La prueba de mutación es la más reveladora: introduce cambios deliberados en el código (mutantes) y comprueba si las pruebas los detectan. Un mutante que sobrevive indica que, aunque esa línea esté cubierta, ninguna aserción distingue el comportamiento correcto del incorrecto. Es la forma más directa de exponer pruebas sin sustancia.
La tasa de detección de defectos, entendida como cuántos errores encuentran las pruebas antes de producción frente a los que se escapan, aporta una visión orientada a resultados. Complementa la cobertura porque mide protección real, no ejecución.
La estabilidad de la suite (proporción de pruebas intermitentes o flaky) también importa: una cobertura alta con pruebas poco fiables genera ruido y erosiona la confianza del equipo. Y métricas de mantenimiento como el tiempo de ejecución o la frecuencia con que las pruebas se rompen ante refactorizaciones indican si la suite es sostenible. Combinar cobertura con mutación y una idea clara de la fiabilidad ofrece un retrato mucho más honesto que cualquier porcentaje aislado.
Buenas prácticas para interpretar y comunicar la cobertura en el equipo
Comunicar bien la cobertura evita que se convierta en fuente de fricción o de números vacíos. La primera práctica es distinguir tendencia de valor absoluto. Que la cobertura descienda de forma sostenida en un módulo crítico dice más que un porcentaje puntual; conviene observar la dirección y el contexto antes de reaccionar.
La segunda es acordar expectativas por tipo de código en lugar de un único umbral global. La lógica de dominio puede exigir mayor rigor que la capa de presentación o los scripts de utilidad. Documentar estas expectativas evita discusiones repetidas en cada revisión.
La tercera es usar la cobertura como insumo de conversación en las revisiones de código, no como puerta automática. Preguntar 'esta rama nueva no está cubierta, ¿es intencional?' invita al criterio en vez de imponerlo. La cuarta es ser transparente sobre las exclusiones: si se decide no cubrir cierto código, conviene que sea una decisión explícita y visible, no un truco silencioso. Aplicadas juntas, estas prácticas convierten la cobertura en una herramienta de aprendizaje compartido, alineada con el objetivo real: entregar software fiable, no maximizar una cifra.
Ejemplo
Tipos de cobertura y qué señal aporta cada uno
| Tipo | Qué mide | Fortaleza | Limitación |
|---|---|---|---|
| Líneas | Líneas ejecutadas | Fácil de obtener y leer | Se infla sin probar lógica |
| Funciones | Funciones invocadas | Detecta código huérfano | No prueba caminos internos |
| Ramas | Bifurcaciones ejercitadas | Expone casos falsos sin probar | Ignora subexpresiones compuestas |
| Condiciones | Subexpresiones booleanas | Revela huecos en lógica compleja | Costosa; solo justifica en zonas críticas |
FAQ
¿Qué porcentaje de cobertura es el ideal? No existe un número universal correcto. Un rango entre el 70% y el 85% con aserciones sólidas suele ser razonable para lógica de negocio, pero el objetivo real es cubrir los caminos que importan, no alcanzar una cifra. Perseguir el 100% a menudo produce pruebas triviales que no aportan protección.
¿Un 100% de cobertura significa que el código no tiene errores? No. La cobertura mide qué código se ejecuta, no si su comportamiento se verifica correctamente. Puedes tener 100% de líneas recorridas sin comprobar casos límite, valores inválidos o dimensiones no funcionales como rendimiento y seguridad. Un porcentaje alto reduce ciertos riesgos, pero no garantiza corrección.
¿Cómo evito que la cobertura se convierta en un objetivo vacío? Trátala como guía y no como barrera automática. Complementa el porcentaje con pruebas de mutación, revisa la cobertura del código modificado en cada cambio y usa los informes detallados para conversar sobre zonas sin probar. Acordar expectativas por tipo de código ayuda más que imponer un único umbral global.
¿Qué diferencia hay entre cobertura de líneas y de ramas? La cobertura de líneas cuenta líneas ejecutadas y es fácil de inflar. La cobertura de ramas verifica que cada bifurcación condicional, incluidos los casos verdadero y falso, se ejercite. Por eso la de ramas ofrece una señal más útil sobre si realmente probaste la lógica del código.
Sigue leyendo
Más información