Módulo 1: De tu máquina al pipeline: por qué CI

1. Presentación del módulo: la suite que solo corría en tu máquina

Descripción

Al terminar esta lección vas a tener claro un problema que probablemente ya viviste sin ponerle nombre, y la idea que lo resuelve. El problema es este: tus tests solo protegen el lugar donde corren. Tú escribiste una suite de Reservo, la corres con pytest, la ves en verde, y confías en ella —con razón, es una buena suite—. Pero esa suite corre en tu máquina, cuando te acuerdas de correrla, con la versión de Python que tienes, con las dependencias que instalaste y con las variables de entorno que exportaste en tu shell hace meses. Cada una de esas palabras —"tu", "tú"— es una grieta. El día que tu compañero clona el repo y corre la misma suite, o el día que ese código llega a producción, cualquiera de esas grietas puede convertir tu verde tranquilo en un rojo que nadie esperaba.

La idea que resuelve esto tiene nombre y es el corazón de toda la guía: Integración Continua (CI, por Continuous Integration). En una sola frase: correr tu suite de tests automáticamente, en un entorno limpio y compartido, en cada cambio que alguien sube al repositorio. No cuando te acuerdes: siempre. No en tu máquina: en una máquina neutral que arranca de cero cada vez. No para ti solo: para todo el equipo, como una verdad común. Ese es el salto que da esta guía: llevar la suite de Reservo desde "corre en mi laptop" hasta "corre sola, en un pipeline, cada vez que alguien toca el código".

Conexión con el módulo: esta lección es el mapa, no el territorio. Aquí no escribes todavía un workflow de CI —eso llega en el módulo 2, y a propósito—; aquí instalas el problema y el concepto, que son lo que hace que el módulo 2 tenga sentido. La lección 2 te pone cara a cara con "en mi máquina funciona": vas a ver el mismo test dar dos veredictos distintos según el entorno, con salida real. La 3 define qué es exactamente el CI y qué no es. La 4 explica por qué un fallo que se detecta tarde cuesta más caro, y cómo el CI acorta ese bucle. La 5 abre un pipeline por dentro: sus etapas —checkout, instalar, testear, reportar— y la señal que lee para decidir. La 6 responde qué protege el CI: la rama principal siempre verde. La 7 distingue el CI del CD en dos minutos. Y la 8, el mini-proyecto, te pone a mapear tu propio flujo manual de Reservo a las etapas de un pipeline.

Una nota sobre las herramientas, porque marca el tono de todo el módulo. Vamos a usar GitHub Actions como la plataforma de CI a lo largo de la guía, porque es la más extendida y la más fácil de tocar sin salir de tu repositorio. Pero en este módulo no vas a escribir su YAML todavía. La razón es la misma por la que la guía de fundamentos te hizo escribir un assert a mano antes de darte pytest: si te entrego la configuración antes de que entiendas el problema, vas a aprender a copiar un workflow sin haber entendido nunca qué es el CI ni por qué lo necesitas. Y esa confusión es cara: produce gente que tiene un pipeline verde y no sabría decir qué está protegiendo. Así que primero el problema, después la herramienta.

El ensayo que solo salió bien en tu sala

Piénsalo así. Eres músico y tienes un solo que tocar en un concierto. Lo ensayas en tu cuarto, con tu guitarra, tu amplificador, tus pedales, a tu volumen, y sale perfecto. Diez veces seguidas, impecable. Te vas a dormir tranquilo: "me lo sé".

Llega el día. Subes al escenario. La guitarra es la misma, pero el amplificador es otro, el escenario tiene un eco que tu cuarto no tenía, el monitor te devuelve el sonido con medio segundo de retraso, y hay un cable que hace un zumbido que en tu casa nunca escuchaste. El solo que te salía perfecto ahora te sale torcido —no porque no te lo supieras, sino porque nunca lo tocaste en estas condiciones. Ensayaste en un entorno, y tocas en otro. Y lo que funcionaba en el primero no estaba garantizado en el segundo; solo lo suponías.

Un músico profesional sabe esto, y por eso hace pruebas de sonido: llega antes, toca en el escenario real, con el equipo real, y descubre el zumbido antes del concierto, cuando todavía se puede arreglar. La prueba de sonido no cambia cómo tocas: cambia dónde pruebas que sabes tocar. Mueve la verificación desde tu cuarto —cómodo, conocido, engañoso— hasta el escenario —el lugar donde de verdad importa—.

El CI es la prueba de sonido de tu código. Tu máquina es tu cuarto: cómodo, conocido, lleno de cosas que instalaste una vez y nunca volviste a mirar. El entorno donde tu código va a correr de verdad —el de tu compañero, el servidor de producción— es el escenario. Y el CI es esa máquina neutral, que arranca vacía como un escenario antes de la prueba de sonido, instala solo lo que el proyecto declara que necesita, y corre tu suite ahí. Si tu suite pasa en ese entorno limpio, tienes una razón de verdad para confiar. Si pasa solo en tu cuarto, no sabes si te la sabes o si tuviste suerte con el amplificador.

Que un test pase en tu máquina prueba que pasa en tu máquina. El CI mueve la prueba al escenario: un entorno limpio, compartido y reproducible, para que "pasa" signifique "pasa para cualquiera".

El caso de la guía: Reservo, ahora con pipeline

Toda la guía trabaja sobre Reservo, el mismo sistema de reservas de salas de un coworking que ya conoces de las guías hermanas de testing. Si vienes de ellas, te lo sabes de memoria; si llegas directo aquí, esto es lo justo que necesitas.

Reservo es lógica pura de Python sobre la librería estándar: no tiene base de datos, ni red, ni servidor web. Sus piezas son tres modelos —Room, Member, Booking— y un puñado de funciones puras. Nos van a acompañar sobre todo estas:

  • price_cents(room, member, hours) — cuánto cobrarle a un miembro por reservar una sala. Aritmética exacta: precio por hora, por horas, menos el descuento del tier pro (20%).
  • overlaps(a_start, a_end, b_start, b_end) — si dos rangos de tiempo [start, end) se pisan. Tocarse en el borde no cuenta como solaparse.
  • refund_cents(booking, price_paid_cents, now) — cuánto se reembolsa al cancelar, según la anticipación.

Todo el dinero va en centavos, como entero$25.00 es 2500—, nunca como float, porque los float acumulan errorcitos que hacen que un total no cuadre por un centavo. Y todo resultado es determinista: la misma entrada da la misma salida, siempre. Esa pureza es justo lo que hace de Reservo un caso perfecto para hablar de CI: cuando la suite falle en el módulo 2, no vas a poder culpar a "la base de datos estaba lenta"; el fallo va a ser del entorno o del código, que es exactamente lo que queremos aprender a distinguir.

Los números-ancla —los resultados exactos que afirmamos una y otra vez— son el "checksum" de la guía:

AfirmaciónCuentaResultado (centavos)
price_cents: basic, 3 h2500 × 37500
price_cents: pro, 3 h7500 − 20%6000
refund_cents: cancelar 72 h antes (≥ 48 h)100% de 60006000
refund_cents: cancelar 36 h antes (24–48 h)50% de 60003000
refund_cents: cancelar 12 h antes (< 24 h)0%0

Estos números ya tienen sus tests escritos —esa es la premisa de la guía: los tests ya existen, el trabajo es correrlos bien, en CI—. Aquí no aprendes a escribir tests (eso es testing-fundamentals-and-tdd-guide); aprendes a llevar la suite que ya tienes a un pipeline que la corra por ti.

Ejemplo trabajado: esto es lo que el CI va a correr

Bajemos la idea al piso con la suite de verdad. Tenemos el paquete reservo/ (los modelos y las funciones) y tres archivos de test junto a él:

reservo-ci/
├── reservo/
│   ├── __init__.py
│   ├── models.py        # Room, Member, Booking
│   ├── pricing.py       # price_cents
│   ├── scheduling.py    # overlaps, is_available
│   └── refunds.py       # refund_cents
├── test_pricing.py      # los cuatro precios ancla
├── test_scheduling.py   # los bordes de overlaps
└── test_refunds.py      # las cinco filas del reembolso

Los tests son tablas de casos con parametrize, uno por número-ancla. Por ejemplo, el de precios:

# test_pricing.py
import pytest

from reservo.models import Room, Member
from reservo.pricing import price_cents

focus = Room(id="r-focus", name="Focus", capacity=1, hourly_cents=2500)


@pytest.mark.parametrize("tier, hours, expected", [
    ("basic", 3, 7500),   # 2500 * 3
    ("pro",   3, 6000),   # 7500 - 20%
    ("basic", 1, 2500),   # 2500 * 1
    ("pro",   1, 2000),   # 2500 - 20%
], ids=["basic-3h", "pro-3h", "basic-1h", "pro-1h"])
def test_price_by_tier_and_hours(tier, hours, expected):
    member = Member(id="m-1", name="Ana", tier=tier)
    assert price_cents(focus, member, hours) == expected

No hay nada nuevo aquí para ti: es la suite que sabes escribir. Lo interesante es lo que pasa cuando la corres. Este es el comando que vas a teclear en tu máquina hoy —y, dentro de dos módulos, exactamente el mismo comando que el CI va a teclear por ti en su máquina neutral.

Qué esperar. Con Python 3.14.0 y pytest 9.1.1, corriendo python3 -m pytest en la raíz del proyecto, sale esto:

============================= test session starts ==============================
platform darwin -- Python 3.14.0, pytest-9.1.1, pluggy-1.6.0
rootdir: /private/tmp/reservo-ci
collected 12 items

test_pricing.py ....                                                     [ 33%]
test_refunds.py .....                                                    [ 75%]
test_scheduling.py ...                                                   [100%]

============================== 12 passed in 0.04s ==============================

Léelo con calma, porque cada pieza vuelve más adelante. La cabecera te dice en qué entorno corriste: Python 3.14.0, pytest-9.1.1. Ese dato, que ahora parece decorativo, es el protagonista del módulo 3: cuando el CI corra en Python 3.12 y tú en 3.14, esa línea es la primera pista de por qué el CI está rojo y tú verde. Cada punto (.) es un test que pasó. Y la última línea, 12 passed in 0.04s, es el veredicto: doce afirmaciones sobre Reservo, todas ciertas, comprobadas en cuatro centésimas de segundo.

Guarda esta imagen, porque es el punto de partida de todo: hoy, esta suite verde vive en tu máquina y en ningún otro lado. Si tu compañero no la corre, no sabe que está verde. Si tú olvidas correrla antes de subir un cambio, nadie la corre. Y si tu máquina tiene algo que la de él no tiene, tu verde no dice nada sobre el de él. El resto de la guía es cerrar esas tres grietas —el olvido, el aislamiento y la diferencia de entorno— con un pipeline. Y la primera, la más traicionera, la de la diferencia de entorno, es la protagonista de la lección que sigue.

El mapa de la guía

Ocho módulos, y cada uno te deja una capacidad concreta sobre correr tu suite en CI. No están en cualquier orden: van de entender por qué a montar el primer pipeline a hacerlo robusto, rápido y con puertas de calidad, y terminan armando un pipeline completo para Reservo.

MóduloQué instalaCapacidad con la que sales
1. De tu máquina al pipeline: por qué CIEl problema "en mi máquina funciona", qué es el CI, el bucle de feedback, las etapas de un pipeline y qué protegeExplicar por qué necesitas CI y qué hace, sin escribir todavía el workflow
2. Tu primer pipeline: pytest en CIEl primer workflow de GitHub Actions que corre pytest en cada push: anatomía del YAML (on, jobs, steps), leer el logEscribir y leer un pipeline que corre tu suite en cada cambio
3. Reproducir un fallo de CI en localCI en rojo, local en verde: la brecha de entorno, dependencias pinneadas, instalaciones deterministas, reproducir el fallo en tu máquinaCerrar la brecha entre "funciona en mi máquina" y el runner
4. La matriz: versiones y entornosCorrer la suite en varias versiones de Python y sistemas operativos con strategy.matrix; cuándo la matriz paga y cuándo es ruidoProbar en varios entornos a la vez sin duplicar trabajo
5. CI rápido: caché y paralelismoCachear dependencias, paralelizar con pytest-xdist, dividir la suite, el trade-off velocidad/costoTener un pipeline que protege sin volverse un cuello de botella
6. Puertas de calidad: umbrales de coberturaUn umbral de cobertura que rompe el build, fallar en caída de cobertura, cuándo una puerta ayuda y cuándo estorbaPoner puertas que suben la barra sin volverse un fetiche
7. Flaky tests en CIEl debate del retry, la cuarentena, el fallo que solo ocurre en CI, por qué un flaky erosiona la confianzaManejar los tests inestables sin apagar tu red de seguridad
8. Proyecto: un pipeline de CI para ReservoEl proceso completo: la suite, la matriz, la puerta de cobertura, caché y paralelismo, y la paridad localArmar y defender un pipeline de CI completo de punta a punta

Fíjate en la forma del arco. El módulo 1 te da el porqué. El 2, el primer pipeline de verdad. El 3 cierra la grieta del entorno —la de "en mi máquina funciona"—. El 4 lo lleva a varios entornos a la vez. El 5 lo hace rápido. El 6 le pone puertas. El 7 se enfrenta a los tests inestables. Y el 8 junta todo en un pipeline real para Reservo.

La frontera: qué se enseña aquí y qué al lado

Esta guía vive en un ecosistema de guías hermanas sobre testing, y tiene una regla clara sobre qué le toca. Es la guía de correr tus tests en CI/CD: no de escribirlos, no de diagnosticarlos a fondo, no de desplegar tu app. Conviene que sepas desde ya dónde buscar cada cosa, para no esperar de aquí algo que le toca a otra guía o a un módulo posterior:

TemaQué se ve aquíDónde está el desarrollo completo
Cómo ESCRIBIR y organizar testsNada: los tests de Reservo ya existen; el foco es correrlostesting-fundamentals-and-tdd-guide, test-automation-framework-architecture-guide
El workflow YAML concreto de GitHub ActionsSolo el concepto de pipeline y sus etapas; el YAML llega en el módulo 2Módulo 2 de esta misma guía
La matriz de versiones / la velocidad del CISe nombran como parte del mapa, no se desarrollanMódulos 4 y 5 de esta misma guía
Diagnosticar a fondo un falloSe toca "reproducir el fallo de CI" en el módulo 3, pero la diagnosis profunda va apartetest-failure-diagnosis-guide
Desplegar la app / CD a producciónEl CD se nombra en la lección 7 para ubicarlo; no se enseña desplieguetesting-backend-applications-guide lo roza; el foco aquí es el CI de tests

La regla mecánica para recordarlo: si la pregunta es "¿cómo corro mi suite automáticamente en cada cambio?", es esta guía. Si es "¿cómo escribo un buen test para esta lógica?", es la de fundamentos. Si es "¿cómo despliego mi app?", es otra. Mantener esa frontera clara es lo que te permite aprender a montar un pipeline sin ahogarte en temas que aún no necesitas.

Errores comunes

Creer que "los tests pasan" y "los tests pasan para todos" son lo mismo. Qué pasa: alguien corre la suite en su máquina, la ve verde, y concluye "el código está probado, listo". Lo sube. Su compañero clona el repo, corre la misma suite y ve rojo, porque en su máquina falta una dependencia, o la versión de Python es otra, o una variable de entorno no está. Por qué pasa: se confunde el veredicto local ("pasa aquí") con un veredicto universal ("pasa en cualquier lado"). Cómo detectarlo: pregúntate "¿alguien más, en otra máquina, obtendría este mismo verde?". Si no puedes responder con certeza, tu verde es local. Cómo corregirlo: es literalmente el tema de la guía —correr la suite en un entorno limpio y compartido, o sea, en CI—. Este módulo instala por qué; el módulo 2 lo hace realidad.

Pensar que el CI es "una herramienta que hay que configurar" en vez de un concepto. Qué pasa: alguien salta directo a copiar un archivo YAML de un tutorial, lo pega en su repo, ve un check verde y cree que "ya tiene CI", sin entender qué está corriendo ni qué protege. Cuando el pipeline se pone rojo por una razón de entorno, no tiene idea de qué está pasando, porque nunca entendió el mecanismo. Por qué pasa: la parte visible del CI es un archivo de configuración, así que es tentador tratarlo como un trámite de copiar y pegar. Cómo detectarlo: si tienes un pipeline pero no sabrías explicar en una frase qué problema resuelve, te falta el concepto. Cómo corregirlo: este módulo entero. Entiende el problema —"en mi máquina funciona"— y el concepto —correr la suite automática, limpia y compartida— antes de tocar el YAML del módulo 2.

Esperar el YAML en este módulo. Qué pasa: alguien llega buscando "cómo escribo el workflow" y se frustra porque este módulo no se lo da. Por qué pasa: es natural querer ir directo a la parte que se teclea. Cómo detectarlo: si estás esperando ver on: push y jobs: en esta lección, estás adelantándote. Cómo corregirlo: date el permiso de entender primero. El módulo 2 se dedica entero al workflow, línea por línea, y va a rendir mucho más cuando llegues con el problema y el concepto ya claros. El orden es a propósito, igual que en fundamentos se escribió un assert a mano antes de tocar pytest.

Ejercicios

Ejercicio 1 — Encuentra las tres palabras "tú". Vuelve a leer esta frase del inicio: "esa suite corre en tu máquina, cuando tú te acuerdas de correrla, con la versión de Python que tú tienes". Sin mirar la lección, identifica las tres grietas que esas palabras esconden —una por cada "tú"— y di, para cada una, qué parte del CI la cierra.

Ver solución

Las tres grietas son:

  1. "en tu máquina" → la grieta del entorno. Tu verde solo vale para tu máquina; en otra puede ser rojo. La cierra el CI corriendo la suite en un entorno limpio y neutral, igual para todos (el tema de las lecciones 2 y 3).
  2. "cuando tú te acuerdas" → la grieta del olvido. Una suite que depende de que alguien la corra a mano tarde o temprano no se corre. La cierra el CI corriendo la suite automáticamente en cada push/PR, sin que nadie tenga que acordarse (la lección 3).
  3. "la versión de Python que tú tienes" → un caso concreto de la grieta del entorno: la diferencia de versiones. Tu 3.14 no es el 3.12 de tu compañero. La cierra el CI declarando qué versión usar (y, en el módulo 4, probando en varias a la vez con la matriz).

La lección: "en mi máquina funciona" no es una sola falla, son varias grietas distintas —entorno, olvido, versiones— y el CI las cierra con mecanismos distintos. Nombrarlas por separado es el primer paso para entender qué está resolviendo cada parte de un pipeline.

Ejercicio 2 — ¿Qué garantiza un verde local? Corres python3 -m pytest en tu máquina y ves 12 passed. Para cada una de estas afirmaciones, decide si el verde local la garantiza o solo la sugiere, y explica por qué en una frase. (a) "Los doce tests pasan en mi máquina, ahora." (b) "Los doce tests pasan en la máquina de mi compañero." (c) "Los doce tests pasarán mañana en mi máquina." (d) "El código no tiene bugs."

Ver solución
  • (a) Garantizado. Es exactamente lo que el verde local afirma: pasaron, aquí, en este momento. Ni más ni menos.
  • (b) Solo sugerido. Tu máquina y la de tu compañero pueden diferir en versión de Python, dependencias, variables de entorno o sistema operativo. El verde local no dice nada firme sobre la máquina de otro; para garantizarlo necesitas correr en un entorno común, o sea, CI.
  • (c) Solo sugerido. Si algún test depende del reloj, de una fecha, o de algo que cambia con el tiempo, mañana podría ser rojo aunque el código no cambie. El verde de hoy no garantiza el de mañana por sí solo.
  • (d) Ni garantizado ni bien sugerido. El verde dice que los casos que probaste pasan, no que todos los casos pasen. Un bug en un caso que ningún test toca queda perfectamente oculto tras un verde. (Esto es la lección de cobertura, en testing-fundamentals M6 y aquí en el módulo 6.)

La lección: un verde local garantiza una sola cosa —"pasó aquí y ahora"— y el resto son suposiciones de distinto grado. Buena parte de esta guía es convertir la suposición (b) en garantía, moviendo la prueba a un entorno compartido.

Ejercicio 3 — Ubica cada necesidad en su lugar. Para cada frase, decide si la resuelves con este módulo, con un módulo posterior de esta guía, o con una guía hermana, y di cuál en una frase. (a) "Quiero entender por qué mi test verde se rompió para otro." (b) "Quiero escribir el archivo de workflow que corre pytest en cada push." (c) "Quiero probar mi suite en Python 3.11, 3.12 y 3.13 a la vez." (d) "Quiero aprender a escribir un buen test para refund_cents."

Ver solución
  • (a) Este módulo (y el 3). El fenómeno "en mi máquina funciona" es justo lo que instala este módulo, en la lección 2; reproducir y cerrar la brecha a fondo es el módulo 3.
  • (b) Un módulo posterior de esta guía: el módulo 2. El workflow YAML concreto —on, jobs, steps— es el tema del módulo 2. Aquí solo verás el concepto de pipeline y sus etapas.
  • (c) Un módulo posterior de esta guía: el módulo 4. Correr la suite en varias versiones a la vez es la matriz, y se desarrolla en el módulo 4. Aquí solo se nombra como parte del mapa.
  • (d) Una guía hermana: testing-fundamentals-and-tdd-guide. Escribir buenos tests es de la guía de fundamentos. Aquí los tests de Reservo ya existen; el trabajo es correrlos en CI.

La regla mecánica: "¿por qué se rompe / cómo lo corro automáticamente?" es esta guía; "¿cómo escribo el test?" es fundamentos; y dentro de esta guía, el concepto está en el módulo 1 y la mecánica (workflow, matriz, velocidad, puertas) en los módulos siguientes.

Resumen y siguiente paso

En esta lección instalaste el problema que sostiene los ocho módulos: tus tests solo protegen el lugar donde corren. Tu suite de Reservo vive hoy en tu máquina, corre cuando te acuerdas, y su verde solo habla de tu entorno. Eso deja tres grietas —el olvido, el aislamiento y la diferencia de entorno— por donde se cuelan los bugs más caros. La idea que las cierra es la Integración Continua: correr la suite automáticamente, en un entorno limpio y compartido, en cada cambio. Es la prueba de sonido de tu código: mueve la verificación desde tu cuarto cómodo hasta el escenario donde de verdad importa.

Volviste a ver a Reservoprice_cents, overlaps, refund_cents, dinero en centavos enteros, resultados deterministas— y su suite corriendo de verdad en local: 12 passed in 0.04s. Ese verde es el punto de partida. Todo lo que sigue es llevarlo a un pipeline para que signifique "pasa para cualquiera", no "pasa para mí".

Antes de avanzar deberías poder: explicar en una frase qué es el CI; nombrar las tres grietas de un verde local; decir por qué el dinero de Reservo va en centavos enteros; y recitar un par de números-ancla (basic 3 h → 7500, pro 3 h → 6000).

Lo que sigue es mirar la primera grieta de frente. En la lección 2 vas a ver "en mi máquina funciona" en carne viva: el mismo test, con el mismo código, dando dos veredictos opuestos —verde para ti, rojo en un entorno limpio— solo porque el entorno cambió. Con salida real, medida, no inventada. Es la escena que hace inevitable todo lo demás.

Recursos