100% de cobertura de tests es una señal de alerta, no una medalla
Cien por ciento de cobertura de tests es una señal de alerta, no una medalla. Martin Fowler lo dijo sin rodeos: sospecharía "de cualquier cosa cercana al 100% — olería a alguien escribiendo tests para dejar contentos los números de cobertura, sin pensar en lo que está haciendo". Cuando el número se vuelve la meta, los equipos escriben tests para getters, código de configuración y código generado — esfuerzo gastado exactamente donde no viven los bugs.
Qué mide la cobertura en realidad
La cobertura te dice qué líneas (o ramas) ejecutaron tus tests. No dice nada sobre si verificaron algo útil. Un test que llama a una función y no comprueba nada igual pinta sus líneas de verde. Por eso la cobertura sirve mejor como detector de código sin tests, no como puntaje de calidad: un número bajo es una advertencia real; un número alto prueba muy poco.
- Cobertura de sentencias (líneas) — ¿se ejecutó esta línea? Barata de alcanzar, fácil de inflar.
- Cobertura de ramas — ¿se ejecutaron ambos lados de cada
if? Mucho más difícil de falsear, porque te obliga a pasar por las rutas de error y los casos borde donde se esconden los bugs reales.
La división 80 / casi-100
En vez de una sola meta para toda la suite, divide tu código en dos niveles:
| Nivel | Meta | Métrica | Ejemplos |
|---|---|---|---|
| Todo | ~80% | cobertura de sentencias | handlers, capa de UI, utilidades |
| Rutas críticas | casi 100% | cobertura de ramas | auth, pagos, mutación de datos |
El ~80% global mantiene la suite honesta sin forzar tests sobre código trivial. La meta de casi 100% de ramas va solo donde un caso borde perdido cuesta dinero real o datos reales: decisiones de autenticación y autorización, todo lo que mueve dinero, y cualquier código que muta estado persistente. El rango que el propio Fowler considera razonable para código bien probado es "80s altos o 90s" — alcanzado como consecuencia de testear bien, nunca impuesto como cuota.
Haz cumplir la división en CI, no en code review
Las herramientas modernas soportan umbrales por nivel directamente, así que la política es un archivo de configuración y no la memoria de un revisor. Jest acepta umbrales por glob — piso global, exigencia por ruta:
coverageThreshold: {
global: { statements: 80 },
'./src/auth/': { branches: 100 },
'./src/payments/': { branches: 100 },
}
Jest falla la corrida si cualquier nivel no llega a su propio número; los patrones se evalúan de forma independiente del piso global.
Python (coverage.py): activa la medición de ramas con coverage run --branch (o branch = true bajo [run] en la configuración), y define fail_under en la sección [report] — coverage sale con código 2 si el total queda por debajo. Corre una segunda configuración más estricta, acotada a tus paquetes críticos vía include, para exigir el nivel de casi 100%.
Movidas de usuario avanzado
- Trinquete, no salto. Fija
fail_underen tu número actual y súbelo un punto con cada PR que lo mejore. Un trinquete nunca deja que la cobertura decaiga y nunca exige una maratón de escribir tests. - Lee las líneas sin cubrir, no el porcentaje. La salida más útil de la cobertura es el reporte de líneas rojas en tus módulos críticos — cada rama sin cubrir ahí es un riesgo concreto y con nombre que testeas o aceptas conscientemente.
- Excluye el ruido. Código generado, migraciones y trivialidades tipo
__repr__van en tu lista de exclusión. Cada línea trivial excluida hace más significativo el número restante. - Cuidado con los tests sin aserciones. Si un módulo tiene cobertura alta pero los bugs siguen escapando, revisa que sus tests tengan aserciones reales — la cobertura detecta código que tu suite nunca ejecuta, pero como señaló Brian Marick, no puede detectar una suite que ejecuta todo y no verifica nada.
La conclusión
La cobertura es una herramienta, no un trofeo. Apunta a aproximadamente 80% de cobertura de sentencias en toda la suite, exige casi 100% de cobertura de ramas en auth, pagos y mutación de datos, y conecta ambos niveles a CI para que el estándar se haga cumplir solo. Tus tests se concentran donde un bug de verdad duele — y dejas de inflar un número global en el que nadie debería confiar.
Recursos
- Test Coverage — Martin Fowler
- Branch coverage measurement — documentación de coverage.py
- Referencia de configuración (
fail_under,branch) — documentación de coverage.py coverageThreshold— documentación de configuración de Jest
¿Construyendo una funcionalidad con IA? Yeda AI diseña, audita y entrega sistemas LLM en producción.