La cobertura muestra qué líneas y caminos ejecutó la suite. Permite encontrar comportamientos olvidados, pero no mide la calidad de las aserciones ni demuestra ausencia de defectos.

Medir líneas y branches

python -m pip install coverage pytest
coverage run --branch -m pytest
coverage report -m
coverage html

Revisa htmlcov/index.html. Branch coverage detecta decisiones donde solo se recorrió un destino aunque todas las líneas parezcan cubiertas.

[run]
branch = True
source = src

[report]
show_missing = True
fail_under = 85

El límite debe evitar retrocesos, no fomentar pruebas vacías. La guía de pytest explica el diseño de escenarios y fixtures y mocks ayudan a aislar dependencias.

Prioriza reglas de negocio, errores y fronteras externas. Una línea trivial y una decisión financiera pesan igual en el porcentaje, pero no en el riesgo. Revisa pruebas que ejecutan código sin verificar el resultado.

La documentación oficial de coverage.py, consultada el 22 de julio de 2026, explica statements, branches e informes. Usa la cobertura como mapa de investigación, no como objetivo aislado.

Entender medición, combinación e informes

coverage.py separa la ejecución del código de la presentación de resultados. coverage run recopila datos en .coverage, coverage report imprime un resumen, coverage html crea un informe navegable y los formatos XML o JSON sirven para integraciones. Así es posible generar varias salidas sin repetir la suite.

Inicia la medición desde un directorio predecible. Cuando las pruebas se ejecuten en procesos o tareas diferentes, genera archivos separados y combínalos:

coverage erase
coverage run --parallel-mode -m pytest tests/unit
coverage run --parallel-mode -m pytest tests/integration
coverage combine
coverage report -m

coverage erase evita contaminar el resultado con datos anteriores. El modo paralelo crea archivos distintos y coverage combine los reúne. En una CI distribuida, descarga los artefactos de cada tarea en un directorio común. Compara porcentajes solo si usan la misma configuración y el mismo conjunto de fuentes.

Preferir branch coverage para decisiones

La cobertura de líneas indica si una línea se ejecutó, pero puede ocultar una ruta ausente:

def coste_envio(total: float, urgente: bool) -> float:
    if total >= 200 or urgente:
        return 0
    return 18

Una prueba con total=250 llega al resultado gratuito, pero no demuestra qué sucede con urgente=True y un total menor. Otro caso debe recorrer el resultado de pago. Branch coverage observa las transiciones de una decisión y marca ramas parciales en HTML.

Esto no exige probar cada combinación mecánica. Elige casos que representen comportamientos: debajo del límite, exactamente en el límite, envío urgente y entrada inválida cuando corresponda. Las aserciones deben verificar el valor y los efectos relevantes, no limitarse a llamar la función.

Configurar el código medido

La opción source permite identificar módulos que podrían haberse ejecutado, incluso archivos nunca importados por la suite. Adáptala a la estructura real:

[run]
branch = True
source =
    src
omit =
    */tests/*
    */migrations/*

[report]
show_missing = True
skip_covered = False
precision = 1
fail_under = 85

[html]
directory = htmlcov

No copies exclusiones sin revisar el repositorio. Puede ser razonable omitir migraciones generadas, pero una transformación de datos escrita a mano quizá necesite pruebas directas. Los módulos de prueba suelen quedar fuera de la métrica de producción, aunque los helpers complejos de la suite también deben verificarse.

El directorio de ejecución influye. Ejecutar subconjuntos desde ubicaciones diferentes puede producir rutas difíciles de combinar. Estandariza los comandos locales y de CI y conserva una sola configuración versionada en .coveragerc, pyproject.toml o setup.cfg.

Excluir solo con una razón

Algunas líneas son defensivas o específicas de una plataforma. coverage.py entiende marcas como # pragma: no cover, pero cada excepción debe poder revisarse:

if TYPE_CHECKING:
    from paquete_externo import Cliente

if __name__ == "__main__":  # pragma: no cover
    main()

Antes de excluir, pregunta si el comportamiento puede probarse mediante una interfaz pública. No marques bloques difíciles solo para mejorar el panel. Las exclusiones amplias reducen la capacidad de encontrar riesgos futuros. Las reglas mediante expresiones regulares también necesitan patrones concretos y documentación.

Definir una política de límite útil

No existe un objetivo universal. Un paquete nuevo puede comenzar con un límite alto; un sistema antiguo puede registrar su nivel actual y bloquear retrocesos mientras mejora módulos importantes. Un fail_under global es sencillo, pero permite compensar código nuevo sin pruebas con módulos antiguos muy cubiertos.

Durante la revisión, examina el cambio y pregunta qué caminos nuevos se añadieron. Un módulo de autenticación con 90% puede presentar más riesgo que un formateador con 60%. Combina el control automático con revisión de escenarios, pruebas de regresión y análisis de las líneas ausentes.

Configura el redondeo conscientemente. precision modifica la presentación, mientras el límite se evalúa sobre la medición. Ejecuta localmente el mismo comando de CI para evitar resultados inesperados.

Integrar pytest y subprocesos

coverage run -m pytest mantiene explícita la capa de medición. El plugin pytest-cov ofrece opciones integradas, pero no es obligatorio. Elige un comando oficial y documéntalo; mezclar invocaciones puede cambiar filtros, ramas o el tratamiento de datos anteriores.

El código en subprocesos requiere configuración adicional, porque un intérprete nuevo no hereda automáticamente la medición. Sigue la guía de subprocesos de la versión instalada y confirma que los archivos paralelos se guardaron y combinaron. No supongas que el proceso padre representa workers, consumidores de colas o comandos externos.

Leer el informe como diagnóstico

Empieza por líneas ausentes y parciales en módulos de mayor riesgo. En el manejo de errores, prueba fallos de red, datos incorrectos, permisos y liberación de recursos. En bucles, incluye colecciones vacías y salidas anticipadas. En condiciones compuestas, busca cortocircuitos que eviten evaluar un operando.

Una mejora real incluye una aserción que fallaría si cambia el comportamiento. Pregunta si sustituir el valor devuelto o eliminar un efecto rompería la prueba. Mutation testing puede profundizar el análisis, pero no sustituye un buen diseño de escenarios.

En CI, publica el informe mínimo necesario y controla el acceso al HTML cuando el código sea privado. No subas configuraciones con secretos ni archives indiscriminadamente el espacio de trabajo. El artefacto útil es el informe, no todo el entorno de ejecución.

Revisar la cobertura como parte del diseño

Cuando un cambio modifica una condición, inspecciona las líneas ausentes de esa zona antes de perseguir código no relacionado. Pregunta qué comportamiento observable representa cada rama y prueba mediante la interfaz pública cuando sea posible. Esas pruebas resisten mejor una refactorización que las acopladas a detalles internos.

La cobertura también puede descubrir código muerto. Antes de crear una prueba solo para ejecutar una salida inalcanzable, confirma que esa rama todavía pertenece al producto. Eliminar lógica obsoleta puede aportar más claridad. Documenta las exclusiones de forma estrecha y explica por qué una defensa no puede ejecutarse en el entorno soportado.

Revisa ocasionalmente el informe HTML aunque CI aplique un umbral. La vista por archivo muestra grupos de comportamiento sin probar que un porcentaje global oculta. Combina esa evidencia con el historial de errores y la importancia del módulo para decidir dónde invertir el siguiente esfuerzo.