Módulo 7: CI Integration con GitHub Actions
Coverage en CI y Branch Protection
Coverage en CI y Branch Protection
Descripción de la cápsula
Tienes tests corriendo en CI y reportes de coverage generados localmente. Pero si el coverage no forma parte del pipeline, nadie lo revisa antes del merge. Un PR puede bajar el coverage del 90% al 60% y mergearse sin que nadie se entere. Y sin branch protection, alguien puede mergear incluso cuando los tests fallan.
Esta cápsula cierra el círculo: añadir coverage al pipeline de CI, publicarlo como artifact, mostrarlo como comentario en los PRs, y configurar branch protection para que ningún código roto o con coverage insuficiente llegue a main. Al final tendrás un pipeline profesional completo que combina tests, coverage, reportes en PRs y protecciones de branch. También un prompt para que Claude Code genere toda la configuración.
Añadir coverage al pipeline de CI
Dependencia pytest-cov
Asegúrate de que pytest-cov esté en tus dependencias:
# requirements.txt
pytest>=7.0.0
pytest-cov>=4.0.0
O en pyproject.toml:
[project.optional-dependencies]
dev = ["pytest", "pytest-cov"]
Step de coverage en el workflow
- name: Run tests with coverage
run: |
pytest tests/ -v --cov=src --cov-report=term-missing --cov-report=html --cov-report=xml
--cov=src: Mide el paquetesrc.--cov-report=term-missing: Muestra en terminal las líneas no cubiertas.--cov-report=html: Generahtmlcov/para el reporte HTML.--cov-report=xml: Generacoverage.xml, necesario para muchas acciones que comentan en PRs.
Workflow mínimo con coverage
# .github/workflows/tests.yml
name: Tests
on:
push:
branches: [main]
pull_request:
branches: [main]
jobs:
test:
runs-on: ubuntu-latest
steps:
- name: Checkout repository
uses: actions/checkout@v4
- name: Set up Python
uses: actions/setup-python@v5
with:
python-version: "3.12"
- name: Install dependencies
run: |
python -m pip install --upgrade pip
pip install -r requirements.txt
- name: Run tests with coverage
run: |
pytest tests/ -v --cov=src --cov-report=term-missing --cov-report=html --cov-report=xml
- name: Upload coverage report
uses: actions/upload-artifact@v4
if: always()
with:
name: coverage-report
path: htmlcov/
El artifact coverage-report contiene el directorio htmlcov/. Tras la ejecución, lo descargas, descomprimes y abres index.html para ver el reporte visual.
Publicar coverage como artifact
¿Por qué htmlcov?
El reporte HTML es visual, fácil de explorar y no requiere servicios externos. Subirlo como artifact permite a cualquier reviewer descargar el reporte y revisar qué líneas quedaron sin cubrir después de un PR.
Configuración del upload
- name: Upload coverage report
uses: actions/upload-artifact@v4
if: always() # Sube incluso si tests fallan
with:
name: coverage-report-${{ github.run_number }}
path: htmlcov/
run_number hace que cada ejecución tenga un artifact con nombre único. Si prefieres que se sobrescriba, usa solo name: coverage-report.
Retention de artifacts
Por defecto, los artifacts se guardan 90 días. En Settings → Actions → General puedes cambiar la retención. Para proyectos con muchos PRs, 7-30 días suele ser suficiente.
Coverage como comentario en PRs
La acción coverage-comment
Existen varias acciones que publican el coverage como comentario en el PR. Una popular es py-cov-action o coverage-comment. La idea: después de los tests, la acción lee coverage.xml (o el reporte de coverage) y escribe un comentario en el PR con el porcentaje y un diff del coverage por archivo.
Ejemplo con py-cov-action
- name: Run tests with coverage
run: |
pytest tests/ -v --cov=src --cov-report=xml
- name: Comment coverage on PR
if: github.event_name == 'pull_request'
uses: py-cov-action/python-coverage-report-action@v1
with:
badge: true
fail_below: 80
Esta acción comenta en el PR con un badge de coverage y una tabla. fail_below: 80 hace que el job falle si el coverage está por debajo del 80%, bloqueando el merge.
Alternativa: coverage-comment
- name: Run tests with coverage
run: |
pytest tests/ -v --cov=src --cov-report=xml
- name: Post coverage comment
if: github.event_name == 'pull_request'
uses: py-cov-action/python-coverage-report-action@v1
with:
coverage-files: coverage.xml
Revisa la documentación de la acción que elijas para los parámetros exactos; pueden cambiar entre versiones.
Qué esperar en el comentario
El comentario típicamente incluye:
- Porcentaje total de coverage.
- Tabla por archivo: nombre, cobertura, líneas cubiertas/total.
- Indicación de si el PR sube o baja el coverage respecto a la base (main).
Branch Protection Rules
¿Qué son?
Las branch protection rules son reglas que GitHub aplica antes de permitir un merge. Puedes requerir que ciertos checks pasen, que haya reviews aprobadas, que no haya push directo a main, etc.
Configuración básica
- Repo → Settings → Branches.
- Add branch protection rule.
- Branch name pattern:
main(o*para todas). - Activa:
- Require a pull request before merging
- Require status checks to pass before merging
- Require branches to be up to date before merging (opcional)
- Do not allow bypassing the above settings
Status checks requeridos
En "Require status checks to pass", selecciona el nombre del job o del workflow que debe pasar. Por ejemplo, si tu job se llama test, aparecerá algo como test (o el nombre del workflow). Debes seleccionar al menos uno.
Si no aparece ningún check, es porque el branch aún no ha tenido una ejecución del workflow. Haz un push o abre un PR para que el workflow corra una vez; después el check aparecerá en la lista.
Require reviews
Puedes requerir 1 o más approvals antes del merge. Para equipos pequeños, 1 suele ser suficiente.
No permitir push directo a main
Activa "Do not allow bypassing" para que ni los admins puedan push directo sin pasar por PR. Opcional pero recomendado en equipos.
Umbrales de coverage (fail si está por debajo)
Opción 1: pytest-cov --cov-fail-under
pytest-cov puede fallar si el coverage está por debajo de un umbral:
- name: Run tests with coverage
run: |
pytest tests/ -v --cov=src --cov-report=term-missing --cov-report=xml --cov-fail-under=80
Si el coverage es menor a 80%, pytest retorna exit code 2 (o similar) y el job falla. El PR no podrá mergearse hasta que suba el coverage o se baje el umbral.
Opción 2: coverage.py en setup.cfg o pyproject.toml
[tool.coverage.run]
source = ["src"]
omit = ["tests/*", "*/__pycache__/*"]
[tool.coverage.report]
fail_under = 80
Con eso, pytest --cov=src usará esta configuración y fallará si el coverage está por debajo de 80%.
Opción 3: Acción externa
Algunas acciones de coverage (como py-cov-action) tienen parámetro fail_below. Si lo configuras, la acción falla y el job falla.
Elegir el umbral
- 70-80% para proyectos en crecimiento.
- 90%+ para código crítico o librerías.
- Evita 100% de forma inflexible: suele generar tests triviales para cubrir líneas imposibles o no importantes.
Umbrales por tipo de código
En proyectos grandes puedes querer umbrales distintos por módulo. coverage.py y pytest-cov permiten configuración granular:
# pyproject.toml
[tool.coverage.report]
fail_under = 70
[tool.coverage.report.fail_under]
# Archivos críticos exigen más
"src/auth/*" = 90
"src/payments/*" = 95
No todas las versiones de coverage soportan esto; verifica la documentación. Una alternativa es usar un job separado que verifique coverage solo en src/auth/ con un umbral más alto.
Excluir código que no se testea
[tool.coverage.run]
source = ["src"]
omit = [
"tests/*",
"*/__pycache__/*",
"*/migrations/*",
"src/cli_main.py"
]
Los archivos en omit no cuentan en el reporte. Útil para script de CLI, migraciones de DB, o código generado.
Alternativas a py-cov-action
Además de py-cov-action/python-coverage-report-action, existen otras acciones para comentar coverage en PRs:
- codecov/codecov-action: Sube el reporte a Codecov.io y comenta con un bot. Requiere cuenta en Codecov. Ofrece historial de coverage, gráficos y comparación entre branches.
- coverallsapp/github-action: Similar a Codecov, usa Coveralls. Ambas integran con GitHub y ofrecen dashboards externos.
- dorny/paths-filter: No comenta coverage pero permite que el job de coverage solo corra cuando cambian archivos relevantes (ej. código en
src/), ahorrando tiempo en PRs que solo tocan docs. - EnricoMi/publish-unit-test-result-action: Si generas JUnit XML con coverage, algunas variantes pueden incluir métricas. Revisa la documentación.
Para proyectos que no quieren servicios externos, py-cov-action con coverage.xml local es suficiente. Para equipos que quieren historial y tendencias, Codecov o Coveralls son opciones sólidas.
Estrategia cuando un PR baja el coverage legítimamente
A veces un PR añade código nuevo (features, refactors) que baja temporalmente el coverage: más líneas sin tests aún. Opciones:
- Bajar el umbral temporalmente: No recomendado; el umbral tiende a quedarse bajo.
- Añadir tests en el mismo PR: Lo ideal. El PR no mergea hasta que el coverage suba.
- Excluir archivos nuevos: Con
omiten coverage, puedes excluir un archivo que aún no testeas. Útil para código en desarrollo que se testeará en un PR posterior. Usa con moderación. - pragma: no cover en líneas concretas: Para líneas defensivas o imposibles de testear, usa
# pragma: no cover. No abuses.
La regla práctica: si el PR añade 100 líneas y ninguna está cubierta, el PR no está listo. Pide tests o excluye explícitamente con justificación en el code review.
Pipeline completo: tests + coverage + protección
Aquí tienes un workflow que integra todo lo anterior.
# .github/workflows/tests.yml
name: Tests
on:
push:
branches: [main]
pull_request:
branches: [main]
jobs:
test:
runs-on: ubuntu-latest
steps:
- name: Checkout repository
uses: actions/checkout@v4
- name: Set up Python
uses: actions/setup-python@v5
with:
python-version: "3.12"
- name: Install dependencies
run: |
python -m pip install --upgrade pip
pip install -r requirements.txt
- name: Run tests with coverage
run: |
pytest tests/ -v --cov=src --cov-report=term-missing --cov-report=html --cov-report=xml --cov-fail-under=80
- name: Upload coverage report
uses: actions/upload-artifact@v4
if: always()
with:
name: coverage-report
path: htmlcov/
- name: Comment coverage on PR
if: github.event_name == 'pull_request'
uses: py-cov-action/python-coverage-report-action@v1
with:
badge: true
fail_below: 80
Branch protection para este workflow
- Settings → Branches → Add rule.
- Branch name:
main. - Activa "Require status checks to pass".
- Selecciona el check
test(o el nombre que GitHub asigne al job). - Activa "Require pull request reviews" (1 approval si quieres).
- Save.
A partir de ahí, ningún PR podrá mergearse a main si:
- Los tests fallan.
- El coverage está por debajo del 80%.
- No hay approval (si lo configuraste).
Configuración avanzada de branch protection
Además de "Require status checks" y "Require reviews", puedes activar:
- Require branches to be up to date before merging: Obliga a que el branch esté actualizado con main antes del merge. Evita que un PR desactualizado mergee código que podría conflictuar con cambios recientes.
- Require conversation resolution before merging: Todas las conversaciones en el PR deben estar resueltas (no quedan comentarios sin resolver).
- Require linear history: Obliga a que los commits se mergeen con squash o rebase, evitando merges con múltiples padres.
- Require signed commits: Solo acepta commits firmados. Más estricto, útil para equipos con procesos de seguridad.
- Restrict who can push to matching branches: Solo ciertos usuarios o equipos pueden push a main. El resto solo por PR.
- Allow force pushes: Por defecto desactivado en branches protegidas. Actívalo solo en branches de desarrollo, nunca en main.
- Allow deletions: Normalmente desactivado para main.
Para el proyecto del Módulo 7, "Require status checks" y "Require a pull request" son suficientes. Las opciones avanzadas son útiles cuando el equipo crece o los requisitos de compliance aumentan.
Alternativas a py-cov-action
Además de py-cov-action/python-coverage-report-action, existen otras acciones para comentar coverage en PRs:
orhun/github-action-coverage-badge: Genera un badge de coverage que se puede commitear al repo o usar en README.codecov/codecov-action: Sube el reporte a Codecov (servicio externo) que integra con GitHub y comenta en PRs. Requiere cuenta.coveralls/github-action: Similar a Codecov, usa Coveralls.io.davelosert/video-downloader-action(no aplica) — Ejemplo de nombre genérico. Busca en GitHub Marketplace "coverage comment" para más opciones.
Para proyectos que prefieren no depender de servicios externos, py-cov-action o acciones similares que leen coverage.xml y comentan directamente son la opción más simple. Codecov y Coveralls ofrecen historial de coverage entre commits y dashboards, pero añaden una dependencia externa.
Estrategia cuando un PR baja el coverage legítimamente
A veces un PR añade código nuevo (features, refactors) que aún no está testeado, y el coverage global baja. Opciones:
- Añadir tests en el mismo PR: Lo ideal. El desarrollador escribe tests para el código nuevo antes del merge.
- Bajar temporalmente el umbral: No recomendado como hábito. Crea deuda.
- Usar
[pragma: no cover]en código defensivo: Para líneas que nunca deberían ejecutarse (asserts imposibles, branches de error que no aplican), puedes marcar# pragma: no coverpara excluirlas del reporte. - Omitir archivos o directorios: Si añades un módulo experimental o en WIP, puedes omitirlo temporalmente en
[tool.coverage.run] omithasta que esté listo para producción. - Merge con aprobación de excepción: Algunos equipos permiten que un maintainer haga "bypass" en casos excepcionales, documentando la razón. Requiere configurar "Allow specified actors to bypass" en branch protection.
La regla de oro: el umbral debe ser alcanzable con esfuerzo razonable. Si el 80% bloquea constantemente PRs legítimos, quizá el umbral es muy alto para la fase actual del proyecto. Mejor un 70% sostenible que un 80% que nadie cumple.
Claude Code: generar la configuración CI completa
Prompt para pipeline completo
Genera la configuración completa de CI para mi proyecto Python:
Estructura:
- src/ con el código
- tests/ con pytest
- requirements.txt con pytest, pytest-cov
Necesito:
1. Workflow .github/workflows/tests.yml que corra en push y pull_request a main
2. Python 3.12
3. Tests con coverage (--cov=src, reportes term-missing, html, xml)
4. Umbral de coverage 80% (--cov-fail-under=80)
5. Upload del reporte htmlcov como artifact
6. Comentario de coverage en PRs usando py-cov-action
7. Matrix para Python 3.10, 3.11, 3.12
8. Caching de pip
El archivo debe ser ejecutable y completo desde la primera línea.
Claude Code generará un workflow que integra tests, coverage, artifacts, comentarios en PR, matrix y caching. Revísalo, ajusta la versión de la acción si es necesario, y haz push.
Ajustar después de generar
- Versiones de acciones: Usa
actions/checkout@v4,actions/setup-python@v5,actions/upload-artifact@v4. Claude Code puede usar versiones distintas; verifica en la documentación de cada acción. - Nombre del job: Si usas matrix, el job puede tener un nombre compuesto. En branch protection, selecciona el check correcto.
- fail_below vs --cov-fail-under: Pueden estar en la acción o en pytest. No los dupliques con valores contradictorios.
Práctica guiada: de cero a pipeline con coverage y protección
1. Crear estructura mínima
mkdir -p cov-demo/src cov-demo/tests cov-demo/.github/workflows
cd cov-demo
2. Código y tests
# src/calc.py (y src/__init__.py vacío)
def add(a: float, b: float) -> float:
return a + b
def unmapped(x: int) -> int:
return x * 2 # Sin test, no cubierto
# tests/test_calc.py
from src.calc import add
def test_add():
assert add(2, 3) == 5
3. requirements.txt
pytest>=7.0.0
pytest-cov>=4.0.0
4. Workflow con coverage
Crea .github/workflows/tests.yml con el workflow completo (tests + coverage + artifact + fail-under 80). Con un solo test que cubre add, el coverage será bajo (~50%); el job fallará si usas --cov-fail-under=80. Eso demuestra que el umbral funciona.
5. Subir y abrir PR
git init
git add .
git commit -m "Add project with coverage CI"
git remote add origin https://github.com/your-username/cov-demo.git
git push -u origin main
Crea un branch feature/foo, añade un test para unmapped que suba el coverage, haz push y abre un PR. Verás el workflow correr. Si el coverage sube por encima de 80%, el check pasará.
6. Configurar branch protection
En Settings → Branches → Add rule para main:
- Require status checks: selecciona
test. - Require pull request before merging.
- Save.
Intenta mergear un PR que baje el coverage (por ejemplo, comenta un test). El merge estará bloqueado.
Alternativas para comentar coverage en PRs
Además de py-cov-action, existen otras acciones:
| Acción | Características |
|---|---|
coverage-comment | Comenta en PRs, soporta múltiples formatos, puede actualizar el mismo comentario en cada push |
codecov/codecov-action | Sube a Codecov.io; el comentario incluye enlaces al reporte detallado en su sitio |
romeovs/lcov-report-action | Genera reporte desde lcov; más común en ecosistemas JS/TS |
dorny/test-reporter | Reporter general que también puede mostrar coverage si lo generas en formato compatible |
Para proyectos Python puros, py-cov-action o coverage-comment suelen ser suficientes. Codecov es útil si quieres historial de coverage, tendencias y dashboards centralizados (requiere cuenta en codecov.io).
Estrategia cuando un PR baja el coverage legítimamente
A veces un PR añade código nuevo (features, refactors) que legítimamente baja el porcentaje total porque añade más líneas que tests. Opciones:
- Escribir tests para el código nuevo: Lo ideal. Si el PR añade 100 líneas, añade tests que las cubran.
- Bajar temporalmente el umbral: No recomendable como hábito; erosiona la disciplina.
- Excluir módulos específicos: Si el código es provisional o generado, añádelo a
omiten coverage. Usa con cuidado. - Coverage por archivo modificado: Algunas acciones permiten fallar solo si el coverage de los archivos que el PR toca baja. Más complejo de configurar pero más justo.
- Aprobar como excepción: Si tienes "Allow specified actors to bypass required pull requests", un maintainer puede mergear en casos excepcionales. Úsalo solo cuando hay justificación documentada.
La práctica más sana: trata el umbral como compromiso del equipo. Si se baja, debe ser decisión explícita en una reunión o RFC, no un bypass silencioso.
Resumen visual: flujo del pipeline con coverage
Push o PR a main
│
▼
┌──────────────────┐
│ Checkout código │
└────────┬─────────┘
│
▼
┌──────────────────┐
│ Setup Python │
└────────┬─────────┘
│
▼
┌──────────────────┐
│ Install deps │
└────────┬─────────┘
│
▼
┌──────────────────────────────┐
│ pytest --cov=src │
│ --cov-report=html,xml │
│ --cov-fail-under=80 │
└────────┬─────────────────────┘
│
├── Si tests fallan o coverage < 80% → Job FAILED, merge bloqueado
│
▼ (si pasa)
┌──────────────────┐
│ Upload htmlcov │
│ (artifact) │
└────────┬─────────┘
│
▼ (si es PR)
┌──────────────────┐
│ Comment coverage │
│ en PR │
└──────────────────┘
El branch protection impide merge mientras el check "test" no esté en verde. Eso incluye tests que pasen y coverage por encima del umbral.
Checklist para pipeline CI profesional
Antes de dar por terminado el proyecto del Módulo 7, verifica:
- Tests corren en push y pull_request a main
- Al menos dos versiones de Python en matrix (o una si el proyecto lo declara)
- Cache de pip configurado y funcionando (ver "Cache hit" en logs)
- Coverage generado con
--cov-report=xmlpara comentarios en PR - Umbral de coverage configurado (
--cov-fail-under) - Artifact de htmlcov para descargar el reporte
- Branch protection con "Require status checks" activo
- El check aparece en la lista de branch protection (una vez corrido el workflow)
- Claude Code prompt documentado para regenerar el workflow si cambia la estructura del proyecto
Resumen visual del flujo
Push / PR a main
│
▼
┌──────────────┐
│ Checkout │
└──────┬───────┘
│
▼
┌──────────────┐
│ Setup Python │
└──────┬───────┘
│
▼
┌──────────────────────────┐
│ Install dependencies │
└──────┬──────────────────┘
│
▼
┌─────────────────────────────────┐
│ pytest + coverage (xml, html) │
│ --cov-fail-under=80 │
└──────┬──────────────────────────┘
│
├──▶ Si falla: job failed, merge bloqueado
│
▼
┌──────────────────┐
│ Upload htmlcov │
└──────┬───────────┘
│
▼
┌─────────────────────────────┐
│ Comentar coverage en PR │
│ (si evento = pull_request) │
└─────────────────────────────┘
Branch protection verifica que el job "test" (o tu nombre de job) haya pasado antes de permitir el merge. Sin check verde, el botón Merge está deshabilitado.
Ejercicios
Ejercicio 1: Añadir coverage al workflow (Básico)
Tienes un workflow que solo ejecuta pytest tests/ -v. Añade pytest-cov, el flag --cov=src y los reportes term-missing y html. Verifica que el job genere htmlcov/.
Ver solución
- name: Install dependencies
run: pip install -r requirements.txt
# Asegúrate de que requirements.txt tenga pytest-cov
- name: Run tests with coverage
run: pytest tests/ -v --cov=src --cov-report=term-missing --cov-report=html
- name: Upload coverage report
uses: actions/upload-artifact@v4
if: always()
with:
name: coverage-report
path: htmlcov/
Ejercicio 2: Umbral de coverage (Básico)
Configura el workflow para que falle si el coverage está por debajo del 75%. Ejecuta y verifica que con coverage bajo el job falle.
Ver solución
- name: Run tests with coverage
run: pytest tests/ -v --cov=src --cov-report=xml --cov-fail-under=75
O en pyproject.toml:
[tool.coverage.report]
fail_under = 75
Ejercicio 3: Branch protection (Intermedio)
Configura una regla de branch protection para main que requiera que el check test pase antes del merge. Abre un PR con un test que falle y verifica que no puedas mergear.
Ver solución
- Settings → Branches → Add rule.
- Branch name pattern:
main. - Activa "Require status checks to pass before merging".
- Busca y selecciona el check
test(o "Tests / test"). - Activa "Require a pull request before merging".
- Save.
Crea un branch, rompe un test, push, abre PR. El check estará en rojo y el botón Merge estará deshabilitado.
Ejercicio 4: Comentario de coverage en PR (Intermedio)
Integra una acción que comente el coverage en cada PR. Usa py-cov-action/python-coverage-report-action o similar. Verifica que el comentario aparezca al abrir o actualizar un PR.
Ver solución
- name: Run tests with coverage
run: pytest tests/ -v --cov=src --cov-report=xml
- name: Comment coverage on PR
if: github.event_name == 'pull_request'
uses: py-cov-action/python-coverage-report-action@v1
with:
badge: true
Genera coverage.xml con --cov-report=xml. La acción lo lee y comenta. Revisa la documentación actual de la acción por cambios en parámetros.
Ejercicio 5: Pipeline con matrix y coverage (Avanzado)
Combina matrix (Python 3.10, 3.11, 3.12) con coverage. ¿El comentario de coverage en PR debe ejecutarse una vez o por cada versión de Python? Explica y configura la opción más sensata.
Ver solución
Lo más sensato: un solo reporte de coverage que represente una versión (por ejemplo, 3.12). Si generas coverage en cada job de la matrix, tendrías 3 reportes distintos (pueden variar ligeramente). Para el comentario en PR, usar uno solo evita ruido.
Opciones:
- Job separado de coverage: Un job que depende de
testy solo corre con Python 3.12, generando coverage y comentando. - Un job sin matrix para coverage: Un job
coverageque corre con 3.12, genera el reporte y comenta; el jobtestusa matrix para validar que los tests pasen en todas las versiones.
Ejemplo con job separado:
jobs:
test:
strategy:
matrix:
python-version: ["3.10", "3.11", "3.12"]
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-python@v5
with:
python-version: ${{ matrix.python-version }}
- run: pip install -r requirements.txt
- run: pytest tests/ -v
coverage:
needs: test
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-python@v5
with:
python-version: "3.12"
- run: pip install -r requirements.txt
- run: pytest tests/ --cov=src --cov-report=xml
- uses: py-cov-action/python-coverage-report-action@v1
if: github.event_name == 'pull_request'
with:
badge: true
Ejercicio 6: Prompt para Claude Code (Avanzado)
Escribe un prompt completo para Claude Code que genere un workflow con: tests en matrix 3.10/3.11/3.12, caching de pip, coverage con umbral 85%, artifact de htmlcov, comentario en PR, y branch protection (solo la explicación de qué configurar manualmente en Settings).
Ver solución
Genera el archivo .github/workflows/tests.yml para un proyecto Python con:
- Código en src/, tests en tests/
- requirements.txt con pytest, pytest-cov
- Matrix para Python 3.10, 3.11, 3.12
- Caching de dependencias pip usando actions/cache con key basada en hash de requirements.txt
- Tests con coverage --cov=src, reportes xml y html
- Umbral --cov-fail-under=85
- Upload de htmlcov como artifact
- Comentario de coverage en PRs con py-cov-action (solo en eventos pull_request)
- Triggers: push y pull_request a main
Incluye al final un comentario con instrucciones para configurar branch protection en GitHub Settings → Branches: require status checks "test" (o el nombre del job), require PR antes de merge.
Claude Code generará el YAML. Las instrucciones de branch protection son para hacerlas manualmente en la UI, ya que no se puede configurar por YAML.
Troubleshooting
El comentario de coverage no aparece en el PR
Causa: La acción puede requerir coverage.xml en un path específico, o el evento no es pull_request, o la acción falla silenciosamente.
Solución: Verifica que --cov-report=xml genere coverage.xml en la raíz. Revisa que el step tenga if: github.event_name == 'pull_request'. Mira los logs del step de la acción para ver errores.
Branch protection no muestra el check
Causa: El workflow no ha corrido aún en ese branch, o el nombre del check no coincide.
Solución: Haz push o abre un PR para que el workflow corra. Después de una ejecución, el check aparecerá en Settings → Branches al configurar la regla. El nombre suele ser el del job (ej. test) o "WorkflowName / JobName".
Coverage en 0% o "No data to report"
Causa: El path --cov=src no coincide con lo que importas en los tests, o el directorio no existe.
Solución: Si importas from src.calc import add, el source debe ser src. Ejecuta desde la raíz. Verifica que src/ exista y tenga __init__.py si es paquete. Si usas app/ en lugar de src/, usa --cov=app.
fail_under hace fallar aunque el coverage suba
Causa: Puede haber configuración contradictoria (p. ej. omit que excluye demasiado, o branch coverage que cuenta distinto).
Solución: Revisa [tool.coverage.run] y [tool.coverage.report] en pyproject.toml. Asegúrate de que source y omit sean correctos. Ejecuta localmente pytest --cov=src --cov-fail-under=80 para reproducir.
La acción py-cov-action falla
Causa: Cambios en la API de la acción, o coverage.xml no existe o está en otro path.
Solución: Revisa la documentación actual de la acción. Algunas versiones usan coverage-files en lugar de auto-detección. Especifica el path explícitamente si es necesario.
Estrategia cuando un PR baja el coverage legítimamente
A veces añades código nuevo (ej. un endpoint) que baja el porcentaje total porque aún no tiene tests. Opciones:
- Añadir tests en el mismo PR: La opción ideal. El PR no mergea hasta que el coverage suba.
- Bajar temporalmente el umbral: No recomendado como norma; genera deuda técnica.
- Excluir archivos nuevos con pragma: En el código nuevo,
# pragma: no coveren las líneas que temporalmente no testeas. coverage las ignora. Quita el pragma cuando añadas los tests en un PR posterior. - Branch protection con excepciones: Los admins pueden tener permiso para bypass en emergencias. Usa con criterio; no lo conviertas en costumbre.
Conexión con Proyecto
Este pipeline con coverage y branch protection es el entregable del proyecto del Módulo 7. Junto con las cápsulas 02-04 tienes: workflow básico, pytest en CI, matrix y caching, y ahora coverage + protección. El proyecto final del Módulo 8 requiere este CI como parte de la entrega.
Resumen
- Añade
pytest-covy--cov=src --cov-report=term-missing --cov-report=html --cov-report=xmlal step de pytest - Publica
htmlcov/como artifact para revisar el reporte - Usa acciones como
py-cov-actionpara comentar coverage en PRs - Configura branch protection para exigir que los checks pasen antes del merge
- Usa
--cov-fail-underofail_underen coverage para bloquear PRs con coverage bajo - Claude Code puede generar el workflow completo si le das contexto detallado
Próxima cápsula: Proyecto — CI pipeline funcional completo.
Recursos Adicionales
- pytest-cov documentation - Opciones de pytest-cov
- coverage.py configuration - Configuración de coverage
- GitHub Branch protection - Documentación de branch protection
- py-cov-action - Acción para comentar coverage en PRs
- actions/upload-artifact - Subir artifacts
- GitHub Status checks - Status checks y branch protection
Módulo 7, Cápsula 05 — Testing with Claude Code Guide