Módulo 8: Proyecto — un pipeline de CI para Reservo
1. Presentación del módulo: el pipeline completo
Descripción
Durante siete módulos aprendiste una pieza a la vez. Correr la suite en cada push. Cerrar la brecha entre tu máquina y el runner. Probar en varias versiones. Cachear y paralelizar. Poner una puerta de cobertura. Manejar los flaky. Cada pieza vino con su demo, su mini-proyecto y su frontera clara, aislada de las demás para que la entendieras a fondo. Estaba bien que fuera así: se aprende mejor una herramienta cuando no compite con otras cinco por tu atención. Pero un pipeline real no es una pieza; es todas a la vez, tejidas en un solo archivo que corre en cada cambio y decide si tu código entra o no.
Este módulo es donde las juntas. Vas a armar el pipeline de CI completo de Reservo —el que corre la suite, la corre en tres versiones de Python, la acelera con caché y paralelismo, y la frena en seco si la cobertura cae por debajo de un umbral— y vas a entregar dos cosas concretas: el tests.yml completo, con todas las capas comentadas, y el setup de paridad local, un comando que corre en tu terminal exactamente lo que el CI corre en el runner. Ese segundo entregable es el que hace que "en mi máquina funciona" deje de ser una excusa y se vuelva una promesa verificable.
Al terminar esta lección vas a tener el mapa entero en la cabeza: qué etapa del pipeline resuelve qué problema, de qué módulo vino cada una, y cómo se apilan una sobre otra sin estorbarse. Vas a ver el pipeline completo dibujado como una tubería de etapas, la tabla que conecta cada etapa con el dolor que quita, y la primera corrida real de la suite de Reservo —la que el pipeline ejecutará en cada celda—. No aprendes una herramienta nueva aquí: aprendes a componer las siete que ya tienes en algo que protege de verdad.
Conexión con el módulo: esta lección es el plano del capstone. Aquí ves el pipeline entero de un vistazo y entiendes cómo encajan las capas; las lecciones 2 a 7 lo arman pieza por pieza, cada una tomando una capa y mostrándola en su lugar dentro del conjunto —la 2 el workflow base, la 3 las dependencias pinneadas, la 4 la matriz, la 5 caché y paralelismo, la 6 la puerta de cobertura, la 7 la política de flaky—. La lección 8 es el proyecto formal: escribes el tests.yml completo, montas la paridad local, y te evalúas por el método de tus decisiones. Y esa misma lección 8 cierra la guía, con el repaso de los ocho módulos y el camino hacia las guías hermanas.
Una nota sobre la honestidad de la guía, que en este módulo importa más que nunca porque vamos a mostrar mucho pipeline. El workflow de CI se ejecuta de verdad en un runner de GitHub, que aquí no tenemos. Así que el YAML lo escribes y lo lees como contenido —te muestro cómo se ve y cómo se leería su log—, mientras que las corridas de pytest, coverage y xdist son reales, hechas en local con Python 3.14.0 y pytest 9.1.1, porque tu máquina hace de una celda del pipeline. Cada número que cite —13 passed, 1 skipped, 88% de cobertura, 4.07s que bajan a 1.19s con paralelismo— lo medí ejecutando, no lo inventé. Cuando muestre un log de CI con tres jobs, ese es el formato honesto de cómo se vería, no una captura de un runner fantasma.
La orquesta que ensayó cada instrumento por separado
Piensa en una orquesta que prepara una sinfonía. Durante meses, cada sección ensayó por su cuenta: los violines en una sala, los metales en otra, la percusión en la suya. Cada grupo pulió su parte hasta dominarla. Si escucharas a los violines solos, dirías "perfecto"; a los metales solos, "impecable". Cada sección, aislada, suena bien.
Pero una sinfonía no es cada sección por separado; es todas tocando juntas, al mismo tiempo, coordinadas. El primer ensayo general es el momento de la verdad: ¿entran los metales cuando deben, sin tapar a los violines? ¿La percusión marca el tempo que el resto sigue? ¿El conjunto suena como una sola pieza, o como seis grupos compitiendo? Dominar tu instrumento es necesario, pero no basta. Lo que hace a la orquesta es la composición: las partes encajadas en un todo que funciona.
Tu pipeline de CI es esa sinfonía. Cada módulo de la guía fue el ensayo de una sección: aprendiste el workflow base, la matriz, el caché, la puerta de cobertura, cada uno hasta dominarlo por separado. Este módulo es el ensayo general. Vas a poner las siete secciones a tocar juntas en un solo tests.yml y a comprobar que el conjunto suena: que la matriz no pelea con el caché, que la puerta de cobertura no choca con el paralelismo, que el flaky no arruina un run entero. El capstone no te enseña un instrumento nuevo; te enseña a dirigir.
Un pipeline de CI completo no es una etapa; es la composición de todas. Cada módulo te dio una pieza dominada por separado; el capstone las teje en un solo workflow que protege de verdad porque las siete tocan juntas.
El pipeline completo, de un vistazo
Antes de armarlo pieza por pieza en las próximas lecciones, mira el conjunto entero. Un push llega, y el pipeline lo procesa en etapas, cada una construida sobre la anterior:
push / pull_request
│
▼
┌─────────────────────────────────────────────────────────────┐
│ MATRIZ: se abre en 3 celdas paralelas (3.11 · 3.12 · 3.13) │ ← módulo 4
│ ┌───────────────────────────────────────────────────────┐ │
│ │ 1. checkout (traer el código al runner) │ │ ← módulo 2
│ │ 2. setup-python (instalar la versión de la celda) │ │ ← módulo 4
│ │ 3. restaurar CACHÉ (deps pinneadas, sin bajar de nuevo)│ │ ← módulo 5
│ │ 4. pip install (desde requirements, determinista) │ │ ← módulo 3
│ │ 5. pytest -n auto (la suite, en paralelo) │ │ ← módulo 5
│ │ + --cov-fail-under=85 (PUERTA: rompe si cae) │ │ ← módulo 6
│ │ + política de flaky (retry/cuarentena) │ │ ← módulo 7
│ └───────────────────────────────────────────────────────┘ │
└─────────────────────────────────────────────────────────────┘
│
▼
3 veredictos (✓/✗) → protección de rama → merge o bloqueo
Léelo de arriba a abajo. Un push (o un pull request) dispara el pipeline. Lo primero que ocurre es que la matriz lo multiplica: en vez de un job, se abren tres, uno por versión de Python, corriendo en paralelo. Dentro de cada celda, los pasos son los mismos: traer el código, instalar la versión de Python de esa celda, restaurar el caché de dependencias para no bajarlas de internet cada vez, instalarlas desde una lista pinneada, y correr la suite —en paralelo con -n auto, con la puerta de cobertura activa y la política de flaky aplicada—. Al final, tres veredictos verdes o rojos, que la protección de rama usa para decidir si el cambio puede entrar a main.
Fíjate en algo importante: cada etapa vino de un módulo distinto, y ninguna sobra. El checkout y el pytest son el piso del módulo 2. La versión de cada celda es la matriz del módulo 4. El caché y el -n auto son la velocidad del módulo 5. La instalación determinista es la reproducibilidad del módulo 3. El --cov-fail-under es la puerta del módulo 6. Y la política de flaky es el módulo 7. El capstone es, literalmente, esta tubería: siete módulos convertidos en un archivo.
La tabla que conecta cada etapa con el problema que resuelve
Un pipeline no se arma juntando etapas porque sí. Cada una existe para quitar un dolor concreto —un modo específico en que el software se rompe cuando no tienes CI—. Esta es la tabla que quiero que memorices, porque es el esqueleto de todo el módulo: qué etapa, qué problema quita, y de qué módulo salió.
| Etapa del pipeline | El problema que quita | Módulo |
|---|---|---|
| Correr la suite en cada push y PR | "Se me olvidó correr los tests antes de subir" — el error humano de confiar en la disciplina | 2 |
| Instalar desde dependencias pinneadas | "En mi máquina funciona" — el CI en rojo y tu local en verde por versiones distintas | 3 |
| Matriz de versiones de Python | "Funciona en mi versión, pero el usuario tiene otra y truena" — el punto ciego de un solo entorno | 4 |
Caché de dependencias + pytest-xdist | "El CI tarda una eternidad y frena a todo el equipo" — el pipeline vuelto cuello de botella | 5 |
Puerta de cobertura (--cov-fail-under) | "Llegó código nuevo sin un solo test y nadie lo notó" — la erosión silenciosa de la red de seguridad | 6 |
| Política de flaky (retry / cuarentena) | "El CI se pone rojo a veces sin que nada cambie, y ya nadie le cree" — la confianza erosionada | 7 |
Guárdala. En las próximas seis lecciones vamos a recorrer esta tabla fila por fila, en el mismo orden, armando la etapa correspondiente dentro del tests.yml de Reservo. Cuando termines el módulo, deberías poder mirar cualquier pipeline del mundo real, reconocer cada una de estas etapas, y decir de qué problema protege —o notar cuál falta y qué riesgo deja abierto—.
Hay una segunda lectura de la tabla, más sutil, que vale la pena señalar. Las etapas no son independientes: se habilitan unas a otras. La matriz (módulo 4) multiplica el trabajo por tres, lo que hace urgente la velocidad (módulo 5): sin caché ni paralelismo, tres celdas tardan el triple. La velocidad, a su vez, mete el paralelismo (-n auto), que introduce un orden de ejecución no determinista, que es justo uno de los disparadores de los flaky (módulo 7). Y la puerta de cobertura (módulo 6) solo tiene sentido si la suite corre de verdad y de forma reproducible (módulos 2 y 3). No es una lista de opciones sueltas; es una cadena donde cada eslabón apoya y tensiona al siguiente. Diseñar el pipeline es entender esa cadena, no encender interruptores al azar.
Reservo, tal como lo dejamos
Seguimos con Reservo, el sistema de reservas de salas de un coworking que venimos probando desde el primer módulo. Lógica pura de Python: sin base de datos, sin red, sin relojes escondidos, dinero en centavos int. Un repaso de sus piezas, porque el pipeline se arma alrededor de su estructura:
Room(id,name,capacity,hourly_cents),Member(id,name,tier:"basic"o"pro"),Booking(con su campoprice_cents, elstart, elendcomo rango medio-abierto[start, end), y sustatus).- Las funciones núcleo en el paquete
reservo/:price_cents,refund_cents,overlaps,is_available,book,cancel, elCalendaren memoria, yreport_pages(la feature dependiente de versión del módulo 4). - Los números-ancla, el checksum de toda la guía: basic 3 h → 7500, pro 3 h → 6000 (20% de descuento), basic 1 h → 2500; y el reembolso sobre 6000 pagados: 6000 si cancelas 72 h antes (≥ 48 h, 100%), 3000 a 36 h (24–48 h, 50%), 0 a 12 h (< 24 h).
La suite vive en tests/, repartida en cuatro archivos: test_pricing.py, test_refunds.py, test_availability.py y test_version_features.py. En total, catorce tests, uno de los cuales se salta en Python 3.12+ (el del fallback manual de report_pages, que solo aplica en versiones viejas). Ese es el material que el pipeline va a proteger.
Ejemplo trabajado: la corrida que el pipeline ejecuta en cada celda
Antes de escribir una línea de YAML, corramos la suite en la máquina donde estoy escribiendo —Python 3.14.0, pytest 9.1.1— para ver, de verdad, lo que cada celda del pipeline ejecutaría. Esta es la unidad de trabajo que se repetirá tres veces en la matriz.
python -m pytest -v -rs
La bandera -v (--verbose) lista cada test con su veredicto; -rs (report skipped) imprime la razón de cada test que se salta.
Qué esperar. En Python 3.14.0, medido ejecutando (esta es la salida real, no una maqueta):
============================= test session starts ==============================
platform darwin -- Python 3.14.0, pytest-9.1.1, pluggy-1.6.0 -- /private/tmp/reservo-m8/.venv/bin/python
cachedir: .pytest_cache
rootdir: /private/tmp/reservo-m8
configfile: pyproject.toml
testpaths: tests
plugins: xdist-3.8.0, rerunfailures-16.4, cov-7.1.0
collected 14 items
tests/test_availability.py::test_sequential_bookings_do_not_overlap PASSED [ 7%]
tests/test_availability.py::test_crossing_ranges_overlap PASSED [ 14%]
tests/test_availability.py::test_available_on_empty_calendar PASSED [ 21%]
tests/test_availability.py::test_book_rejects_a_conflicting_slot PASSED [ 28%]
tests/test_pricing.py::test_basic_three_hours PASSED [ 35%]
tests/test_pricing.py::test_pro_three_hours PASSED [ 42%]
tests/test_pricing.py::test_basic_one_hour PASSED [ 50%]
tests/test_pricing.py::test_pro_one_hour_rounds_with_integer_math PASSED [ 57%]
tests/test_refunds.py::test_full_refund_72h_before PASSED [ 64%]
tests/test_refunds.py::test_half_refund_36h_before PASSED [ 71%]
tests/test_refunds.py::test_no_refund_12h_before PASSED [ 78%]
tests/test_version_features.py::test_report_pages_groups_bookings PASSED [ 85%]
tests/test_version_features.py::test_report_pages_uses_stdlib_batched PASSED [ 92%]
tests/test_version_features.py::test_report_pages_manual_fallback_on_old_python SKIPPED [100%]
=========================== short test summary info ============================
SKIPPED [1] tests/test_version_features.py:24: el fallback manual solo se ejercita en Python < 3.12
========================= 13 passed, 1 skipped in 0.02s =========================
Lee el resumen final: 13 passed, 1 skipped. Trece tests pasaron y uno se saltó, limpiamente, con su razón impresa gracias a -rs. Entre los trece verdes están los seis números-ancla que son el corazón de Reservo —test_basic_three_hours (7500), test_pro_three_hours (6000), test_basic_one_hour (2500), y los tres de reembolso (6000, 3000, 0)—. El skip es test_report_pages_manual_fallback_on_old_python: como esta máquina corre 3.14 (≥ 3.12), la rama del fallback manual no aplica, así que pytest lo salta sin fingir que pasa ni fallar por algo irrelevante.
Fíjate también en las líneas del encabezado, porque cuentan la historia del módulo entero. configfile: pyproject.toml y testpaths: tests dicen que hay configuración pinneada (módulo 3). plugins: xdist-3.8.0, rerunfailures-16.4, cov-7.1.0 lista las tres herramientas que las próximas lecciones activan: xdist para el paralelismo (módulo 5), rerunfailures para los flaky (módulo 7), cov para la puerta de cobertura (módulo 6). Todas ya instaladas, esperando su turno. Esta corrida es el estado base —la suite verde en un entorno— sobre el que apilaremos, capa por capa, las seis etapas que faltan.
Los dos entregables del capstone
Todo el módulo apunta a producir dos cosas. Vale la pena tenerlas claras desde ya, porque cada lección aporta un pedazo de una o de otra.
Entregable 1: el tests.yml completo. Un solo workflow de GitHub Actions que teje las seis etapas de la tabla: dispara en cada push y PR, abre la matriz de tres versiones, restaura el caché de dependencias, instala desde una lista pinneada, y corre la suite en paralelo con la puerta de cobertura activa. Comentado, para que cualquiera que lo lea entienda qué hace cada línea y de qué protege. Es contenido —no lo ejecutamos aquí—, pero es contenido que entiendes hasta la última línea.
Entregable 2: la paridad local. Un comando o un script corto —lo llamaremos check.sh— que corre en tu máquina exactamente lo que el CI corre en el runner: la misma instalación, la misma suite, la misma puerta de cobertura. Su valor es que convierte el CI de "el lugar donde descubro que algo estaba mal" en "la confirmación automática de algo que ya sé que está bien". Si check.sh pasa en tu terminal, tienes evidencia directa de que el pipeline pasará. La paridad local es la idea que ha recorrido toda la guía, aquí vuelta un entregable concreto.
Se evalúan por el método, no por el tamaño. Un tests.yml con una matriz de tres versiones bien justificadas vale más que uno con nueve celdas encendidas por reflejo. Una puerta de cobertura en un umbral que puedes defender con un argumento vale más que una en 100% que reviente en el primer código legítimamente no cubrible. El capstone no premia el pipeline más grande; premia el mejor pensado.
Errores comunes
Creer que "sé usar cada etapa" es lo mismo que "sé diseñar el pipeline". Qué pasa: alguien completó los siete módulos, sabe escribir una matriz, un caché, una puerta —cada uno por separado—, y asume que juntarlos es trivial. Al armar el tests.yml completo, descubre que las etapas interactúan: el orden de los steps importa, el caché depende de la lista de dependencias, la puerta necesita que la suite corra primero. Por qué pasa: dominar una pieza aislada y componer el conjunto son habilidades distintas, como tocar un instrumento y dirigir una orquesta. Cómo detectarlo: si nunca has escrito las seis etapas en un solo archivo, no sabes todavía si encajan. Cómo corregirlo: este módulo, que las arma juntas y en orden; el ensayo general es insustituible.
Encender todas las etapas por reflejo, sin preguntarse si el proyecto las necesita. Qué pasa: alguien copia un pipeline "completo" de internet —matriz de 3×3, caché, xdist, puerta al 95%— para un proyecto que no lo amerita, y termina con nueve celdas para lógica pura, paralelismo que solo añade sobrecarga a una suite de 0.02s, y una puerta que revienta en código legítimamente no cubrible. Por qué pasa: "más etapas" se siente "más profesional". Cómo detectarlo: por cada etapa, pregúntate "¿qué problema real de este proyecto quita?". Si la respuesta es "ninguno todavía", esa etapa es costo y ruido. Cómo corregirlo: la tabla de esta lección al revés —parte del problema, no de la herramienta—. Reservo necesita la matriz (es una librería que promete versiones) pero no necesita -n auto en su suite actual (es de 0.02s); el capstone justifica cada elección.
Entregar el YAML sin la paridad local. Qué pasa: alguien escribe un tests.yml impecable y lo da por terminado, sin correr nunca los comandos en su máquina. Sube, y el CI real revela en minutos lo que la terminal habría revelado en segundos: una dependencia faltante, un comando mal escrito, un umbral mal puesto. Por qué pasa: el YAML "se ve bien" y da sensación de trabajo terminado. Cómo detectarlo: si no tienes una salida real de pytest/coverage de tu propia terminal, no has verificado nada, solo escrito intenciones. Cómo corregirlo: la paridad local es la mitad del entregable, no un extra. Corre en tu máquina lo mismo que el CI correrá, y pega la salida verde como evidencia.
Ejercicios
Ejercicio 1 — Mapea la etapa al problema. Para cada síntoma, di qué etapa del pipeline lo resuelve y de qué módulo viene. (a) "Un compañero subió un cambio sin correr los tests y rompió main." (b) "El test pasa en mi laptop con Python 3.13 pero un usuario con 3.11 reporta un ImportError." (c) "Cada push tarda 14 minutos y el equipo se queja." (d) "Alguien agregó una función de 40 líneas sin un solo test y nadie lo notó en el review."
Ver solución
- (a) Correr la suite en cada push y PR → módulo 2. El problema es confiar en que cada quien recuerde correr los tests; el pipeline los corre automáticamente en cada push, así que "se me olvidó" deja de poder romper
main. - (b) Matriz de versiones → módulo 4. Un solo entorno verde afirma un solo entorno. La matriz corre la suite en 3.11, 3.12 y 3.13, así que un
ImportErrorque solo aparece en 3.11 se caza en la celda de 3.11 antes de llegar a un usuario. - (c) Caché + paralelismo → módulo 5. El pipeline se volvió un cuello de botella. Cachear las dependencias evita bajarlas cada vez, y
pytest-xdistcorre la suite en paralelo; juntos recortan el tiempo del ciclo de feedback. - (d) Puerta de cobertura → módulo 6. Código nuevo sin tests baja la cobertura;
--cov-fail-underrompe el build si cae por debajo del umbral, convirtiendo "llegó sin tests" de algo invisible en algo que frena el merge.
La regla mecánica: cada síntoma es el problema que una fila de la tabla quita. Si puedes nombrar la fila, entiendes por qué la etapa existe.
Ejercicio 2 — La cadena de dependencias entre etapas. En la lección dijimos que las etapas "se habilitan unas a otras". Explica, en tres frases, por qué agregar la matriz (módulo 4) hace más urgente el caché y el paralelismo (módulo 5), y por qué el paralelismo puede a su vez alimentar un problema de flaky (módulo 7).
Ver solución
La matriz multiplica el trabajo: una matriz de tres versiones corre la suite completa tres veces por push, así que cualquier lentitud —bajar dependencias, correr los tests en serie— se paga por triplicado, y lo que era tolerable en un solo job se vuelve un cuello de botella en tres. Por eso el caché (no volver a bajar las dependencias en cada celda) y el paralelismo (-n auto, correr los tests de cada celda en varios procesos) pasan de "lujo" a "necesidad" en cuanto hay matriz.
El paralelismo, a su vez, cambia el orden en que corren los tests: con -n auto, los tests se reparten entre procesos y ya no corren en la secuencia predecible de una corrida en serie. Si algún test dependía sin querer del orden —dejaba un archivo, una variable global, un estado compartido que otro test asumía—, ese acoplamiento oculto, invisible en serie, se destapa en paralelo como un fallo intermitente: un flaky. Así, la etapa de velocidad (módulo 5) puede exponer trabajo para la etapa de flaky (módulo 7). No es un defecto de xdist; es que el paralelismo revela un acoplamiento que ya existía y que la ejecución en serie escondía.
Ejercicio 3 — ¿Qué etapa NO necesita Reservo (todavía), y por qué? El pipeline que armaremos incluye seis etapas. Reservo es una librería de lógica pura (aritmética de enteros, comparación de fechas; sin red, sin disco, sin BD), con una suite que corre en 0.02 segundos. Elige una etapa que, para Reservo tal como está hoy, sea discutible o directamente innecesaria, y justifica por qué —y qué tendría que cambiar en Reservo para que sí valiera la pena—.
Ver solución
La respuesta más defendible es el paralelismo con pytest-xdist (-n auto). Reservo tiene una suite de catorce tests que corre en 0.02 segundos. Paralelizarla no la haría más rápida: pytest-xdist tiene que arrancar procesos worker, repartirles los tests y recolectar los resultados, y esa sobrecarga fija —del orden de un segundo— es mayor que los 0.02s que la suite tarda en serie. Encender -n auto en una suite así la volvería más lenta, no más rápida (lo verás medido en la lección 5). Es la herramienta correcta para el problema equivocado: xdist paga cuando la suite es lenta (minutos), no cuando ya es instantánea.
Para que valiera la pena, Reservo tendría que crecer hasta que su suite tardara lo suficiente como para amortizar la sobrecarga: cientos o miles de tests, o tests lentos de verdad (que hagan E/S, que esperen recursos, que simulen carga). El día que la suite tarde, digamos, un minuto en serie, -n auto sobre varios cores la bajaría a una fracción, y ahí el paralelismo pasaría de sobrecarga a inversión. Otra respuesta defendible es la dimensión de sistema operativo en la matriz: Reservo es lógica pura que da idéntico en Linux, macOS y Windows, así que probar en tres SO gastaría el triple sin cazar un solo bug extra —eso cambiaría el día que Reservo escriba archivos a disco, donde rutas y saltos de línea sí difieren por sistema—. La lección de fondo: una etapa se justifica por el problema que quita, y una suite instantánea de lógica pura no tiene (todavía) el problema de velocidad ni el de portabilidad de SO.
Resumen y siguiente paso
En esta lección instalaste el plano del capstone: un pipeline de CI completo no es una etapa, es la composición de todas. Viste el pipeline entero de Reservo dibujado como una tubería —push, matriz de tres celdas, y dentro de cada una checkout, setup-python, caché, install, y pytest con la puerta de cobertura y la política de flaky— y entendiste que cada etapa vino de un módulo distinto y quita un problema concreto. Memorizaste la tabla que conecta etapa ↔ problema ↔ módulo, y viste que las etapas no son independientes: se habilitan y se tensionan unas a otras, como las secciones de una orquesta en el ensayo general.
Corriste la suite de Reservo de verdad —13 passed, 1 skipped en Python 3.14.0— y leíste en su encabezado las tres herramientas que las próximas lecciones activan (xdist, rerunfailures, cov), ya instaladas y esperando su turno. Y quedaron claros los dos entregables hacia los que apunta todo el módulo: el tests.yml completo y la paridad local, evaluados por el método de sus decisiones, no por el número de tests.
Antes de avanzar deberías poder: dibujar de memoria la tubería del pipeline y nombrar cada etapa; recitar la tabla etapa ↔ problema ↔ módulo; explicar por qué la matriz hace urgente la velocidad y por qué el paralelismo puede alimentar un flaky; y justificar qué etapa Reservo no necesita todavía.
Lo que sigue, en la lección 2, es empezar a armar el pipeline por su base: el workflow que corre la suite. Es la capa del módulo 2 —checkout, setup-python, install, pytest— vista ahora como el piso sobre el que se apilan las otras cinco etapas. Vas a escribirla, correr su paridad local, y confirmar con tus manos que el esqueleto verde funciona, para luego, lección tras lección, ir sumándole la reproducibilidad, la matriz, la velocidad, la puerta y la política de flaky hasta tener el tests.yml entero.
Recursos
- Building and testing Python — GitHub Actions — la guía oficial de GitHub para probar proyectos de Python, el punto de partida del pipeline que armaremos capa por capa. Vuelve a ella cuando montes el CI de tu propio proyecto.
- How to invoke pytest — documentación de pytest — las formas de correr la suite (completa, verbosa, con
-rs) que usamos en la corrida base, y la sección de exit codes que conecta con el color del job. La referencia del día a día. - Understanding GitHub Actions — el vocabulario del pipeline (workflow, job, step, runner) que aparece en la tubería de esta lección. Léelo para que los términos de las próximas seis lecciones caigan en su lugar.
- pytest-xdist, coverage.py y pytest-rerunfailures — las tres herramientas que el encabezado de la corrida listó (
plugins: xdist, rerunfailures, cov) y que las lecciones 5, 6 y 7 activan. Aquí solo las asomamos; en sus módulos las abrimos.