Módulo 2: Tu primer pipeline — correr pytest en CI
4. Los steps que preparan el terreno: checkout y setup-python
Descripción
Ya tienes el cuándo (on:) y el dónde (runs-on:). Esta lección empieza el qué: los pasos. Y empieza por los dos que la mayoría de los principiantes olvida, porque parecen "obvios" y no lo son —el runner no sabe nada de ti hasta que se los das—. Al terminar vas a entender por qué el runner arranca completamente vacío, qué hacen exactamente actions/checkout (traer tu código a la máquina) y actions/setup-python (instalar el Python que pediste), cómo se le pasan parámetros a una acción con with:, y por qué el orden de estos pasos no es negociable.
Estos dos steps son los cimientos. Sin ellos, el pytest de la lección 6 no tendría ni código que probar ni el Python correcto con qué probarlo. Son tan de rutina que aparecen casi idénticos en cualquier workflow de Python del mundo, y esa familiaridad es una ventaja: aprenderlos bien una vez es aprenderlos para siempre. La regla del módulo sigue vigente —el workflow es contenido que explicamos—; pero entender qué hace cada paso te deja escribirlo con criterio en vez de copiarlo a ciegas.
Conexión con el módulo: en la lección 2 clasificaste estos steps como de tipo uses (traen acciones reutilizables) sin abrirlos; aquí los abrimos. Preparan el terreno que las lecciones siguientes usan: la lección 5 instala las dependencias sobre el Python que setup-python puso, y la lección 6 corre pytest sobre el código que checkout trajo. Mantente en la frontera: por qué el runner puede diferir de tu máquina y qué hacer cuando el CI falla y tu local pasa es el módulo 3; correr en varias versiones de Python a la vez (la matriz) es el módulo 4. Aquí ponemos una versión, en una máquina, bien.
Un cuarto de hotel vacío que tú equipas
Imagina que reservas un cuarto de hotel para trabajar un fin de semana en un proyecto. Cuando llegas, el cuarto está impecable... y vacío de tus cosas. Tiene una cama, un escritorio, electricidad —la infraestructura básica—, pero no tiene tu computadora, ni tus documentos, ni las herramientas específicas de tu proyecto. Nadie dejó tus archivos ahí; ¿por qué lo harían? Es un cuarto que se limpia por completo entre huéspedes, precisamente para que lo que hizo el anterior no te afecte a ti.
Para poder trabajar, haces dos cosas nada más entrar. Primero, sacas tus documentos —el material del proyecto— y los pones sobre el escritorio. Sin ellos no tienes en qué trabajar. Segundo, si tu proyecto necesita una herramienta particular que el cuarto no trae —digamos, una lámpara de cierta luz para revisar planos—, la instalas. Solo después de esos dos pasos el cuarto vacío se convierte en tu espacio de trabajo.
El runner de GitHub es ese cuarto de hotel. Arranca limpio y vacío de lo tuyo —una máquina recién formateada, con el sistema operativo y utilidades generales, pero sin tu código y sin garantía de tener la versión de Python que necesitas—. Y se limpia por completo entre corridas, a propósito, para que ninguna corrida contamine a la siguiente (esa limpieza es la que da la reproducibilidad del módulo 1: cada corrida empieza igual de fresca). Los dos primeros pasos de tu workflow son sacar tus documentos y montar tu herramienta: checkout trae tu código, setup-python instala el Python que pides. Sin esos dos, el cuarto sigue vacío y no hay nada que probar.
actions/checkout: traer tu código al runner
- uses: actions/checkout@v5
Este es el primer paso de casi todo workflow, y hace una sola cosa fundamental: descarga tu repositorio dentro del runner. Antes de este step, la máquina no tiene ni un archivo tuyo —ni reservo/, ni los test_*.py, ni el requirements.txt—. Después de este step, todo tu proyecto está ahí, en el estado exacto del commit que disparó el workflow, listo para que los pasos siguientes lo usen.
El nombre lo dice: "checkout" es el término de Git para "pon el árbol de trabajo en este commit". La acción hace precisamente eso en la máquina limpia: clona tu repo y lo deja en el commit correspondiente. Fíjate en lo que esto implica sobre qué se prueba: el CI no prueba lo que tienes en tu disco local ni lo último de main, prueba el código del commit que disparó la corrida. Si hiciste push de un commit con un bug, checkout trae ese commit con el bug, y por eso el CI lo atrapa. Es la conexión directa entre "subí esto" y "el CI probó exactamente esto".
Es una acción oficial de GitHub —el prefijo actions/ señala que la publica la organización actions, mantenida por GitHub—, así que puedes confiar en ella sin pensarlo. No la escribes tú; la usas. Ese es todo el punto de uses: traer piezas probadas en vez de reimplementar cómo se clona un repo en una máquina.
actions/setup-python: poner el Python correcto
- uses: actions/setup-python@v5
with:
python-version: "3.14"
El runner de Ubuntu trae algún Python de fábrica, pero no debes confiar en cuál: puede ser una versión distinta a la que tu proyecto necesita, y depender de "la que venga" es justo el tipo de suposición frágil que el CI existe para eliminar. actions/setup-python resuelve eso: instala y deja activa la versión exacta de Python que le pidas, para que los pasos siguientes —instalar dependencias, correr pytest— usen esa y no otra.
Aquí aparece algo nuevo: el bloque with:. Una acción como checkout funciona sin configuración —"trae el código", no hay nada que ajustar—, pero setup-python necesita que le digas qué versión. El with: es cómo se le pasan parámetros a una acción: es un mapa (recuerda el YAML de la lección 2) de opciones que la acción entiende. Aquí le pasamos una, python-version: "3.14", que significa "instala Python 3.14". Léelo como llenar un formulario: la acción tiene campos, y with: los rellena.
Dos detalles sobre python-version que evitan sorpresas:
- Las comillas alrededor de
"3.14"importan. En YAML, sin comillas,3.14se interpreta como el número decimal tres-punto-catorce, y3.10se interpretaría como tres-punto-uno (¡el cero final se pierde en un número!), pidiendo Python 3.1 en vez de 3.10. Poner la versión entre comillas la trata como el texto"3.10"y evita ese clásico tropiezo. Acostúmbrate a escribirpython-versionsiempre entre comillas. - Puedes pedir la versión con el detalle que quieras.
"3.14"toma la última publicación de la serie 3.14;"3.14.0"fija el parche exacto. Para el CI de este módulo,"3.14"está bien: pedimos la serie que la guía usa y dejamos que el runner traiga su último parche. Fijar el parche exacto es una decisión de determinismo más fina que roza el módulo 3.
Ejemplo trabajado: los dos steps, en orden, con lo que dejan listo
Aquí están los dos primeros pasos del workflow, aislados para verlos solos:
jobs:
test:
runs-on: ubuntu-latest
steps:
# Paso 1: traer el código del commit que disparó la corrida.
- uses: actions/checkout@v5
# Paso 2: instalar y activar Python 3.14 en el runner.
- uses: actions/setup-python@v5
with:
python-version: "3.14"
# ...los pasos de instalar dependencias y correr pytest siguen aquí
Recuerda: no ejecutamos un CI real. Pero podemos describir con exactitud qué deja listo cada paso, porque es determinista. Qué esperar (el estado del runner después de cada paso):
Estado inicial del runner: máquina Ubuntu limpia, sin tu código,
con algún Python de fábrica (no garantizado).
Después del paso 1 (checkout):
/home/runner/work/reservo/reservo/
├── reservo/ ← tu código ya está aquí
│ ├── models.py
│ ├── pricing.py
│ └── ...
├── test_pricing.py
├── test_refunds.py
└── requirements.txt
Después del paso 2 (setup-python 3.14):
$ python --version
Python 3.14.0 ← la versión que pediste, activa
Esa ruta /home/runner/work/reservo/reservo/ es la carpeta de trabajo real de un runner de GitHub; la ponemos para que reconozcas el formato cuando la veas en un log de CI (lección 7). Lo esencial del bloque: tras estos dos pasos, el cuarto de hotel dejó de estar vacío. Hay código sobre el escritorio (checkout) y la herramienta correcta montada (Python 3.14). Ahora sí, los pasos siguientes tienen sobre qué trabajar.
Puedes verificar la mitad "de verdad" de esto en tu propia máquina: python3 --version te dice qué Python tienes activo, igual que setup-python lo dejaría en el runner. Cuando en la lección 6 corras la suite en local con Python 3.14.0, estarás en el mismo estado que este runner tras el paso 2 —código presente, Python correcto— y por eso tu salida de pytest será la que el CI vería.
El orden no es negociable
Fíjate en la secuencia: checkout primero, setup-python después, y las dos antes que cualquier pip install o pytest. Ese orden tiene una lógica de dependencias que conviene hacer explícita, porque romperlo produce fallos confusos.
- Checkout va primero porque todo lo demás necesita tu código. No puedes instalar
requirements.txtsi el archivo no está en la máquina, y no puedes correrpytestsobrereservo/sireservo/no existe todavía. Si pusieraspip install -r requirements.txtantes del checkout, fallaría con "no such file or directory": el archivo aún no ha sido traído. - Setup-python va antes de instalar y de probar porque
pipypytestcorren sobre un Python concreto. Si instalas dependencias antes de fijar la versión, las instalas sobre el Python de fábrica del runner —quizá el equivocado—, y luegosetup-pythoncambia el Python activo y tus paquetes "desaparecen" (quedaron en el otro Python). El síntoma es un desconcertante "no module named pytest" justo después de haberlo instalado.
La regla mnemotécnica: primero el terreno (código y Python), luego lo que se apoya en él (dependencias y tests). Cada paso siguiente asume que los anteriores ya ocurrieron. Los steps se ejecutan de arriba a abajo por diseño, y ese orden es tu herramienta para expresar "esto depende de aquello".
Una nota honesta sobre las versiones de las acciones (@v5)
El @v5 en actions/checkout@v5 y actions/setup-python@v5 fija la versión mayor de la acción que usas. Es el mismo @version que viste en la lección 2, y cumple aquí el papel que cumple pinnear cualquier dependencia: garantiza que mañana uses la misma pieza que hoy, sin sorpresas por una actualización que no pediste.
Ahora, la parte honesta: las acciones oficiales publican versiones mayores nuevas cada cierto tiempo. Cuando leas esto, es muy posible que checkout y setup-python ya vayan por una versión mayor más alta que v5 (las acciones de GitHub han ido avanzando: v5, v6, y más allá). Eso no invalida nada de lo que aprendiste; el comportamiento de estos steps —traer el código, poner Python— es estable entre versiones mayores. Lo que cambia son detalles internos (la versión de Node que la acción usa por debajo, ajustes de rendimiento).
El hábito correcto, entonces, no es memorizar un número, sino esto: fija siempre una versión mayor con @vN, y consulta la página de la acción en el Marketplace de GitHub para saber cuál es la vigente. Nunca uses una acción sin @version (quedarías a merced de cambios) ni pinnees a algo tan viejo que ya no reciba mantenimiento. En esta guía usamos @v5 como una versión concreta y válida para ilustrar; en tu proyecto, mira la página de actions/checkout y actions/setup-python y usa la mayor que recomienden. La lección no es el 5; es el hábito de pinnear la mayor y revisar cuál es.
Errores comunes
Olvidar el checkout y correr pytest sobre la nada (de terreno ausente). Qué pasa: alguien escribe un workflow que salta directo a setup-python y pytest, sin checkout, y el step de pytest falla con "no tests ran" o "no such file", porque el código nunca llegó al runner. Por qué pasa: en tu máquina el código "siempre está ahí", así que uno olvida que en el runner limpio no está hasta que lo traes. Cómo detectarlo: si pytest en CI reporta que no encontró nada, o pip install -r requirements.txt no halla el archivo, sospecha de un checkout ausente. Cómo corregirlo: el actions/checkout es el primer paso de prácticamente todo workflow; ponlo siempre, y siempre primero. Es sacar tus documentos antes de intentar trabajar.
Instalar dependencias antes de setup-python y perder los paquetes (de orden invertido). Qué pasa: alguien pone pip install antes del setup-python, instala sobre el Python de fábrica del runner, y luego setup-python activa otra versión donde esos paquetes no existen; el pytest posterior falla con "no module named pytest". Por qué pasa: uno piensa en "instalar" como un paso independiente, sin notar que instala sobre un Python específico. Cómo detectarlo: un "no module named X" justo después de un pip install aparentemente exitoso es la firma de este error. Cómo corregirlo: setup-python va antes de cualquier pip install, para que las dependencias se instalen sobre el Python correcto y activo. Primero fija el Python, luego instala sobre él.
Escribir python-version: 3.10 sin comillas y pedir Python 3.1 sin querer (de YAML numérico). Qué pasa: alguien pone python-version: 3.10 sin comillas; YAML lo lee como el número 3.1 (el cero final de un decimal no significa nada numéricamente), y setup-python intenta instalar Python 3.1, que no existe en las versiones modernas, fallando o trayendo algo inesperado. Por qué pasa: sin comillas, YAML trata 3.10 como número, y 3.10 == 3.1 como números. Cómo detectarlo: si setup-python se queja de una versión de Python rara o inexistente, revisa si te faltan las comillas. Cómo corregirlo: pon python-version siempre entre comillas —"3.10", "3.14"—, para que se trate como texto y la versión llegue tal cual la escribiste.
Ejercicios
Ejercicio 1 — Ordena los pasos y justifica. Aquí están cuatro steps del workflow, desordenados. Ponlos en el orden correcto y explica, para al menos dos pares consecutivos, por qué uno debe ir antes del otro.
(a) - run: pytest
(b) - uses: actions/setup-python@v5
with:
python-version: "3.14"
(c) - uses: actions/checkout@v5
(d) - run: pip install -r requirements.txt
Ver solución
El orden correcto es (c) → (b) → (d) → (a): checkout, setup-python, pip install, pytest.
- uses: actions/checkout@v5 # (c)
- uses: actions/setup-python@v5 # (b)
with:
python-version: "3.14"
- run: pip install -r requirements.txt # (d)
- run: pytest # (a)
Por qué el orden importa, en los pares:
- (c) antes de (d):
pip install -r requirements.txtnecesita que el archivorequirements.txtexista en la máquina, y ese archivo llega con el checkout. Sin checkout primero, pip no encuentra el archivo. - (b) antes de (d):
pip installinstala sobre un Python concreto; si instalas antes de fijar la versión consetup-python, los paquetes van a parar al Python de fábrica y "desaparecen" cuando setup-python activa otro. Fijar el Python primero garantiza que las dependencias queden donde pytest las buscará. - (d) antes de (a):
pytestes una dependencia que se instala en (d); correrlo antes de instalarlo daría "no module named pytest".
La regla que resume todo: primero el terreno (código con checkout, Python con setup-python), luego lo que se apoya en él (dependencias, tests).
Ejercicio 2 — Detecta el error de versión. Un compañero quiere correr sus tests en Python 3.12 y escribe esto. La corrida falla diciendo que no puede encontrar Python 3.1. ¿Qué está mal y cómo se arregla?
- uses: actions/setup-python@v5
with:
python-version: 3.12
Ver solución
El problema son las comillas ausentes en la versión. Sin comillas, YAML interpreta 3.12 como el número decimal tres-punto-doce... que numéricamente es lo mismo que 3.12, pero el peligro real aparece con ceros finales y con cómo algunas herramientas normalizan el número. El caso de libro es 3.10 sin comillas, que YAML lee como 3.1 (el cero final de un decimal no cambia el valor numérico), pidiendo Python 3.1. Para 3.12 el riesgo es análogo según cómo se procese el número; la solución es la misma y elimina toda ambigüedad: tratar la versión como texto.
Corregido:
- uses: actions/setup-python@v5
with:
python-version: "3.12"
Con las comillas, "3.12" es la cadena literal 3.12 y setup-python la recibe tal cual. La regla práctica —escribir python-version siempre entre comillas— evita esta clase entera de errores sin tener que razonar caso por caso qué número normaliza YAML y cuál no.
Ejercicio 3 — Explica el cuarto vacío. Un compañero pregunta: "Si el runner ya tiene Ubuntu y trae Python, ¿para qué necesito el paso de checkout y el de setup-python? ¿No debería simplemente correr mis tests?". Respóndele con la analogía del cuarto de hotel y con el detalle técnico de qué falta sin cada paso.
Ver solución
Una respuesta que cubre las dos capas:
El runner es como un cuarto de hotel recién limpiado: tiene la infraestructura (Ubuntu, electricidad, un Python de fábrica) pero está vacío de tus cosas. Se limpia por completo entre corridas —a propósito, para que ninguna afecte a otra—, así que no tiene ni tu código ni garantía del Python que necesitas.
- Sin
checkout, tu código no está en la máquina.pytestno tendría qué probar (reservo/no existe ahí) ypip install -r requirements.txtno encontraría el archivo. Checkout es "sacar tus documentos y ponerlos sobre el escritorio". - Sin
setup-python, correrías sobre el Python que Ubuntu trae de fábrica, que puede ser una versión distinta a la que tu proyecto necesita. Depender de "la que venga" es exactamente la suposición frágil que el CI existe para eliminar. Setup-python es "montar la herramienta correcta", fijando la versión exacta.
Y el matiz importante: que el runner tenga un Python no significa que tenga el correcto. "Simplemente correr mis tests" funcionaría por casualidad si el Python de fábrica coincidiera con el tuyo y no tuvieras dependencias —pero un pipeline se construye sobre garantías, no casualidades—. Los dos pasos convierten el cuarto vacío en un espacio de trabajo idéntico y reproducible en cada corrida.
Resumen y siguiente paso
En esta lección pusiste los cimientos del qué. Entendiste que el runner arranca como un cuarto de hotel vacío —limpio a propósito, sin tu código y sin garantía del Python correcto— y que dos pasos lo equipan. actions/checkout trae tu repositorio al runner, en el estado exacto del commit que disparó la corrida (por eso el CI prueba justo lo que subiste). actions/setup-python, con su bloque with: python-version:, instala y activa la versión de Python que pides —siempre entre comillas, para que YAML no la lea como número—. Viste por qué el orden es inamovible (checkout primero porque todo necesita el código; setup-python antes de instalar porque pip y pytest corren sobre un Python concreto) y la regla honesta sobre el @v5: fija siempre una versión mayor y revisa en la página de la acción cuál es la vigente, porque estas acciones avanzan con el tiempo.
Antes de avanzar deberías poder: explicar por qué el runner necesita checkout y setup-python; escribir un setup-python con la versión entre comillas; ordenar correctamente checkout, setup-python, install y pytest justificando cada dependencia; y describir qué falla si inviertes el orden.
El terreno está listo: hay código en la máquina y el Python correcto activo. Falta lo que se apoya en él. En la lección 5 instalamos las dependencias: verás por qué el runner tiene Python pero no tus paquetes, cómo requirements.txt es la lista de lo que hace falta, y cómo python -m pip install --upgrade pip y pip install -r requirements.txt la instalan sobre el Python que acabas de fijar. Esta vez sí hay una parte que corre de verdad —la salida real de pip— porque instalar dependencias es algo que puedes reproducir en tu propia terminal.
Recursos
- actions/checkout en el Marketplace de GitHub — la página oficial de la acción de checkout, con su versión mayor vigente y sus opciones. Este es el lugar exacto donde revisas "¿cuál es el
@vNactual?", justo como recomienda la nota honesta de la lección. - actions/setup-python en el Marketplace de GitHub — la página oficial de setup-python, con todas las opciones de
with:(versiones disponibles, caché, archivos de versión). La referencia para configurar el Python del runner más allá depython-version. - Build and test Python (documentación de GitHub Actions) — la guía oficial de GitHub para probar proyectos de Python, con el workflow de ejemplo que usa estos mismos dos pasos. Útil para ver el patrón completo tal como GitHub lo documenta.