Módulo 5: Coverage y Edge Cases
pytest-cov y Coverage Reports
pytest-cov y Coverage Reports
Descripción de la cápsula
Tus tests pasan. ¿Pero cuánto de tu código realmente ejecutan? Sin medición, estás volando a ciegas. Un test suite que "pasa" puede estar ejecutando solo el 30% del código — dejando el 70% como territorio desconocido donde se esconden los bugs.
Esta cápsula te introduce a pytest-cov, la herramienta que mide qué porcentaje de tu código se ejecuta durante los tests. Aprenderás a instalarla, ejecutarla, leer los reportes en terminal y HTML, configurarla correctamente, y usar un ejemplo completo (calculadora) para identificar gaps de coverage.
Al final, tendrás el mapa que necesitas para saber qué tests te faltan. El siguiente paso — interpretar ese mapa — lo verás en la cápsula 03.
Antes de empezar
Necesitas tener instalado Python 3.8+ y pytest. Si vienes del Módulo 4, ya tienes un proyecto con tests. Si no, crea un directorio con un módulo mínimo y un test que lo importe — suficiente para ejecutar pytest --cov.
Qué es Code Coverage
No es una medida de calidad
Code coverage es el porcentaje de líneas (o branches, o funciones) de tu código que se ejecutan cuando corres tus tests. Eso es todo. No mide si tu código es correcto, si tus tests son buenos, ni si tu feature funciona en producción.
Coverage alto ≠ Código de calidad
100% coverage ≠ Sin bugs
Un test que hace assert True en cada función da 100% coverage. Pero no valida nada. Coverage te dice dónde van tus tests, no qué tan bien validan.
Una analogía: el mapa
Piensa en coverage como un mapa de tu código:
- ✅ Verde (covered): Tus tests pasan por ahí. Sabes qué hace ese código durante los tests.
- ❌ Rojo (missing): Tus tests nunca entran ahí. No tienes idea qué pasa cuando se ejecuta ese camino.
- ⚠️ Amarillo (parcial): Cubres una rama del
ifpero no la otra. Hay caminos lógicos sin explorar.
El mapa no te dice si el territorio es seguro. Pero sin el mapa, no sabes ni qué territorio existe. Coverage es visibilidad, no garantía.
Por qué importa cuando trabajas con AI
Claude Code puede generar 200 líneas en segundos. Sin coverage, no tienes forma objetiva de saber qué fracción de esas 200 líneas está validada. Puedes tener 50 tests que pasan y cubrir solo el happy path — dejando toda la lógica de error, validaciones y edge cases sin tocar. Coverage te da el número concreto y las líneas exactas que faltan.
Lo que coverage no te dice
Es importante tener claras las limitaciones:
- Coverage no valida lógica. Una línea puede ejecutarse con un input trivial y tu assert puede ser incorrecto. Por ejemplo:
assert add(2, 2) == 4— la línea se ejecuta, pero si hubieras escritoassert add(2, 2) == 5por error, el test fallaría. Coverage solo mide ejecución, no corrección. - Coverage no prioriza. Todas las líneas cuentan igual. Una línea en el crítico path de autenticación y una en un
printde debug suman igual al porcentaje. Tu juicio sigue siendo necesario para decidir qué gaps cerrar primero. - 100% no es el objetivo ciego. Algunas líneas son imposibles de cubrir (configuración de CLI,
if __name__ == "__main__"), otras no vale la pena (código defensivo que "nunca" debería ejecutarse). La cápsula 03 profundiza en esto.
Antes de empezar: requisitos
Para seguir esta cápsula necesitas:
- ✅ Python 3.8+ instalado
- ✅ Proyecto con pytest configurado (Módulos 1-4)
- ✅ Estructura típica: carpeta de código fuente (
src,app, etc.) y carpetatests/
Si tu proyecto usa src/ como paquete, asegúrate de tener un pyproject.toml con [tool.setuptools.packages.find] o equivalente, o de ejecutar pytest desde la raíz con el path correcto.
Instalación y Ejecución Básica
Instalar pytest-cov
pip install pytest-cov
En un proyecto con pyproject.toml o requirements.txt:
# requirements.txt
pytest>=7.0.0
pytest-cov>=4.0.0
Ejecutar coverage
Por defecto, pytest-cov usa la librería coverage.py para medir qué código se ejecuta. Necesitas indicar qué módulo(s) quieres medir:
# Medir el módulo "mymodule" mientras corres tests en tests/
pytest --cov=mymodule tests/ -v
# Medir varios módulos
pytest --cov=src --cov=mymodule tests/ -v
El flag --cov indica el source a medir. Suele ser el paquete principal de tu proyecto (src, app, mymodule, etc.).
Formatos de reporte
Terminal con líneas faltantes:
pytest --cov=mymodule --cov-report=term-missing tests/
Esto muestra en la terminal qué líneas exactas no están cubiertas (por ejemplo: 18-22, 31-35).
Reporte HTML (interactivo):
pytest --cov=mymodule --cov-report=html tests/
Genera la carpeta htmlcov/. Abre htmlcov/index.html en el navegador para ver el reporte con colores: verde = cubierto, rojo = no cubierto. Clic en cualquier archivo para ver línea por línea.
Ambos a la vez:
pytest --cov=mymodule --cov-report=term-missing --cov-report=html tests/
Leyendo el Reporte en Terminal
Ejemplo de salida
Name Stmts Miss Cover Missing
-------------------------------------------------
mymodule/calc.py 25 5 80% 18-22
mymodule/utils.py 40 12 70% 31-35, 38-44
-------------------------------------------------
TOTAL 65 17 74%
Qué significa cada columna
| Columna | Significado |
|---|---|
| Name | Archivo o módulo medido |
| Stmts | Número de statements ejecutables (líneas de código que pueden ejecutarse) |
| Miss | Statements que ningún test ejecutó |
| Cover | Porcentaje: (Stmts - Miss) / Stmts |
| Missing | Números de línea exactos que no se cubrieron |
Cómo usar la columna Missing
La columna Missing es la más útil para mejorar tu suite. Si ves 18-22, abre el archivo, ve a esas líneas, y pregunta: "¿qué escenario de test haría que el flujo llegue aquí?" A veces es código muerto (nunca se usa). A veces es un branch de error que nunca testeaste.
Ejemplo práctico de lectura
Imagina que el reporte muestra:
src/auth.py 42 12 71% 23-28, 35-40
Pasos concretos:
- Abres
src/auth.py. - Líneas 23-28: ves un
if not user:que lanzaUnauthorizedError. - Líneas 35-40: ves un
except ValidationErrorque retorna un mensaje específico. - Conclusión: no tienes tests que simulen usuario inexistente ni validación fallida. Esos son tus próximos tests.
El Reporte HTML
Generar y abrir
pytest --cov=mymodule --cov-report=html tests/
open htmlcov/index.html # macOS
# o: xdg-open htmlcov/index.html (Linux)
# o: start htmlcov/index.html (Windows)
Qué verás
- Página principal: Lista de archivos con porcentaje de coverage.
- Verde: Líneas ejecutadas por al menos un test.
- Rojo: Líneas nunca ejecutadas.
- Clic en un archivo: Vista línea por línea con colores. Rojo = gap.
El HTML es ideal para explorar código grande: puedes saltar entre archivos y ver exactamente qué ramas de if/else o try/except no están cubiertas.
Cuándo usar HTML vs terminal
| Situación | Recomendación |
|---|---|
| CI/CD, commit rápido | term-missing — rápido, en la misma salida que pytest |
| Análisis profundo, código nuevo | HTML — exploración visual |
| Code review de coverage | HTML — compartir htmlcov/ con el equipo |
| Debug de un archivo concreto | Ambos — terminal para Missing, HTML para contexto |
Tipos de Coverage
Coverage no es un solo número. Hay varias dimensiones:
Line coverage (por defecto)
Pregunta: ¿Se ejecutó esta línea al menos una vez?
Es el más común. Una línea cubierta significa que algún test la ejecutó. No dice si cubriste todas las ramas lógicas.
Branch coverage
Pregunta: ¿Se ejecutaron ambas ramas de cada if, else, try/except, etc.?
Ejemplo:
def classify(x: int) -> str:
if x >= 0:
return "positive"
else:
return "negative"
Si solo testeas classify(5), tienes 100% line coverage pero 50% branch coverage: la rama else nunca se ejecuta. Branch coverage es más valioso porque cubre caminos lógicos, no solo líneas.
Para activarlo:
pytest --cov=mymodule --cov-branch tests/
Function coverage
Pregunta: ¿Se llamó esta función al menos una vez?
Suele ser redundante con line coverage (si una función se ejecutó, sus líneas se cubrieron). Pero en reportes detallados puede ser útil para ver qué funciones nunca se invocan.
Resumen práctico
Line coverage: ¿Pasó el flujo por esta línea?
Branch coverage: ¿Pasó por ambas ramas del if/else?
Function coverage: ¿Se llamó esta función?
Prioriza branch coverage cuando puedas — captura bugs en ramas de error que line coverage ignora.
Usar pytest-cov con Claude Code
Cuando trabajas con Claude Code, coverage se convierte en una herramienta de colaboración:
-
Mide después de cada feature: Ejecuta
pytest --cov=src --cov-report=term-missing tests/tras implementar. Comparte el output (o la columnaMissing) a Claude Code con el prompt: "Estas líneas no están cubiertas. Genera tests que las cubran." -
Da contexto del reporte: En vez de pedir "escribe tests", di algo como: "El coverage report muestra Missing: 23-28 en auth.py. Esas líneas son el branch que lanza UnauthorizedError cuando el usuario no existe. Escribe tests que cubran ese caso."
-
Usa HTML para análisis profundo: Si tienes un módulo grande con muchos gaps, genera el HTML y abre
htmlcov/index.html. Luego pide a Claude Code: "Tengo este módulo [pega el código]. El reporte de coverage muestra que las líneas 45-67 no están cubiertas. ¿Qué tests debería escribir?" Claude Code puede analizar el código y el contexto de las líneas para proponer tests relevantes. -
Itera con el reporte: El workflow ideal es: medir → dar el reporte a Claude Code → generar tests → medir de nuevo. Cada iteración reduce los gaps hasta alcanzar tu target (por ejemplo 90%).
Configuración con pyproject.toml o .coveragerc
pyproject.toml
[tool.pytest.ini_options]
addopts = "--cov=src --cov-report=term-missing --cov-branch"
[tool.coverage.run]
source = ["src"]
omit = ["tests/*", "*/__pycache__/*", "*/venv/*"]
[tool.coverage.report]
exclude_lines = [
"pragma: no cover",
"def __repr__",
"raise NotImplementedError",
]
addopts: Opciones que pytest ejecuta por defecto (incluyendo coverage).source: Qué carpetas medir.omit: Archivos o patrones a excluir (tests, cache, venv).exclude_lines: Líneas que coverage ignorará (útil para__repr__, código defensivo que "nunca" se ejecuta, etc.).
Con esto, basta con ejecutar pytest tests/ y ya tendrás coverage.
.coveragerc (alternativa)
Si prefieres un archivo dedicado:
[run]
source = src
omit = tests/*, */__pycache__/*, */venv/*
[report]
exclude_lines =
pragma: no cover
def __repr__
raise NotImplementedError
Ejemplo Completo: Calculadora
Estructura del proyecto
calc_demo/
├── src/
│ └── calc.py
├── tests/
│ └── test_calc.py
├── pyproject.toml
└── htmlcov/ # generado por pytest-cov
Módulo a medir: calc.py
# src/calc.py
"""Calculadora básica con operaciones aritméticas."""
def add(a: float, b: float) -> float:
"""Suma dos números."""
return a + b
def subtract(a: float, b: float) -> float:
"""Resta b de a."""
return a - b
def multiply(a: float, b: float) -> float:
"""Multiplica dos números."""
return a * b
def divide(a: float, b: float) -> float:
"""Divide a entre b. Lanza ZeroDivisionError si b es 0."""
if b == 0:
raise ZeroDivisionError("Cannot divide by zero")
return a / b
Tests incompletos (solo add y subtract)
# tests/test_calc.py
import pytest
from src.calc import add, subtract
def test_add_positive_numbers():
assert add(2, 3) == 5
def test_add_negative_numbers():
assert add(-1, -2) == -3
def test_subtract():
assert subtract(10, 4) == 6
Ejecutar coverage
cd calc_demo
pip install pytest pytest-cov
pytest --cov=src --cov-report=term-missing tests/ -v
Salida esperada
Name Stmts Miss Cover Missing
---------------------------------------------
src/calc.py 15 8 47% 14-21
---------------------------------------------
TOTAL 15 8 47%
Las líneas 14-21 corresponden a multiply y divide. El reporte te dice exactamente qué funciones ningún test está ejecutando.
Cómo usar esta información
- Miras
Missing: 14-21. - Abres
src/calc.pyy ves que sonmultiplyydivide. - Escribes tests para esas funciones (incluyendo el caso
divide(x, 0)para cubrir la rama delif b == 0). - Vuelves a ejecutar coverage. El porcentaje sube y
Missingse reduce o desaparece para ese archivo.
Walkthrough: del reporte a los tests
Supongamos que ejecutaste coverage y obtuviste:
Name Stmts Miss Cover Missing
---------------------------------------------
src/calc.py 15 8 47% 14-21
---------------------------------------------
TOTAL 15 8 47%
Paso 1: Abre src/calc.py y busca las líneas 14-21.
Paso 2: Identificas que son multiply y divide (incluyendo el if b == 0).
Paso 3: Decides qué tests escribir:
- Para
multiply: un test con números positivos y otro con negativos (por si acaso). - Para
divide: un test normal y uno que verifiqueZeroDivisionError.
Paso 4: Escribes los tests (o pides a Claude Code que los genere con el contexto del reporte).
Paso 5: Ejecutas pytest --cov=src --cov-report=term-missing tests/ de nuevo.
Resultado esperado: Coverage sube a ~93% o 100%, y Missing queda vacío o desaparece para calc.py.
Este loop — medir, identificar, escribir, medir — es el núcleo del proyecto de este módulo.
Usar Claude Code para cerrar gaps
Cuando tengas el reporte con Missing identificado, puedes dar contexto a Claude Code para que genere los tests. Un prompt efectivo:
Tengo este módulo src/calc.py. El reporte de coverage indica que las líneas 14-21
(multiply y divide) no están cubiertas. Escribe los tests que faltan para
test_calc.py, incluyendo el caso divide por cero que lanza ZeroDivisionError.
Claude Code genera los tests, tú los ejecutas, y el coverage sube. En la cápsula 04 aprenderás prompts más sofisticados para edge cases.
Referencia Rápida de Comandos
| Objetivo | Comando |
|---|---|
| Coverage básico | pytest --cov=src tests/ |
| Ver líneas faltantes | pytest --cov=src --cov-report=term-missing tests/ |
| Reporte HTML | pytest --cov=src --cov-report=html tests/ |
| Incluir branch coverage | pytest --cov=src --cov-branch tests/ |
| Todo junto | pytest --cov=src --cov-report=term-missing --cov-report=html --cov-branch tests/ |
| Solo un archivo | pytest tests/test_calc.py --cov=src.calc --cov-report=term-missing |
Usar pytest-cov con Claude Code
Cuando trabajas con Claude Code para mejorar coverage, el reporte es tu aliado. Sigue estos patrones:
Dar el reporte como contexto
Antes de pedir tests para cerrar gaps, pasa el output de coverage:
Aquí está el reporte de coverage de mi módulo auth:
Name Stmts Miss Cover Missing
---------------------------------------------
src/auth.py 52 18 65% 23-28, 35-40, 67-72
---------------------------------------------
TOTAL 52 18 65%
Las líneas 23-28 son el branch de usuario no encontrado.
Las 35-40 son el manejo de ValidationError.
Las 67-72 son el logout.
Genera tests que cubran estas líneas.
Cuanto más específico seas (números de línea, qué hace cada bloque), más precisos serán los tests que Claude Code genere.
Iterar con el reporte actualizado
Después de agregar tests, vuelve a ejecutar coverage y comparte el nuevo reporte. Si aún hay gaps, pide tests para las líneas que quedan. El ciclo es:
- Ejecutar
pytest --cov=src --cov-report=term-missing tests/ - Copiar la salida y pegarla en el chat con Claude Code
- Pedir tests para las líneas en
Missing - Repetir hasta alcanzar el target
El reporte HTML como referencia visual
Si el proyecto es grande, genera el HTML y abre calc.py (o el archivo que te interesa). Describe a Claude Code qué líneas están en rojo: "En calc.py, las líneas 14-21 (multiply y divide) están sin cubrir." Claude Code no puede ver la imagen, pero con esa descripción puede generar los tests correctos.
Ejercicios
Ejercicio 1: Instalar y ejecutar (Básico)
Crea un directorio exercise_01 con esta estructura:
exercise_01/
├── greet.py
└── tests/
└── test_greet.py
El módulo greet.py debe tener una función greet(name: str) -> str que retorne f"Hello, {name}!". Escribe un test que llame a greet("World"). Desde exercise_01, ejecuta pytest --cov=greet --cov-report=term-missing tests/ -v y anota el porcentaje que obtienes.
Ver solución
# exercise_01/greet.py
def greet(name: str) -> str:
return f"Hello, {name}!"
# exercise_01/tests/test_greet.py
from greet import greet
def test_greet():
assert greet("World") == "Hello, World!"
pip install pytest pytest-cov
pytest --cov=greet --cov-report=term-missing tests/ -v
Deberías ver 100% coverage en greet.py porque la única línea ejecutable (el return) se ejecuta en el test. Si el porcentaje es menor, revisa que el import sea correcto y que estés ejecutando desde el directorio exercise_01.
Ejercicio 2: Interpretar Missing (Intermedio)
Tienes este módulo:
# validator.py
def is_even(n: int) -> bool:
if n % 2 == 0:
return True
return False
Y este test:
def test_is_even():
assert is_even(4) is True
Ejecuta coverage. ¿Qué líneas aparecen en Missing? ¿Por qué?
Ver solución
Las líneas en Missing serán las del return False (por ejemplo, línea 5). El test solo pasa por la rama True (cuando n % 2 == 0). La rama False nunca se ejecuta. Para cubrirla, necesitas un test como:
def test_is_even_false():
assert is_even(3) is False
Ejercicio 3: Configurar pyproject.toml (Intermedio)
Añade configuración de coverage a un pyproject.toml existente para que, al ejecutar solo pytest, se ejecute coverage con term-missing y --cov-branch, midiendo el paquete app y excluyendo tests/ y venv/.
Ver solución
[tool.pytest.ini_options]
addopts = "--cov=app --cov-report=term-missing --cov-branch"
[tool.coverage.run]
source = ["app"]
omit = ["tests/*", "*/venv/*", "*/__pycache__/*"]
Ejercicio 4: Identificar gaps con HTML (Intermedio)
Genera un reporte HTML para el proyecto de la calculadora (src/calc.py con add, subtract, multiply, divide). Abre htmlcov/index.html, entra a calc.py, y anota qué líneas están en rojo. Escribe los tests que faltan para que desaparezcan.
Ver solución
Las líneas rojas serán multiply y divide, más la rama if b == 0 dentro de divide. Tests necesarios:
def test_multiply():
assert multiply(3, 4) == 12
def test_divide():
assert divide(10, 2) == 5.0
def test_divide_by_zero_raises():
with pytest.raises(ZeroDivisionError, match="Cannot divide by zero"):
divide(5, 0)
Ejercicio 5: Branch vs Line coverage (Avanzado)
Este código tiene un bug en la rama else:
# buggy.py
def clamp(value: int, low: int, high: int) -> int:
if value < low:
return low
elif value > high:
return high
else:
return value
Escribe un test que pase clamp(5, 0, 10) (valor en rango). Ejecuta coverage con --cov-branch y sin él. Explica por qué line coverage puede mostrar 100% pero branch coverage no.
Ver solución
Con un solo test clamp(5, 0, 10):
- Line coverage: 100% — todas las líneas se ejecutan (el flujo entra por
else). - Branch coverage: ~50% o menos — no cubres las ramas
value < lownivalue > high.
Si tuvieras un bug en la rama elif value > high (por ejemplo return low en vez de return high), line coverage no lo detectaría porque esa rama nunca se ejecuta. Branch coverage te obliga a escribir tests para cada rama.
Tests completos:
def test_clamp_below_low():
assert clamp(-5, 0, 10) == 0
def test_clamp_above_high():
assert clamp(15, 0, 10) == 10
def test_clamp_in_range():
assert clamp(5, 0, 10) == 5
Ejercicio 6: Omit y exclude (Avanzado)
Tienes un __init__.py que solo hace from .calc import * y un main() que nunca se testea (es el entry point de CLI). Configura coverage para omitir __init__.py y excluir líneas con pragma: no cover para que main() no cuente en el reporte.
Ver solución
En pyproject.toml:
[tool.coverage.run]
source = ["src"]
omit = ["tests/*", "*/__init__.py", "*/__pycache__/*"]
Para main(), en el código:
def main(): # pragma: no cover
"""CLI entry point - not tested."""
...
Y en configuración:
[tool.coverage.report]
exclude_lines = [
"pragma: no cover",
]
pragma: no cover es un comentario especial que coverage.py reconoce para excluir líneas del reporte.
Práctica guiada: reproducir el ejemplo completo
Si quieres seguir el ejemplo de la calculadora paso a paso, haz lo siguiente:
1. Crear la estructura:
mkdir -p calc_demo/src calc_demo/tests
cd calc_demo
2. Crear src/calc.py con el código del módulo (add, subtract, multiply, divide) tal como se muestra arriba.
3. Crear un src/__init__.py vacío (o con from .calc import *) para que src sea un paquete.
4. Crear tests/test_calc.py solo con los tests de add y subtract.
5. Crear pyproject.toml mínimo:
[project]
name = "calc-demo"
version = "0.1.0"
[tool.pytest.ini_options]
testpaths = ["tests"]
pythonpath = ["."]
6. Ejecutar:
pip install pytest pytest-cov
pytest --cov=src --cov-report=term-missing tests/ -v
Deberías ver ~47% coverage y Missing: 14-21 (o rangos similares según la numeración real). Luego agrega los tests de multiply y divide, vuelve a ejecutar, y verás el porcentaje subir.
7. Generar HTML y explorar:
pytest --cov=src --cov-report=html tests/
open htmlcov/index.html
Entra en src_calc_py.html (el nombre puede variar) y observa en rojo las líneas de multiply y divide. Esta es la vista que usarás en proyectos reales para priorizar qué gaps cerrar primero.
Troubleshooting
"No data to report" o 0% coverage
Causa: El path de --cov no coincide con lo que importas en los tests.
Solución: Si importas from src.calc import add, el source debe ser src (el paquete), no src.calc ni calc. Ejecuta desde la raíz del proyecto y usa --cov=src si tu estructura es src/calc.py.
Coverage incluye tests y venv
Causa: No configuraste omit.
Solución: En pyproject.toml o .coveragerc:
[tool.coverage.run]
omit = ["tests/*", "*/venv/*", "*/__pycache__/*"]
Branch coverage no aparece en el reporte
Causa: No pasaste --cov-branch.
Solución:
pytest --cov=mymodule --cov-branch --cov-report=term-missing tests/
O en addopts:
addopts = "--cov=src --cov-branch --cov-report=term-missing"
HTML no se genera o no actualiza
Causa: Carpeta htmlcov/ de ejecuciones anteriores.
Solución: Borra htmlcov/ y vuelve a ejecutar, o usa --cov-report=html sin modificar manualmente los archivos. Asegúrate de estar en el directorio correcto (la raíz del proyecto).
Coverage muy bajo en un archivo que sí testeaste
Causa: Posible import relativo vs absoluto, o el módulo se importa con otro nombre.
Solución: Verifica que los tests importen desde el mismo path que coverage está midiendo. Si usas src como paquete, instálalo en modo editable (pip install -e .) o configura PYTHONPATH para que los imports resuelvan correctamente.
El reporte muestra archivos que no deberían estar (setup, conftest)
Causa: El source incluye carpetas que no querías medir, o omit no está bien definido.
Solución: Ajusta omit en pyproject.toml:
[tool.coverage.run]
omit = [
"tests/*",
"*/conftest.py",
"*/__pycache__/*",
"setup.py",
]
Si conftest.py está en tests/, tests/* ya lo cubre. Si está en la raíz, añádelo explícitamente.
Consejos para integrar pytest-cov en tu flujo
En desarrollo local
Ejecuta coverage cuando acabes una feature o antes de un commit. No necesitas coverage en cada save — sería lento. Un buen momento: después de que tus tests pasen y antes de hacer push.
En pair programming con Claude Code
Cuando Claude Code genera código nuevo, pídele que ejecute coverage: "ejecuta pytest con coverage y dime qué líneas faltan". Así validas que la implementación está cubierta desde el inicio.
Prioriza gaps por impacto
No todas las líneas no cubiertas son iguales. Prioriza:
- Lógica de negocio crítica (validaciones, cálculos)
- Ramas de error (exceptions, fallbacks)
- Código reciente que no tenía tests
Deja para después: __repr__, prints de debug, código legacy que "nunca falla".
Conexión con el Proyecto
El proyecto de este módulo es Suite con 90%+ coverage: recibes código existente (sin tests o con tests mínimos) y debes llegar a ≥90% coverage. El primer paso de ese workflow es medir el estado actual — y eso es exactamente lo que pytest-cov te permite hacer.
Flujo típico:
- Clonas o abres el código existente.
- Ejecutas
pytest --cov=src --cov-report=term-missing tests/. - Ves el reporte: por ejemplo 35% coverage, con
Missingindicando líneas 45-60, 120-135, etc. - Identificas los gaps más críticos (lógica de negocio, error handling).
- Usas Claude Code para generar tests que cubran esos gaps (cápsulas siguientes).
- Vuelves a medir. Repites hasta ≥90%.
Sin pytest-cov no tendrías el mapa. Con él, sabes exactamente qué tests escribir.
Resumen
- ✅ Coverage mide qué porcentaje de tu código se ejecuta durante los tests — es visibilidad, no calidad.
- ✅
pip install pytest-cov+pytest --cov=mymodule --cov-report=term-missing tests/para empezar. - ✅
--cov-report=htmlgenerahtmlcov/index.htmlpara explorar visualmente. - ✅ Stmts, Miss, Cover, Missing: usa Missing para identificar gaps.
- ✅ Branch coverage (
--cov-branch) es más valioso que solo line coverage. - ✅ Configura
source,omityexclude_linesen pyproject.toml o .coveragerc. - ✅ El workflow: medir → identificar gaps → escribir tests → medir de nuevo.
Próxima cápsula: Interpretar coverage — qué significan los números, cuándo preocuparte por una línea no cubierta, y por qué 100% no significa código perfecto.
Recursos Adicionales
- pytest-cov Documentation - Documentación oficial de pytest-cov
- Coverage.py - Herramienta subyacente de medición
- Coverage.py: Configuring - Opciones de configuración
- Martin Fowler: Test Coverage - Perspectiva sobre coverage y sus límites
- Python Testing: pytest-cov - Integración con pytest
- Ned Batchelder: Coverage.py Blog - Entradas sobre coverage.py por su autor
Módulo 5, Cápsula 02 — Testing with Claude Code Guide