Módulo 7: CI Integration con GitHub Actions

GitHub Actions Fundamentals

GitHub Actions Fundamentals

Descripción de la cápsula

Tienes tests que pasan en tu máquina. Pero ¿qué pasa cuando otro developer hace push, cuando mergeas un PR, o cuando simplemente te olvidas de ejecutar pytest antes del commit? Sin automatización, los tests son una promesa que se rompe cuando hay presión.

CI (Continuous Integration) significa que cada cambio — cada push, cada pull request — dispara automáticamente la ejecución de los tests. Si algo falla, el sistema te avisa antes de que el código llegue a producción. Es la red de seguridad que convierte tus tests en algo que realmente importa.

Esta cápsula te introduce a GitHub Actions, el sistema de CI integrado en GitHub. Aprenderás qué es un workflow, un job, un step y un runner; cómo escribir YAML válido; cómo configurar triggers; y tendrás un workflow completo que hace checkout → setup Python → install deps → run pytest desde la primera línea. Al final sabrás cómo pedirle a Claude Code que genere workflows basándose en tu proyecto.


¿Qué es CI y por qué importa?

Definición práctica

Continuous Integration es la práctica de integrar el código en un repositorio compartido frecuentemente, y de ejecutar un conjunto de verificaciones automáticas (tests, lint, build) en cada integración. El objetivo: detectar problemas tan pronto como aparecen, cuando son baratos de arreglar.

Los problemas que CI resuelve

  • Tests que nadie corre: Si los tests son manuales, en deadlines se saltan. CI los corre siempre.
  • "En mi máquina funciona": CI corre en un entorno limpio, reproducible. Si pasa ahí, es más probable que pase en producción.
  • Merge de código roto: Sin CI, alguien puede mergear un PR que rompe tests sin saberlo. Con CI, el PR se bloquea hasta que todo pase.
  • Confianza para refactoring: Si tienes CI, puedes refactorizar con seguridad. Los tests validan que no rompiste nada.

Por qué importa con Claude Code

Claude Code genera código rápido. Puede producir decenas de archivos en minutos. Sin CI, dependes de que tú (o alguien) recuerde ejecutar pytest. Con CI, cada push — sea de humano o de AI — dispara los tests. Si Claude Code generó algo que rompe un test existente, lo sabes en segundos. Es el complemento natural al TDD que practicaste en módulos anteriores.


Conceptos de GitHub Actions

Workflow

Un workflow es un proceso automatizado definido en un archivo YAML. Vive en .github/workflows/. Un repositorio puede tener múltiples workflows (tests, deploy, lint, etc.). Cada workflow se define en un archivo separado.

Job

Un job es un conjunto de steps que se ejecutan en el mismo runner. Los jobs pueden ser secuenciales (uno tras otro) o paralelos (según dependencias). Por defecto, jobs en el mismo workflow corren en paralelo. Cada job corre en un runner limpio — no comparten el filesystem entre jobs a menos que uses artifacts.

Step

Un step es una unidad atómica de trabajo dentro de un job. Puede ser un script que tú escribes o una acción predefinida (como actions/checkout@v4). Los steps se ejecutan en orden. Si un step falla, el job falla y los steps siguientes no se ejecutan.

Runner

Un runner es la máquina que ejecuta los jobs. GitHub provee runners hospedados (Ubuntu, Windows, macOS). Cada job corre en una máquina virtual nueva, con sistema operativo limpio. Por eso instalar dependencias es necesario en cada job.

Diagrama mental

Workflow (archivo .yml)
└── Job: test
    ├── Step 1: checkout código
    ├── Step 2: setup Python
    ├── Step 3: install deps
    └── Step 4: run pytest

Sintaxis YAML para workflows

Estructura básica

name: Workflow name

on:
  push:
  pull_request:

jobs:
  job-id:
    runs-on: ubuntu-latest
    steps:
      - uses: action/name@version
      - run: comando
  • name: Nombre legible que aparece en la UI de GitHub.
  • on: Eventos que disparan el workflow (push, pull_request, workflow_dispatch, etc.).
  • jobs: Diccionario de jobs. Cada job tiene un runs-on (runner) y una lista de steps.

Indentación

YAML es sensible a la indentación. Usa espacios, nunca tabs. Dos espacios por nivel es el estándar. Un error de indentación invalida el archivo.

Steps: uses vs run

  • uses: Ejecuta una acción reutilizable (de GitHub Marketplace o tu repo). Ejemplo: uses: actions/checkout@v4.
  • run: Ejecuta un comando en el shell. Ejemplo: run: pytest tests/. Puedes usar run: | para scripts multilínea.

Referencia rápida

SintaxisUso
on: pushDispara en cualquier push
on: pull_requestDispara en cualquier PR
on: workflow_dispatchPermite ejecución manual desde la UI
runs-on: ubuntu-latestUsa runner Ubuntu
- uses: owner/repo@refEjecuta acción
- run: cmdEjecuta comando

Workflow completo desde cero

Aquí tienes un workflow funcional que hace checkout, configura Python, instala dependencias y ejecuta pytest. Cópialo tal cual en .github/workflows/tests.yml de tu repositorio.

# .github/workflows/tests.yml
name: Tests

on:
  push:
    branches: [main, develop]
  pull_request:
    branches: [main, develop]
  workflow_dispatch:

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
        run: pytest tests/ -v

Explicación paso a paso

1. Checkout: actions/checkout@v4 descarga el código del repositorio al runner. Sin esto, no tienes archivos.

2. Set up Python: actions/setup-python@v5 instala la versión de Python especificada. Sin esto, solo tienes el Python del sistema (que puede ser viejo o inexistente).

3. Install dependencies: Instalamos pip y las dependencias de requirements.txt. El runner no tiene tus paquetes; hay que instalarlos.

4. Run tests: Ejecutamos pytest. El flag -v (verbose) hace que veas cada test en los logs.

Si usas pyproject.toml en lugar de requirements.txt

      - name: Install dependencies
        run: |
          python -m pip install --upgrade pip
          pip install -e ".[dev]"

O si defines dependencias de desarrollo en [project.optional-dependencies]:

[project.optional-dependencies]
dev = ["pytest", "pytest-cov"]

Entonces pip install -e ".[dev]" instala el paquete en modo editable más las dependencias de desarrollo.

Proyecto sin requirements.txt ni pyproject.toml

Si tu proyecto es minimalista:

      - name: Install dependencies
        run: |
          python -m pip install --upgrade pip
          pip install pytest

Triggers: cuándo corre el workflow

on: push

on:
  push:
    branches: [main, develop]

Corre cuando haces push a main o develop. Si omites branches, corre en cualquier push a cualquier branch.

on: pull_request

on:
  pull_request:
    branches: [main, develop]

Corre cuando se abre o actualiza un PR cuyo base branch es main o develop. Es el trigger más útil para CI: cada PR dispara los tests antes del merge.

on: workflow_dispatch

on:
  workflow_dispatch:

Permite ejecutar el workflow manualmente desde la pestaña Actions de GitHub. Útil para debugging o para workflows que no quieres automáticos.

Combinar triggers

on:
  push:
    branches: [main]
  pull_request:
    branches: [main, develop]
  workflow_dispatch:

El workflow corre en push a main, en PRs hacia main o develop, y manualmente.

Excluir archivos (evitar CI en docs)

on:
  push:
    paths-ignore:
      - "**.md"
      - "docs/**"
  pull_request:
    paths-ignore:
      - "**.md"
      - "docs/**"

Si solo cambiaste archivos .md o dentro de docs/, el workflow no corre. Ahorra minutos en repos con mucha documentación.


Estructura de un proyecto con CI

Tu repositorio podría verse así:

my-project/
├── .github/
│   └── workflows/
│       └── tests.yml
├── src/
│   └── my_module/
│       └── __init__.py
├── tests/
│   └── test_my_module.py
├── requirements.txt
└── pyproject.toml

El archivo .github/workflows/tests.yml es todo lo que necesita GitHub para ejecutar tu CI. Al hacer push, GitHub detecta el workflow y lo dispara.


Verificar que funciona

Después de crear el workflow y hacer push:

  1. Ve a tu repositorio en GitHub.
  2. Pestaña Actions.
  3. Verás el workflow "Tests" en la lista. Clic en la ejecución más reciente.
  4. Clic en el job "test" para ver los steps.
  5. Cada step muestra su salida. Si pytest pasa, verás el check verde.

Si algo falla, los logs del step que falló te mostrarán el error (por ejemplo, un test que falla, un import que no existe, o un paquete que no está en requirements.txt).


Cómo leer los logs de GitHub Actions

Cuando abres una ejecución del workflow, ves la lista de jobs. Cada job expande para mostrar los steps. Cada step tiene un ícono: check verde si pasó, X roja si falló.

Estructura de un step que pasa

✓ Checkout repository (2s)
✓ Set up Python (8s)
✓ Install dependencies (15s)
✓ Run tests (12s)

Clic en un step para ver su salida completa. En "Run tests" verás la salida de pytest tal cual: nombres de tests, PASSED/FAILED, y el resumen final.

Cuando un step falla

El job se detiene en el step que falló. Los steps siguientes no se ejecutan. Clic en el step fallido para ver el error. Ejemplos típicos:

  • Test failure: Verás el traceback de pytest con el assert que falló y el archivo/línea.
  • pip install failed: Verás qué paquete no se pudo instalar y el error de pip.
  • ModuleNotFoundError: Aparece en la salida de pytest; indica que falta un import o que el path no está configurado.

Consejos para depurar

  1. Copia el error completo: Desde la primera línea del traceback hasta el final. Ese contexto ayuda a Claude Code o a ti a diagnosticar.
  2. Verifica el step anterior: Si "Run tests" falla con ModuleNotFoundError, puede que "Install dependencies" no instaló todo. Revisa ese step también.
  3. Ejecuta localmente: Reproduce el entorno: python -m venv .venv, activa, pip install -r requirements.txt, pytest tests/. Si pasa local pero falla en CI, busca diferencias (versión de Python, variables de entorno, paths).

Práctica guiada: primer workflow desde cero

Sigue estos pasos para tener tu primer workflow funcionando en menos de 10 minutos.

1. Crear la estructura de directorios

mkdir -p my-ci-project/.github/workflows my-ci-project/tests
cd my-ci-project

2. Crear un test trivial

# tests/test_hello.py
def test_hello():
    assert "hello" in "hello world"

3. Crear requirements.txt

pytest>=7.0.0

4. Crear el workflow

Crea .github/workflows/tests.yml con el contenido del workflow completo que viste arriba (checkout, setup Python 3.12, install, run pytest).

5. Inicializar git y hacer push

git init
git add .
git commit -m "Add CI workflow"
git remote add origin https://github.com/your-username/my-ci-project.git
git push -u origin main

6. Verificar en GitHub

Abre el repo en GitHub → Actions. Deberías ver la ejecución del workflow. Espera a que termine (unos 30-60 segundos). Check verde = éxito.

7. Probar que falla (opcional)

Modifica el test para que falle: assert False. Haz push. Ve a Actions y observa que el workflow muestra X roja. Es la confirmación de que CI está protegiendo tu main.

8. Revertir y continuar

Vuelve a poner el test correcto y haz push. El workflow debería pasar de nuevo.


Permisos y seguridad básicos

Por defecto, los workflows tienen permisos limitados. Puedes restringirlos explícitamente:

jobs:
  test:
    runs-on: ubuntu-latest
    permissions:
      contents: read   # Necesario para checkout
      # actions: read  # Para usar acciones
    steps:
      - uses: actions/checkout@v4
      # ...

Para un workflow de solo tests, contents: read es suficiente. Si más adelante usas acciones que escriben en el repo (por ejemplo, comentar en PRs), necesitarás permisos adicionales. Por ahora, el default suele ser suficiente.


Variables de entorno en el workflow

Puedes definir variables para todos los steps de un job:

jobs:
  test:
    runs-on: ubuntu-latest
    env:
      PYTHONUNBUFFERED: 1
      MY_VAR: value
    steps:
      - uses: actions/checkout@v4
      - run: echo $MY_VAR

PYTHONUNBUFFERED: 1 es útil para ver la salida de pytest en tiempo real en los logs. Para secrets (API keys, tokens), usa GitHub Secrets, no los pongas en texto plano. Lo verás en la cápsula 03.


Claude Code: prompt para generar workflow YAML

Cuando quieres que Claude Code genere o adapte un workflow para tu proyecto, dale contexto claro:

Tengo un proyecto Python con esta estructura:

- src/ con el código
- tests/ con pytest
- requirements.txt con dependencias (pytest, pytest-cov incluidos)

Genera un archivo .github/workflows/tests.yml que:
1. Corra en push a main y en pull_request a main
2. Use Python 3.12
3. Instale de requirements.txt
4. Ejecute pytest tests/ -v

El archivo debe ser ejecutable desde la primera línea.

Claude Code generará el YAML. Revísalo, ajusta la versión de Python o los paths si tu proyecto difiere, y haz push. Si falla, pega el error del log en el chat y pide la corrección.

Variantes útiles

Con pyproject.toml:

Mi proyecto usa pyproject.toml, no requirements.txt. Las dependencias de test están en [project.optional-dependencies] dev = ["pytest"]. Genera el workflow usando pip install -e ".[dev]".

Con múltiples versiones de Python:

Genera el workflow con matrix para Python 3.10, 3.11 y 3.12.

Manual only:

Solo quiero workflow_dispatch para ejecutar tests a mano desde la UI.

Práctica guiada: de cero a CI funcionando

Si quieres reproducir todo desde cero, sigue estos pasos.

Paso 1: Estructura del proyecto

mkdir -p my-ci-project/.github/workflows my-ci-project/tests
cd my-ci-project

Paso 2: Crear un módulo y tests

# src/calc.py (crea también src/__init__.py vacío)
def add(a: float, b: float) -> float:
    return a + b

def multiply(a: float, b: float) -> float:
    return a * b
# tests/test_calc.py
from src.calc import add, multiply

def test_add():
    assert add(2, 3) == 5

def test_multiply():
    assert multiply(3, 4) == 12

Paso 3: requirements.txt

pytest>=7.0.0

Paso 4: Crear el workflow

Crea .github/workflows/tests.yml con el contenido del workflow completo que viste arriba (checkout, setup Python 3.12, install, pytest).

Paso 5: Inicializar git y push

git init
git add .
git commit -m "Add project with CI"
git remote add origin https://github.com/your-username/my-ci-project.git
git branch -M main
git push -u origin main

Paso 6: Verificar en GitHub

Abre tu repo en GitHub, pestaña Actions. Deberías ver una ejecución del workflow "Tests" con check verde. Entra al job y revisa la salida de cada step. En "Run tests" verás la salida de pytest con los dos tests pasando.


Cómo leer los logs en la UI de GitHub

Cuando un workflow corre, GitHub te da una vista jerárquica:

  1. Workflow run: La ejecución completa (un push = una run).
  2. Job: Cada job aparece como un bloque. Si tienes un solo job "test", verás un bloque "test".
  3. Steps: Dentro del job, cada step es expandible. Clic en "Run tests" para ver la salida de pytest.

Qué buscar cuando algo falla

  • Step "Install dependencies" falla: Revisa si requirements.txt existe y si todos los paquetes están disponibles en PyPI. Si usas pip install -e ".[dev]", verifica que pyproject.toml tenga la sección [project.optional-dependencies].
  • Step "Run tests" falla: La salida de pytest aparece ahí. Busca FAILED tests/test_xxx.py::test_nombre. Ese es el test que falló. El traceback te dice la línea y el error.
  • Job nunca corre: Revisa los triggers. Si configuraste branches: [main] y pusheaste a feature/foo, el workflow no se dispara para ese push (a menos que tengas PR a main).

Descargar logs

En la página del workflow run, hay un botón "Download log archive". Útil para compartir logs con tu equipo o con Claude Code para debugging.


Convenciones de naming

  • Archivos de workflow: Usa nombres descriptivos: tests.yml, lint.yml, deploy.yml. Evita nombres genéricos como ci.yml si tienes varios workflows.
  • Jobs: Usa IDs en snake_case: test, lint, build. El name del job (si lo pones) puede ser más legible: Run Tests.
  • Steps: El name del step ayuda mucho en los logs. Sin name, GitHub muestra el comando o la acción, que puede ser poco legible.

Ejemplo:

steps:
  - name: Checkout repository
    uses: actions/checkout@v4
  - name: Set up Python 3.12
    uses: actions/setup-python@v5
    with:
      python-version: "3.12"

Variables de entorno y secrets (preview)

Aunque profundizarás en la cápsula 03, es útil saber que puedes inyectar variables:

jobs:
  test:
    runs-on: ubuntu-latest
    env:
      MY_VAR: value
      API_URL: https://api.example.com
    steps:
      - run: echo $MY_VAR

Para secrets (API keys, tokens): Settings → Secrets and variables → Actions → New repository secret. Luego en el workflow:

env:
  API_KEY: ${{ secrets.API_KEY }}

No hagas echo de secrets en los logs; GitHub los ofusca, pero es mala práctica exponerlos.


Ejercicios

Ejercicio 1: Crear workflow básico (Básico)

Crea un repositorio (o usa uno existente) con tests/test_example.py que tenga un test trivial def test_pass(): assert True. Añade .github/workflows/tests.yml con checkout, setup Python 3.11, install pytest, run pytest. Haz push y verifica que el workflow pase.

Ver solución
# .github/workflows/tests.yml
name: Tests

on:
  push:
  pull_request:

jobs:
  test:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4

      - uses: actions/setup-python@v5
        with:
          python-version: "3.11"

      - run: pip install pytest

      - run: pytest tests/ -v

tests/test_example.py:

def test_pass():
    assert True

Comando: git add .github/workflows/tests.yml tests/test_example.py && git commit -m "Add CI" && git push. Luego abre la pestaña Actions en GitHub.

Ejercicio 2: Añadir workflow_dispatch (Básico)

Modifica el workflow del ejercicio 1 para que también se pueda ejecutar manualmente. Ejecútalo una vez desde la UI de Actions y confirma que corre.

Ver solución

Añade workflow_dispatch al trigger:

on:
  push:
  pull_request:
  workflow_dispatch:

Luego: Actions → Tests → Run workflow → Run workflow.

Ejercicio 3: Trigger solo en main (Intermedio)

Configura el workflow para que corra en push y PR solo cuando el target branch es main. Si tu repo usa master, usa master.

Ver solución
on:
  push:
    branches: [main]
  pull_request:
    branches: [main]

O para master:

on:
  push:
    branches: [master]
  pull_request:
    branches: [master]

Ejercicio 4: Ignorar cambios en README (Intermedio)

Haz que el workflow no corra cuando el único cambio es en README.md o en archivos dentro de docs/.

Ver solución
on:
  push:
    branches: [main]
    paths-ignore:
      - "README.md"
      - "docs/**"
  pull_request:
    branches: [main]
    paths-ignore:
      - "README.md"
      - "docs/**"

Ejercicio 5: Workflow con pyproject.toml (Intermedio)

Tu proyecto tiene pyproject.toml con [project.optional-dependencies] dev = ["pytest", "pytest-cov"]. Escribe el step de instalación correcto para que el workflow instale el paquete en modo editable con dependencias de desarrollo.

Ver solución
      - name: Install dependencies
        run: |
          python -m pip install --upgrade pip
          pip install -e ".[dev]"

Esto instala el paquete actual (desde el directorio donde corre el job, que tras checkout es la raíz) en modo editable, más las dependencias listadas en dev.

Ejercicio 6: Claude Code prompt (Avanzado)

Escribe un prompt para Claude Code que genere un workflow para un proyecto con estructura app/ (código) y tests/, que use pyproject.toml con dependencias en [project.dependencies] y [project.optional-dependencies] test = ["pytest"], y que corra pytest con -v --tb=short.

Ver solución
Genera .github/workflows/tests.yml para este proyecto Python:

- Código en app/
- Tests en tests/
- pyproject.toml con [project.dependencies] y [project.optional-dependencies] test = ["pytest"]
- Quiero que instale con pip install -e ".[test]" y ejecute pytest tests/ -v --tb=short
- Triggers: push y pull_request a main
- Python 3.12

Claude Code generará algo como:

name: Tests
on:
  push:
    branches: [main]
  pull_request:
    branches: [main]
jobs:
  test:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - uses: actions/setup-python@v5
        with:
          python-version: "3.12"
      - run: pip install -e ".[test]"
      - run: pytest tests/ -v --tb=short

Troubleshooting

El workflow no corre al hacer push

Causa: El archivo podría estar en la ruta equivocada o con extensión incorrecta.

Solución: El archivo debe ser .github/workflows/nombre.yml (o .yaml). Verifica que el directorio se llame exactamente workflows y que el archivo esté en ese directorio. Si usas paths-ignore o branches, asegúrate de que tu push coincida con la configuración (ej. push a main si configuraste branches: [main]).

Error: "pip: command not found"

Causa: El step de setup Python no se ejecutó correctamente o usaste pip antes de que Python esté disponible.

Solución: Usa python -m pip en lugar de pip directamente, o asegúrate de que el step actions/setup-python@v5 esté antes del step de install. Ejemplo: run: python -m pip install pytest.

Error: "ModuleNotFoundError: No module named 'X'"

Causa: El paquete no está en requirements.txt (o en las dependencias de pyproject.toml) o el step de instalación no instaló las dependencias de test.

Solución: Añade el paquete a requirements.txt o a las optional dependencies de desarrollo. Si usas pip install -e ., incluye las deps de test: pip install -e ".[dev]" o .[test].

Error de indentación YAML

Causa: Tabs en lugar de espacios, o niveles incorrectos.

Solución: Usa solo espacios. jobs y on están al nivel raíz (sin indentación). Los elementos de steps deben tener la misma indentación (normalmente 6 espacios si usas 2 por nivel). Un validador YAML online te ayuda a localizar el error.

El job pasa pero los tests fallan localmente

Causa: Diferencias de entorno: versión de Python, variables de entorno, paths.

Solución: Revisa que la versión de Python en el workflow coincida con la que usas localmente. Si usas variables de entorno en tests (API keys mockeadas, etc.), configúralas en el workflow con env: en el step o en el job.


Conexión con Proyecto

Este workflow básico es el primer ladrillo del proyecto del Módulo 7: un CI pipeline funcional. En la cápsula 03 añadirás manejo de salida de pytest, artifacts y categorías de tests. En la 04, matrix testing y caching. En la 05, coverage y branch protection. El workflow que creaste aquí será la base que irás ampliando.


Resumen

  • CI ejecuta tests automáticamente en cada push/PR — evita que código roto llegue a main
  • GitHub Actions usa workflows (archivos YAML), jobs, steps y runners
  • Un workflow mínimo: checkout → setup Python → install deps → run pytest
  • Triggers: push, pull_request, workflow_dispatch
  • Claude Code puede generar workflow YAML si le das contexto del proyecto
  • El workflow vive en .github/workflows/ y corre en runners limpios de GitHub

Próxima cápsula: Correr pytest en CI — manejo de salida, artifacts, variables de entorno y problemas comunes.


Recursos Adicionales

  1. GitHub Actions Documentation - Documentación oficial
  2. workflow syntax for GitHub Actions - Sintaxis completa del YAML
  3. Building and testing Python - Guía para Python
  4. actions/checkout - Acción de checkout
  5. actions/setup-python - Acción para Python

Módulo 7, Cápsula 02 — Testing with Claude Code Guide