Descripción General
La cobertura de código mide qué líneas, branches y condiciones fueron ejecutadas durante los tests. Es un proxy útil para código no testeado, pero no una medida de calidad de test — 100% de cobertura sin assertions es meaningless. El patron a continuacion demuestra cómo recolectar, reportar y configurar thresholds de cobertura significativos sin crear incentivos perversos.
Cuándo Usar
-
For alternatives, see Measure Test Coverage with pytest-cov.
-
Necesitas visibilidad sobre qué rutas de código carecen de ejecución de test
-
Los pipelines de CI necesitan una puerta para prevenir código no testeado de ser mergeado
-
Estás refactorizando código legacy y quieres asegurar que los cambios nuevos están testeados
-
Los equipos necesitan una métrica compartida para rastrear progreso de testing con el tiempo
-
Quieres identificar código muerto que nunca se ejecuta en producción o tests
Cuándo NO Usar
- La cobertura se trata como un objetivo (ej. “debe ser 90%”) en lugar de una guía — esto lleva a tests sin assertions
- El codebase es un prototipo o spike que será descartado — la cobertura no añade valor
- Estás testeando código generado, boilerplate de framework o archivos de configuración
- El equipo optimiza porcentaje de cobertura sobre encontrar bugs reales
Implementación Paso a Paso
Python (pytest-cov)
# Instalar
pip install pytest-cov
# Ejecutar con reporte de terminal
pytest --cov=myproject --cov-report=term-missing tests/
# Generar reporte HTML
pytest --cov=myproject --cov-report=html --cov-report=xml tests/
# Fallar bajo threshold (hecho cumplir en CI)
pytest --cov=myproject --cov-fail-under=80 tests/
# Branch coverage (rastrea si if/else ambos tomados)
pytest --cov=myproject --cov-branch tests/
# Configuración pyproject.toml
[tool.coverage.run]
source = ["myproject"]
branch = true
omit = [
"*/tests/*",
"*/migrations/*",
"*/venv/*",
]
[tool.coverage.report]
precision = 2
fail_under = 80
skip_covered = true
show_missing = true
[tool.coverage.html]
directory = "htmlcov"
[tool.coverage.xml]
output = "coverage.xml"
# Ejecutando en CI con múltiples markers
pytest -m "not slow" --cov=myproject --cov-report=xml --cov-fail-under=80
JavaScript (nyc / c8)
# c8 es la moderna herramienta de cobertura nativa V8 rápida
npm install --save-dev c8
# Ejecutar tests con cobertura
npx c8 npm test
# Reporte HTML
npx c8 --reporter=html --reporter=text npm test
# Fallar bajo threshold
npx c8 --check-coverage --lines 80 --functions 80 --branches 75 npm test
# Excluir archivos de cobertura
npx c8 --exclude="src/**/*.test.js" --exclude="src/vendor/**" npm test
// package.json
{
"scripts": {
"test": "vitest run",
"test:coverage": "vitest run --coverage"
},
"devDependencies": {
"@vitest/coverage-v8": "^1.0.0",
"vitest": "^1.0.0"
}
}
// vitest.config.ts
import { defineConfig } from 'vitest/config';
export default defineConfig({
test: {
coverage: {
provider: 'v8',
reporter: ['text', 'html', 'json'],
lines: 80,
functions: 80,
branches: 75,
statements: 80,
exclude: [
'**/*.test.ts',
'**/tests/**',
'**/node_modules/**',
'**/vendor/**'
]
}
}
});
Java (JaCoCo)
<!-- pom.xml -->
<plugin>
<groupId>org.jacoco</groupId>
<artifactId>jacoco-maven-plugin</artifactId>
<version>0.8.11</version>
<executions>
<execution>
<goals>
<goal>prepare-agent</goal>
</goals>
</execution>
<execution>
<id>report</id>
<phase>test</phase>
<goals>
<goal>report</goal>
</goals>
</execution>
<execution>
<id>check</id>
<goals>
<goal>check</goal>
</goals>
<configuration>
<rules>
<rule>
<element>BUNDLE</element>
<limits>
<limit>
<counter>LINE</counter>
<value>COVEREDRATIO</value>
<minimum>0.80</minimum>
</limit>
<limit>
<counter>BRANCH</counter>
<value>COVEREDRATIO</value>
<minimum>0.75</minimum>
</limit>
</limits>
</rule>
</rules>
</configuration>
</execution>
</executions>
</plugin>
# Generar reporte
mvn jacoco:report
# Verificar thresholds
mvn jacoco:check
# Generar badge para README
mvn jacoco:report && cat target/site/jacoco/index.html | grep -oP 'Total[^%]+%'
Integración CI
# .github/workflows/test.yml
name: Test
on: [push, pull_request]
jobs:
test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-python@v5
with:
python-version: '3.12'
- run: pip install pytest pytest-cov
- run: pytest --cov=myproject --cov-report=xml --cov-fail-under=80
- uses: codecov/codecov-action@v3
with:
files: ./coverage.xml
Lo que funciona
- Mide branch coverage, no solo line coverage. Una sola línea con
if x:reporta como cubierta si la rama true se ejecuta, incluso si la rama false nunca se testea. Branch coverage detecta esto. - Configura thresholds por módulo, no globalmente. La lógica de negocio core debería tener thresholds más altos (85-90%) que el código glue de UI o archivos auto-generados (50-60%).
- Excluye código de infraestructura de los objetivos. Migraciones de base de datos, clientes gRPC generados y archivos de config no deberían contar contra tu métrica de cobertura.
- Rastrea tendencias de cobertura, no números absolutos. Una caída de 5% en un PR es más útil que “estamos en 82% hoy”.
- Revisa líneas no cubiertas en PRs, no solo el porcentaje. Un bot de comentarios que lista las 3 líneas no cubiertas es más útil que un checkmark rojo al 79%.
Errores Comunes
- Hacer cumplir 100% de cobertura. Incentiva tests que ejecutan código sin asertar comportamiento, o anotaciones
@excludepara gamear la métrica. - Solo medir line coverage. Una función con 10 branches puede mostrar 100% de line coverage mientras solo 2 branches están testeados.
- Incluir archivos de test en cobertura. Utilidades de test y clases mock inflan el número y ocultan cobertura de producción faltante.
- Comparar cobertura entre lenguajes. Python branch coverage y Java line coverage no son métricas comparables — rastrea tendencias dentro de cada codebase.
- Ignorar cobertura en tests de integración. Los tests de integración lentos a menudo cubiertan las rutas más importantes; excluirlos de cobertura oculta gaps reales.
Lectura Adicional
- Documentación oficial: consulta la referencia actualizada del framework o herramienta utilizada.
- Guías relacionadas: explora las guías de testing y coverage para profundizar.
- Patrones complementarios: revisa los patrones de diseño aplicables a tu stack tecnológico.
- Postmortems públicos: estudia incidentes reales de equipos que enfrentaron problemas similares en producción.
Notas de Producción
- Despliega gradualmente usando canary o blue-green para detectar regresiones temprano.
- Configura alertas para errores, latencia p99 y tasa de fallos antes de habilitar en producción.
- Documenta el rollback en el runbook; prueba el procedimiento en staging al menos una vez por trimestre.
- Revisa logs estructurados con correlation IDs para trazar requests end-to-end en incidentes.
Puntos Clave
- Aplica medir cobertura de test cuando necesites una solución práctica para tu caso de uso.
- Monitorea el rendimiento después de implementar; mide latencia, errores y uso de recursos antes y después.
- Revisa la sección de Troubleshooting ante errores comunes; la mayoría tienen causa raíz documentada con solución.
- Mantén dependencias actualizadas y ejecuta tests en CI para prevenir regresiones en producción.
Troubleshooting
- Flaky tests: isolate shared state, time, and randomness. Make tests independent and deterministic; quarantine persistently flaky tests.
- High coverage but bugs in production: coverage does not guarantee correctness. Add mutation testing, property-based tests, or contract tests.
- Slow test suite: parallelize, mock slow dependencies, and avoid end-to-end tests for logic that can be unit tested.
- Tests pass locally but fail in CI: check environment differences, timezone, locale, and dependency versions. Pin tool versions.
- Debugging a failing integration test: Reset state before each test.
Errores Comunes en Producción
- Copiar el ejemplo sin adaptarlo a volúmenes y modos de fallo reales.
- Saltar tests de carga e inyección de errores antes del primer despliegue productivo.
- Codificar valores fijos que deberían ser configurables por entorno.
- Olvidar agregar logging y monitoreo en cada paso.
- Desplegar sin plan de rollback ni estrategia de backup probada.
- Asumir que el ejemplo mínimo escalará sin agregar caché o procesamiento por lotes.
- No documentar la versión y configuración usadas en producción.
- Dejar la receta sin cambios cuando evolucionan las dependencias o la escala.
Preguntas frecuentes
¿Es 100% de cobertura un buen objetivo?
100% de cobertura de líneas es alcanzable pero puede ser engañoso. Un número alto de cobertura con aserciones débiles no significa que el código esté bien probado. Apunta a una cobertura significativa de caminos críticos.
¿Cuál es la diferencia entre cobertura de líneas y de ramas?
La cobertura de líneas cuenta líneas ejecutadas. La cobertura de ramas cuenta si cada rama de decisión (if/else, switch) fue tomada. La cobertura de ramas suele revelar más caminos no probados.
¿Cómo debo usar cobertura en CI?
Establece umbrales mínimos para módulos críticos, rastrea tendencias a lo largo del tiempo y rechaza pull requests que bajen considerablemente la cobertura sin justificación. Evita jugar con la métrica.
Recursos Relacionados
Configurar Fixtures de Test
Cómo gestionar fixtures de test con patrones factory, hooks de setup/teardown y datos deterministas para tests unitarios e integración confiables en Python, JavaScript y Java.
RecipeGenerar Datos de Test
Cómo generar datos de test realistas y deterministas con Faker, factory-boy y generadores type-aware para suites de test confiables en Python, JavaScript y Java.
GuideGuía de Estrategia de Testing
Una guía práctica para construir una estrategia de testing en capas con unit, integration y end-to-end tests.
RecipeImplementar Mutation Testing
Cómo usar mutation testing con MutPy, Stryker y PIT para evaluar si tus tests realmente asertan comportamiento o simplemente ejecutan código.
RecipeMedir y Exigir Cobertura de Tests con pytest-cov
Mide y exige cobertura con pytest-cov: reportes HTML, branch coverage, exclusiones e integración.
RecipePytest Fixtures y Parametrize
Cómo usar pytest fixtures y @pytest.mark.parametrize para escribir tests data-driven con lógica de setup reutilizable en proyectos Python.