Módulo 7: CI Integration con GitHub Actions
Matrix Testing y Caching
Matrix Testing y Caching
Descripción de la cápsula
Tu workflow corre en una sola versión de Python. ¿Pero tu proyecto soporta 3.10, 3.11 y 3.12? ¿Quieres asegurarte de que el código funcione en todas? Y cada vez que el workflow corre, instala pip, crea el entorno, descarga dependencias desde cero. Eso puede sumar minutos. Si cambias una línea de código y solo ejecutas tests, ¿por qué reinstalar todo?
Esta cápsula cubre matrix testing (ejecutar el mismo job en múltiples versiones de Python en paralelo) y caching (guardar dependencias pip entre ejecuciones para builds más rápidos). Aprenderás la estrategia matrix, fail-fast, cómo funciona el cache de pip, la clave basada en requirements.txt, una comparación antes/después de caching, y un workflow completo que combina matrix y cache.
¿Qué es Matrix Testing?
Definición
Matrix strategy en GitHub Actions permite ejecutar un job múltiples veces, variando uno o más parámetros. Cada combinación corre en un runner independiente, en paralelo. Para Python, el parámetro típico es la versión: 3.10, 3.11, 3.12.
Por qué importa
- Compatibilidad: Aseguras que el código funcione en las versiones que tu proyecto declara soportar.
- Detección temprana: Si usas una API deprecada en 3.10 que aún funciona pero fue removida en 3.12, el matrix lo detecta.
- Confianza para upgrades: Cuando decides subir el mínimo requerido de 3.10 a 3.11, ya sabes que 3.11 pasa.
Cuándo usarlo
- Proyectos que soportan múltiples versiones de Python (común en librerías).
- Proyectos que quieren validar en la versión más reciente antes de adoptarla.
- Cuando el tiempo de CI no es crítico (cada versión añade ~1-2 min por ejecución, pero corren en paralelo).
Sintaxis de Matrix
Ejemplo básico
jobs:
test:
runs-on: ubuntu-latest
strategy:
matrix:
python-version: ["3.10", "3.11", "3.12"]
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
Cada valor en python-version genera una ejecución del job. Tres versiones = tres ejecuciones en paralelo. matrix.python-version se sustituye en cada run por "3.10", "3.11" o "3.12".
Múltiples dimensiones
strategy:
matrix:
python-version: ["3.10", "3.11"]
os: [ubuntu-latest, macos-latest]
Eso genera 2 × 2 = 4 combinaciones: (3.10, ubuntu), (3.10, macos), (3.11, ubuntu), (3.11, macos). Cada una corre en paralelo. Útil para proyectos multi-plataforma.
Excluir combinaciones
strategy:
matrix:
python-version: ["3.10", "3.11", "3.12"]
os: [ubuntu-latest, macos-latest]
exclude:
- python-version: "3.10"
os: macos-latest
Excluye (3.10, macos). Útil si una combinación es problemática o innecesaria.
Incluir combinaciones adicionales
strategy:
matrix:
python-version: ["3.10", "3.11"]
include:
- python-version: "3.12"
experimental: true
Añade una combinación extra. experimental sería un parámetro que puedes usar en los steps si lo necesitas.
fail-fast
Comportamiento por defecto
Por defecto, fail-fast: true en una estrategia matrix: si una combinación falla, las demás se cancelan. Ahorra minutos cuando ya sabes que hay un fallo.
Desactivar fail-fast
strategy:
fail-fast: false
matrix:
python-version: ["3.10", "3.11", "3.12"]
Con fail-fast: false, si (3.10) falla, (3.11) y (3.12) siguen corriendo. Verás el resultado completo de todas las versiones. Útil para saber qué versiones específicamente fallan.
Cuándo usar cada uno
- fail-fast: true (default): CI rápido; cuando una versión falla, el resto no aporta mucho. Bueno para desarrollo iterativo.
- fail-fast: false: Cuando necesitas el reporte completo (ej. release que debe soportar todas las versiones).
Caching de dependencias pip
El problema
Cada vez que el workflow corre, el runner es nuevo. No tiene pip cache, no tiene paquetes instalados. pip install -r requirements.txt descarga todo desde PyPI. Con 50 paquetes, eso puede ser 1-2 minutos por ejecución. Si el workflow corre 10 veces al día, son 10-20 minutos de downloads repetidos.
La solución: cache
GitHub Actions permite cachear directorios entre ejecuciones. La acción actions/cache@v4 guarda una carpeta (por ejemplo, el cache de pip) bajo una clave. Si la clave coincide en la siguiente ejecución, restaura el cache. pip install entonces reutiliza paquetes ya descargados y solo baja lo nuevo.
Cómo funciona la clave del cache
La clave típica se basa en:
- Sistema operativo del runner (ubuntu, macos, etc.)
- Versión de Python
- Contenido o hash de
requirements.txt
Si cambias requirements.txt, la clave cambia, el cache no coincide, y pip instala de nuevo. Eso es correcto: dependencias nuevas requieren install fresco. Si no cambias requirements, el cache se reutiliza.
Hash de requirements.txt
- name: Cache pip
uses: actions/cache@v4
with:
path: ~/.cache/pip
key: ${{ runner.os }}-pip-${{ hashFiles('requirements.txt') }}
restore-keys: |
${{ runner.os }}-pip-
- path: Dónde pip guarda su cache por defecto en Linux (
~/.cache/pip). - key: Clave única.
runner.osdiferencia ubuntu de macos.hashFiles('requirements.txt')genera un hash del archivo. Si requirements.txt cambia, el hash cambia, nueva clave, nuevo cache. - restore-keys: Si no hay cache exacto, usa el prefijo
ubuntu-pip-para restaurar cualquier cache de pip en ubuntu. Puede ser un cache de una versión anterior de requirements; pip instalará solo lo que falte.
Ubicación del cache en distintos OS
| OS | Path típico |
|---|---|
| Ubuntu | ~/.cache/pip |
| macOS | ~/Library/Caches/pip |
| Windows | ~\AppData\Local\pip\Cache |
La acción actions/setup-python con cache: 'pip' maneja esto automáticamente. Verás esa opción más abajo.
Usar actions/setup-python con cache
La forma más simple de cachear pip es usar el cache integrado de actions/setup-python:
- name: Set up Python
uses: actions/setup-python@v5
with:
python-version: "3.12"
cache: "pip"
cache-dependency-path: "requirements.txt"
Con cache: "pip" y cache-dependency-path, setup-python cachea automáticamente según requirements.txt. No necesitas actions/cache manual para pip.
Con pyproject.toml
- name: Set up Python
uses: actions/setup-python@v5
with:
python-version: "3.12"
cache: "pip"
cache-dependency-path: "pyproject.toml"
Usa el hash de pyproject.toml para la clave. Si tienes requirements-dev.txt aparte, puedes usar un string con varios paths:
cache-dependency-path: |
requirements.txt
requirements-dev.txt
Fallback: cache manual
Si tu proyecto tiene una estructura de dependencias más compleja, usa actions/cache manual:
- name: Cache pip
uses: actions/cache@v4
with:
path: ~/.cache/pip
key: ${{ runner.os }}-py${{ matrix.python-version }}-pip-${{ hashFiles('requirements.txt') }}
restore-keys: |
${{ runner.os }}-py${{ matrix.python-version }}-pip-
- name: Install dependencies
run: pip install -r requirements.txt
Antes y después del caching
Sin cache (típico)
Set up Python: 8s
Install dependencies: 90s ← descarga todo
Run tests: 25s
Total: ~2 min
Con cache (primera vez, cache miss)
Set up Python: 8s
Cache pip: 2s (restore intent, miss)
Install dependencies: 85s
Run tests: 25s
Total: ~2 min (similar, pero guarda cache para después)
Con cache (segunda vez, cache hit)
Set up Python: 8s
Cache pip: 15s (restore, hit)
Install dependencies: 12s ← la mayoría ya está en cache
Run tests: 25s
Total: ~1 min
Puedes ahorrar 50-70% del tiempo de "Install dependencies" cuando el cache da hit. En workflows que corren muchas veces al día, eso se traduce en minutos u horas ahorradas.
Cuándo no usar matrix
Matrix no siempre es la opción correcta:
- Proyectos que solo soportan una versión: Si tu
pyproject.tomldeclararequires-python = ">=3.12", no necesitas matrix con 3.10 o 3.11. Un solo job con 3.12 basta. - CI muy lento: Tres jobs consumen 3x minutos de tiempo de runner (aunque corran en paralelo, cada uno usa su propio runner). Si tu plan de GitHub tiene límites de minutos, matrix puede agotarlos rápido.
- Dependencias incompatibles: Algunas librerías no soportan versiones antiguas de Python. Si 3.10 falla por una dependencia que solo soporta 3.11+, excluye 3.10 o no uses matrix.
- Proyectos internos: Si todo el equipo usa 3.12 y el proyecto no se distribuye, validar en 3.10 puede ser overkill. Un solo job es más simple.
Métricas reales: ejemplo con proyecto típico
Un proyecto Python con ~30 dependencias (FastAPI, SQLAlchemy, pytest, etc.):
| Escenario | Set up Python | Install deps | Run tests | Total |
|---|---|---|---|---|
| Sin cache, 1ª vez | 8s | 95s | 28s | ~2m 10s |
| Con cache, 1ª vez (miss) | 8s | 92s | 28s | ~2m 08s |
| Con cache, 2ª vez (hit) | 8s | 14s | 28s | ~50s |
| Matrix 3 versiones, sin cache | 3 × ~2m | - | - | ~2m (paralelo) |
| Matrix 3 versiones, con cache hit | 3 × ~50s | - | - | ~50s (paralelo) |
El ahorro con cache es de ~1 minuto por run. En 50 pushes al día, son 50 minutos menos de espera. El matrix triplica el consumo de minutos de Actions pero da confianza en compatibilidad multi-versión.
Workflow completo: matrix + caching
# .github/workflows/tests.yml
name: Tests
on:
push:
branches: [main]
pull_request:
branches: [main]
jobs:
test:
runs-on: ubuntu-latest
strategy:
fail-fast: false
matrix:
python-version: ["3.10", "3.11", "3.12"]
steps:
- name: Checkout repository
uses: actions/checkout@v4
- name: Set up Python ${{ matrix.python-version }}
uses: actions/setup-python@v5
with:
python-version: ${{ matrix.python-version }}
cache: "pip"
cache-dependency-path: "requirements.txt"
- name: Install dependencies
run: |
python -m pip install --upgrade pip
pip install -r requirements.txt
- name: Run tests
run: pytest tests/ -v --tb=short
Con pyproject.toml
- name: Set up Python ${{ matrix.python-version }}
uses: actions/setup-python@v5
with:
python-version: ${{ matrix.python-version }}
cache: "pip"
cache-dependency-path: "pyproject.toml"
- name: Install dependencies
run: pip install -e ".[dev]"
Práctica guiada: añadir matrix y cache a un workflow existente
Sigue estos pasos para transformar un workflow básico en uno con matrix y caching.
1. Tener un workflow que funcione
Asegúrate de tener checkout, setup Python, install, run pytest. Que pase en al menos una versión.
2. Añadir la estrategia matrix
Envuelve el job con strategy y matrix.python-version:
jobs:
test:
runs-on: ubuntu-latest
strategy:
matrix:
python-version: ["3.10", "3.11", "3.12"]
3. Usar matrix en setup-python
Cambia el step de setup para usar ${{ matrix.python-version }}:
- uses: actions/setup-python@v5
with:
python-version: ${{ matrix.python-version }}
4. Añadir cache a setup-python
Añade cache y cache-dependency-path:
- uses: actions/setup-python@v5
with:
python-version: ${{ matrix.python-version }}
cache: "pip"
cache-dependency-path: "requirements.txt"
5. Push y verificar
Haz push. En Actions verás tres jobs en paralelo (3.10, 3.11, 3.12). La primera vez el cache será miss; la segunda, deberías ver "Cache hit" en el step de setup y un tiempo de install menor.
Comparación de tiempos: ejemplo real
En un proyecto con ~30 dependencias en requirements.txt:
| Fase | Sin cache | Con cache (1ª vez) | Con cache (2ª+) |
|---|---|---|---|
| Checkout | 5s | 5s | 5s |
| Setup Python | 10s | 10s | 10s |
| Cache (restore) | - | 3s (miss) | 12s (hit) |
| Install deps | 85s | 82s | 18s |
| Run tests | 28s | 28s | 28s |
| Total | ~2.1 min | ~2.1 min | ~1.2 min |
El ahorro es de ~45% en ejecuciones sucesivas sin cambios en requirements. Con matrix (3 versiones), son 3 jobs en paralelo; cada uno con su propio cache. El tiempo total del workflow es el del job más lento, no la suma.
Cuándo no usar matrix
- Proyecto single-versión: Si solo soportas Python 3.12 y no planeas cambiar, matrix añade complejidad sin beneficio.
- CI muy lento: Si ya tarda 5+ minutos, añadir 2 versiones más puede no valer la pena. Valida al menos en la versión mínima y la máxima que soportas.
- Dependencias incompatibles: Si una librería crítica no soporta 3.10, exclúyela del matrix o no uses esa versión.
Orden de los steps: cache antes de install
El orden correcto es:
- Checkout (para tener requirements.txt)
- Cache (restore) — si existe, restaura ~/.cache/pip
- Setup Python
- Install — pip usa el cache restaurado y solo baja lo nuevo
Si pones cache después de install, el restore no tiene efecto en esa ejecución. Si usas actions/setup-python con cache: "pip", setup-python hace el cache por ti; no necesitas un step separado.
Flujo de decisión: ¿usar matrix?
¿Tu proyecto soporta múltiples versiones de Python?
├── No (solo 3.12) → Un job con python-version: "3.12"
└── Sí
├── ¿Es una librería o paquete distribuible? → Sí → Matrix 3.10, 3.11, 3.12
└── ¿Es una app interna?
├── ¿Todo el equipo usa la misma versión? → Sí → Un solo job
└── ¿Quieres preparar upgrade futuro? → Matrix para validar
Cache con múltiples archivos de dependencias
Si tienes requirements.txt, requirements-dev.txt y opcionalmente requirements-prod.txt, la clave del cache debe reflejar todos los que usa el job:
- uses: actions/setup-python@v5
with:
python-version: "3.12"
cache: "pip"
cache-dependency-path: |
requirements.txt
requirements-dev.txt
O con cache manual:
key: ${{ runner.os }}-pip-${{ hashFiles('requirements.txt', 'requirements-dev.txt') }}
Si solo incluyes uno, cambios en el otro no invalidarán el cache y podrías tener dependencias desactualizadas.
Errores frecuentes al configurar matrix
"Invalid versión" en setup-python
Causa: Escribiste "3.12" pero la sintaxis esperada puede variar. Usa strings con el formato exacto: "3.10", "3.11", "3.12". No uses 3.12 sin comillas si falla (aunque a veces YAML lo acepta).
Matrix genera 0 jobs
Causa: Todas las combinaciones fueron excluidas con exclude, o hay un typo en la definición del matrix.
Solución: Revisa que matrix.python-version tenga al menos un valor. Si usas include sin matrix base, puede haber comportamientos inesperados. Prueba con un matrix mínimo primero.
Diferentes versiones de Python, mismo cache key
Causa: La clave no incluye matrix.python-version. Python 3.10 y 3.12 pueden tener wheels distintos; compartir cache puede causar incompatibilidades.
Solución: Incluye la versión en la clave cuando uses actions/cache manual: ${{ runner.os }}-py${{ matrix.python-version }}-pip-${{ hashFiles('requirements.txt') }}. Con setup-python y cache: "pip", la acción maneja esto automáticamente por versión de Python.
Consejos para optimizar tiempos de CI
- Cache siempre que tengas 5+ dependencias: Con proyectos pequeños, el ahorro es mínimo (10-15s). Con 20+ paquetes, el ahorro puede ser de 1-2 min por ejecución. En workflows que corren decenas de veces al día, eso suma.
- Usa fail-fast: true durante desarrollo iterativo: Cuando estás arreglando un fallo y una versión ya falló, cancelar el resto ahorra minutos. Cambia a
fail-fast: falsecuando prepares un release y quieras el reporte completo. - paths-ignore para archivos que no afectan tests: Si solo cambias
README.md,docs/o.gitignore, evita correr CI. Configurapaths-ignoreen los triggers. Cada run que evitas ahorra minutos del plan de Actions. - Paraleliza jobs con cautela: Si unit e integration pueden correr en paralelo, hazlo. Pero si integration necesita el build de unit, usa
needspara no duplicar trabajo. - Revisa el consumo de minutos: En GitHub Settings → Billing → Plans and usage puedes ver cuántos minutos consumen tus workflows. Con plan gratuito (2000 min/mes para privados), matrix de 3 versiones puede agotarlos rápido en repos muy activos.
Resumen de opciones de cache para pip
| Método | Ventajas | Cuándo usarlo |
|---|---|---|
actions/setup-python con cache: "pip" | Simple, un solo step, maneja path por OS | Proyecto estándar con requirements.txt o pyproject.toml |
actions/cache manual | Control total de la clave, múltiples paths | Dependencias en varios archivos o estructura no estándar |
| Sin cache | Sin complejidad, siempre install fresco | Proyecto muy pequeño (<5 deps) o cuando el cache da problemas |
Ejemplo numérico: proyecto con 15 dependencias
Supongamos un proyecto con requirements.txt que incluye FastAPI, SQLAlchemy, pytest, pytest-cov, httpx y unas 10 dependencias más (unas 15 en total).
Sin cache:
- Install dependencies: ~55 segundos
- Run tests: ~20 segundos
- Total por job: ~1 min 20 s
Con cache (segunda ejecución, cache hit):
- Setup Python + restore cache: ~18 segundos
- Install dependencies: ~12 segundos (la mayoría desde cache)
- Run tests: ~20 segundos
- Total por job: ~50 segundos
Ahorro: ~30 segundos por job. Con matrix de 3 versiones son 3 jobs; cada uno ahorra ~30 s. En 20 pushes al día, son 10 minutos menos de tiempo de runner. Si tu plan tiene límite de minutos, el cache te permite más ejecuciones dentro del límite.
Cuándo el cache no ayuda tanto: Proyectos con 2-3 dependencias (pytest, requests) instalan en 10-15 s de todas formas. El ahorro puede ser solo 5-8 s. Sigue siendo útil configurarlo; no cuesta nada y en proyectos que crecen el beneficio aparece.
Verificar que el cache funciona
Tras configurar el cache, haz dos pushes seguidos sin cambiar requirements.txt:
- Primera ejecución: En el step "Set up Python" (o "Cache pip") deberías ver algo como "Cache not found" o "Cache restored from key: ..." con estado "miss".
- Segunda ejecución: Deberías ver "Cache hit" con la misma clave. El step de install será notablemente más rápido.
Si siempre ves "miss", revisa que la clave sea estable. Si usas hashFiles('requirements.txt'), no debe cambiar entre pushes si no modificaste el archivo. Si la clave incluye algo aleatorio (fecha, run_id), cada ejecución tendrá clave distinta y nunca dará hit.
Referencia rápida: sintaxis matrix y cache
Para consulta rápida al implementar:
Matrix básico:
strategy:
matrix:
python-version: ["3.10", "3.11", "3.12"]
# Usar en steps: ${{ matrix.python-version }}
Matrix con exclude:
strategy:
matrix:
python-version: ["3.10", "3.11", "3.12"]
os: [ubuntu-latest, macos-latest]
exclude:
- python-version: "3.10"
os: macos-latest
Cache con setup-python:
- uses: actions/setup-python@v5
with:
python-version: ${{ matrix.python-version }}
cache: "pip"
cache-dependency-path: "requirements.txt"
fail-fast:
strategy:
fail-fast: false # Todos los jobs corren aunque uno falle
matrix:
python-version: ["3.10", "3.11", "3.12"]
Ejercicios
Ejercicio 1: Matrix básico (Básico)
Añade una estrategia matrix al workflow de tests para que corra en Python 3.10 y 3.11. Haz push y verifica que ambos jobs aparezcan en Actions.
Ver solución
jobs:
test:
runs-on: ubuntu-latest
strategy:
matrix:
python-version: ["3.10", "3.11"]
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
En Actions verás "test (3.10)" y "test (3.11)" como jobs separados.
Ejercicio 2: fail-fast false (Básico)
Configura fail-fast: false en el matrix. Introduce un test que falle solo en Python 3.10 (por ejemplo, usando sintaxis que existe en 3.11+ pero no en 3.10). Verifica que los otros jobs sigan corriendo cuando 3.10 falla.
Ver solución
strategy:
fail-fast: false
matrix:
python-version: ["3.10", "3.11", "3.12"]
Para hacer fallar solo 3.10, podrías usar algo que cambió entre versiones. Ejemplo simplificado: un test que hace assert sys.version_info >= (3, 11) — fallará en 3.10. O usa match (disponible desde 3.10) de forma que un bug en tu código solo se manifieste en 3.10. La idea es ver que con fail-fast: false, 3.11 y 3.12 completan aunque 3.10 falle.
Ejercicio 3: Cache con setup-python (Intermedio)
Configura el cache de pip usando la opción integrada de actions/setup-python. Ejecuta el workflow dos veces seguidas sin cambiar requirements.txt. Compara el tiempo del step "Install dependencies" entre la primera y la segunda ejecución.
Ver solución
- uses: actions/setup-python@v5
with:
python-version: "3.12"
cache: "pip"
cache-dependency-path: "requirements.txt"
La primera vez: Cache miss o "Cache not found". Install puede tardar 60-90s. La segunda vez: Cache hit. Install puede bajar a 10-25s. La diferencia depende del tamaño de requirements.txt.
Ejercicio 4: Cache manual con hash (Intermedio)
En lugar de cache: "pip" en setup-python, usa actions/cache@v4 manualmente. Configura la clave con hashFiles('requirements.txt') y el path ~/.cache/pip. Verifica que el cache funcione.
Ver solución
- name: Cache pip
uses: actions/cache@v4
with:
path: ~/.cache/pip
key: ${{ runner.os }}-pip-${{ hashFiles('requirements.txt') }}
restore-keys: |
${{ runner.os }}-pip-
- uses: actions/setup-python@v5
with:
python-version: "3.12"
- run: pip install -r requirements.txt
El orden importa: cache antes de setup-python, e install después. hashFiles genera un hash del contenido de requirements.txt. Si lo cambias, nueva clave, nuevo cache.
Ejercicio 5: Matrix con pyproject.toml (Intermedio)
Tu proyecto usa pyproject.toml con [project.optional-dependencies] dev = ["pytest", "pytest-cov"]. Configura matrix para 3.10, 3.11, 3.12 y cache usando cache-dependency-path: "pyproject.toml". El step de install debe usar pip install -e ".[dev]".
Ver solución
jobs:
test:
runs-on: ubuntu-latest
strategy:
matrix:
python-version: ["3.10", "3.11", "3.12"]
steps:
- uses: actions/checkout@v4
- uses: actions/setup-python@v5
with:
python-version: ${{ matrix.python-version }}
cache: "pip"
cache-dependency-path: "pyproject.toml"
- run: pip install -e ".[dev]"
- run: pytest tests/ -v
Ejercicio 6: Excluir una combinación (Avanzado)
Tienes matrix con python 3.10, 3.11, 3.12 y os ubuntu, macos. Excluye la combinación (3.10, macos) porque en tu proyecto esa combinación tiene problemas conocidos.
Ver solución
strategy:
matrix:
python-version: ["3.10", "3.11", "3.12"]
os: [ubuntu-latest, macos-latest]
exclude:
- python-version: "3.10"
os: macos-latest
jobs:
test:
runs-on: ${{ matrix.os }}
# ...
Así tendrás 5 jobs en lugar de 6: (3.10, ubuntu), (3.11, ubuntu), (3.11, macos), (3.12, ubuntu), (3.12, macos).
Troubleshooting
Cache siempre miss
Causa: La clave cambia en cada ejecución (por ejemplo, incluye un timestamp) o el path es incorrecto.
Solución: Usa una clave estable: hashFiles('requirements.txt') es determinista. Si requirements no cambia, la clave es la misma. Verifica que path coincida con donde pip guarda el cache. En Ubuntu es ~/.cache/pip.
Cache corrupto o instalación incorrecta
Causa: A veces un cache restaurado puede causar instalaciones inconsistentes (paquete actualizado en PyPI, cache viejo).
Solución: Puedes borrar el cache desde la UI de GitHub: Settings → Actions → Caches → Delete. O añadir un step que fuerce reinstall cuando lo necesites. La mayoría del tiempo el cache es fiable; si ves fallos extraños, prueba sin cache una vez.
Matrix job falla en una versión
Causa: Código o dependencia incompatible con esa versión de Python.
Solución: Revisa el error en el log de ese job. Puede ser sintaxis no soportada (ej. match en 3.9), una dependencia que no soporta esa versión, o un test que asume comportamiento de una versión nueva. Corrige el código o excluye esa combinación del matrix si no la soportas.
Tiempo de CI no baja con cache
Causa: El cache no se está usando (miss), o requirements.txt es pequeño y pip ya era rápido.
Solución: Verifica que el step de cache muestre "Cache hit" en la segunda ejecución. Si siempre es miss, revisa la clave. Con pocas dependencias (2-3), el ahorro puede ser mínimo (10-20s). Con muchas (20+), el ahorro suele ser significativo (1-2 min).
Diferentes versiones de Python comparten cache
Causa: La clave del cache no incluye la versión de Python.
Solución: Incluye matrix.python-version en la clave cuando uses matrix: ${{ runner.os }}-py${{ matrix.python-version }}-pip-${{ hashFiles('requirements.txt') }}. Cada versión de Python tiene su propio cache.
El matrix genera jobs con nombres largos
Causa: Con matrix, GitHub muestra "test (3.10)", "test (3.11)", etc. En branch protection, el check puede llamarse "test (3.10)" o simplemente "test" dependiendo de la configuración.
Solución: Para branch protection con matrix, normalmente necesitas que todos los jobs del matrix pasen. Configura "Require status checks" y selecciona todos los checks del job (o el check padre si existe). GitHub suele exponer un check por combinación de matrix.
Práctica guiada extendida: medir el impacto del cache
Para ver el efecto real del cache en tu proyecto:
1. Desactiva el cache temporalmente
Comenta el cache y cache-dependency-path en setup-python. Haz push. Anota el tiempo del step "Install dependencies" en los logs (ej. 78s).
2. Reactiva el cache
Vuelve a añadir cache: "pip" y cache-dependency-path. Haz push de nuevo (sin cambiar requirements). La primera vez será miss. Haz un segundo push trivial (ej. cambio en README). La segunda vez debería ser hit.
3. Compara
En la segunda ejecución, "Install dependencies" debería bajar a 10-25s. La diferencia es el ahorro. En proyectos con muchas dependencias, puede ser de 1+ minuto por ejecución.
Conexión con Proyecto
Matrix y caching son componentes clave del proyecto del Módulo 7. Un CI pipeline profesional valida en múltiples versiones de Python y usa cache para mantener builds rápidos. En la cápsula 05 añadirás coverage en CI y branch protection. El workflow que construiste aquí — matrix + cache — será la base a la que sumarás coverage y umbrales.
Resumen
- Matrix strategy ejecuta un job en múltiples configuraciones (p. ej. Python 3.10, 3.11, 3.12) en paralelo
- fail-fast: true cancela el resto cuando uno falla; fail-fast: false deja que todos terminen
- Caching de pip reduce el tiempo de install reutilizando paquetes entre ejecuciones
- La clave del cache debe incluir hash de requirements.txt (o pyproject.toml) y, con matrix, la versión de Python
- actions/setup-python con cache: "pip" simplifica la configuración
- Antes del cache: install puede ser 60-90s; después (cache hit): 10-25s es común
Próxima cápsula: Coverage en CI y Branch Protection — reportes de coverage, comentarios en PRs, y protecciones de branch.
Recursos Adicionales
- GitHub Actions: Workflow syntax - matrix - Documentación oficial del matrix
- actions/setup-python - caching - Cache integrado de pip
- actions/cache - Acción de cache manual
- Caching dependencies to speed up workflows - Guía de caching
- hashFiles - Función para generar claves de cache
- Python versión support - Ciclo de vida de versiones de Python
Módulo 7, Cápsula 04 — Testing with Claude Code Guide