Módulo 2: Tu primer pipeline — correr pytest en CI
7. Leer el log de CI y el badge de estado
Descripción
Construiste el workflow completo y entiendes qué hace cada paso. Ahora falta la otra mitad del oficio: leer lo que produce cuando corre. Un pipeline que no sabes interpretar es un pipeline que no puedes arreglar cuando se ponga rojo —y se va a poner rojo—. Esta lección te enseña a leer el log de CI: cómo se organiza (jobs que contienen steps colapsables, un ✓ o ✗ por cada uno), dónde vive exactamente la salida de pytest dentro del step que la generó, cómo el ✗ de un step corresponde al exit code no-cero de la lección 6, y cómo la anotación del fallo te lleva directo a la línea culpable. Y cierra con el badge de estado: esa palomita verde en el README que le dice al mundo, de un vistazo, si la suite pasa.
Aquí la regla del módulo pide su matiz más cuidadoso. No levantamos un runner de GitHub, así que el log que verás es una reconstrucción honesta de cómo se ve la pestaña Actions —la estructura, los steps, las palomitas—. Pero la salida de pytest incrustada dentro de ese log es la real, la misma 11 passed que corriste de verdad en la lección 6. Es la combinación que define el módulo: el continente (el log) es "así se vería"; el contenido que importa (la salida de pytest, el exit code) es de verdad. Te lo señalo explícitamente en cada bloque para que sepas siempre qué es qué.
Conexión con el módulo: esta lección lee el resultado del workflow que armaron las lecciones 2 a 6. El ✓/✗ de cada step es la representación visual del exit code que entendiste en la lección 6 (0 → ✓, no-cero → ✗). Frontera: aquí lees el log para entender la estructura y encontrar la salida de pytest; diagnosticar a fondo un fallo —sobre todo uno que solo ocurre en CI— es el módulo 3 (y la guía hermana de diagnosis de fallos). Hoy aprendes a navegar el log y a saber qué significa cada marca; el análisis profundo de una brecha de entorno viene después.
El recibo detallado del supermercado
Piensa en el recibo de una compra grande en el supermercado. Arriba, un encabezado: la tienda, la fecha, la caja. Luego, la lista de artículos, uno por renglón, cada uno con su precio. Y abajo, en grande, el total: el número que de verdad te importa, el que miras primero para saber cuánto pagaste. Si el total te sorprende, entonces subes la vista y recorres los renglones para encontrar el artículo caro que no esperabas.
Un log de CI se lee igual, y en el mismo orden. Arriba está el encabezado del job (cuándo corrió, en qué máquina, disparado por qué). En medio, la lista de steps, uno por renglón, cada uno con su marca de ✓ (salió bien) o ✗ (falló) —son los artículos de tu compra—. Y el "total" es el estado del job entero: verde si todos los steps salieron bien, rojo si alguno falló. Cuando el job está verde, miras el total y sigues con tu vida, como con un recibo cuyo total es el esperado. Cuando está rojo, haces lo del recibo sorpresa: recorres los steps de arriba abajo, encuentras el que tiene el ✗, y lo abres para ver el detalle —el equivalente a mirar de cerca el renglón del artículo caro—. Ahí, dentro del step que falló, está la salida completa: para el step de pytest, la corrida entera con su diagnóstico.
La destreza de leer un log es esa: mirar el total primero, y cuando sea rojo, saber qué renglón abrir y qué buscar dentro. No leas los mil renglones cada vez; ve al total, y baja al detalle solo cuando el total te lo pida.
Cómo se organiza el log: jobs, steps, y las palomitas
Cuando entras a la pestaña Actions de tu repositorio y abres una corrida, ves una jerarquía de tres niveles:
- El workflow (arriba del todo): el
name: testsque le pusiste. Una corrida pertenece a un workflow. - Los jobs: en nuestro caso, uno solo,
test. Cada job tiene su propio estado (✓/✗) y corrió en su propia máquina. - Los steps, dentro del job: la lista de pasos que escribiste, más un par que GitHub agrega solo (uno de preparación al inicio, uno de limpieza al final). Cada step es colapsable: aparece como un renglón con su nombre y su ✓ o ✗, y haces clic para expandirlo y ver la salida que ese step produjo.
Ese último punto es el que más ayuda a navegar: la salida de cada comando vive dentro de su step, colapsada por defecto. El log no es un muro de texto único; es una lista de cajones, y cada cajón guarda lo que su step imprimió. Si quieres ver qué imprimió pip, abres el cajón "Install dependencies". Si quieres ver la corrida de pytest, abres el cajón del step que corre pytest. Por eso conviene nombrar los steps con name: (lección 5): un log lleno de steps llamados "Run pytest" e "Install dependencies" se navega solo; uno con steps sin nombre te obliga a adivinar cuál es cuál por su comando.
Aquí está el workflow con steps nombrados, la forma que produce un log legible —es el que usarás en el mini-proyecto—:
name: tests
on: [push, pull_request]
jobs:
test:
runs-on: ubuntu-latest
steps:
- name: Check out the code
uses: actions/checkout@v5
- name: Set up Python
uses: actions/setup-python@v5
with:
python-version: "3.14"
- name: Install dependencies
run: |
python -m pip install --upgrade pip
pip install -r requirements.txt
- name: Run the test suite
run: pytest
Cada name: de un step se convierte en el título de su cajón en el log. Los cinco pasos que escribiste, más los dos automáticos de GitHub, serán siete renglones legibles.
Ejemplo trabajado: el log de una corrida verde
Así se vería el log del job test cuando la suite pasa. Importante (regla del módulo): la estructura del log —los steps, las palomitas, los tiempos— es una reconstrucción de cómo se ve la pestaña Actions; no levantamos un runner. Pero lo que aparece dentro del cajón "Run the test suite" es la salida real de pytest, la misma 11 passed que corriste de verdad en la lección 6.
Qué esperar (así se vería el log, con la salida de pytest real incrustada):
✓ test (job: verde)
✓ Set up job 2s
✓ Check out the code 1s
✓ Set up Python 4s
✓ Install dependencies 6s
✓ Run the test suite 1s ◀── abrimos este cajón
✓ Post Set up Python 0s
✓ Complete job 0s
Los siete renglones son los steps del job, cada uno con su ✓ y el tiempo que tardó. Los cinco de en medio son los tuyos (nota los nombres que les pusiste); "Set up job" al inicio y "Complete job"/"Post ..." al final los agrega GitHub para preparar y limpiar la máquina. Todos con ✓: el job entero es verde.
Ahora abrimos el cajón "Run the test suite" —clic en ese renglón— y dentro está exactamente esto (salida real de pytest):
============================= test session starts ==============================
platform linux -- Python 3.14.0, pytest-9.1.1, pluggy-1.6.0
rootdir: /home/runner/work/reservo/reservo
collected 11 items
test_availability.py .... [ 36%]
test_pricing.py .... [ 72%]
test_refunds.py ... [100%]
============================== 11 passed in 0.03s ==============================
Esa es la conexión completa: el ✓ del step "Run the test suite" en la lista de arriba es la representación visual del 11 passed de aquí adentro, que a su vez viene del exit code 0 (lección 6). El log no inventa el color; lo hereda del número que pytest devolvió. La salida de pytest en CI es idéntica a la de tu terminal —mismo texto, mismo veredicto— porque es el mismo comando sobre la misma suite. Lo único distinto es dónde la lees: dentro de un cajón en la pestaña Actions, en vez de en tu terminal.
Ejemplo trabajado: el log de una corrida roja y la anotación
Cuando la suite falla —por ejemplo, con el bug del descuento del 25% de la lección 6— el log cambia de dos formas. Otra vez: la estructura es "así se vería"; la salida de pytest incrustada es real.
Qué esperar (así se vería el log en rojo):
✗ test (job: rojo)
✓ Set up job 2s
✓ Check out the code 1s
✓ Set up Python 4s
✓ Install dependencies 6s
✗ Run the test suite 1s ◀── el step que falló
✓ Post Set up Python 0s
✓ Complete job 0s
Fíjate en el patrón, que es información pura: la mayoría de los steps siguen en ✓; el único con ✗ es "Run the test suite". Eso te dice de un vistazo que el terreno se preparó bien —el código se trajo, Python se instaló, las dependencias se pusieron— y que el fallo está en los tests mismos, no en la infraestructura. El ✗ del step corresponde al exit code no-cero que pytest devolvió (aquí, 1). El "total" del recibo —el estado del job— es rojo porque un renglón tiene ✗.
Abrimos el cajón "Run the test suite" (el del ✗) y dentro está la corrida roja real:
test_pricing.py:17: AssertionError
=========================== short test summary info ============================
FAILED test_pricing.py::test_pro_member_gets_twenty_percent_off_the_subtotal - AssertionError: assert 5625 == 6000
========================= 1 failed, 10 passed in 0.03s =========================
El short test summary info de pytest es tu mejor amigo en un log de CI: es la lista compacta de qué falló y por qué, sin tener que leer toda la corrida. FAILED test_pricing.py::test_pro_member_... - AssertionError: assert 5625 == 6000 te da, en un renglón, el archivo, el test y el valor equivocado. Con eso ya sabes dónde mirar antes de abrir nada más.
Además, GitHub suele mostrar una anotación: un resumen del fallo destacado arriba de la corrida (y, en un pull request, junto a la línea de código correspondiente), del estilo:
test_pricing.py:17: test_pro_member_gets_twenty_percent_off_the_subtotal
AssertionError: assert 5625 == 6000
La anotación es un atajo: te lleva directo a la línea culpable (test_pricing.py:17) sin que tengas que expandir cajones ni buscar en el texto. Es el "artículo caro señalado con una flecha" del recibo. A partir de ahí, entender por qué 5625 en vez de 6000 y arreglarlo es el trabajo de diagnóstico —que cuando el fallo se reproduce igual en tu máquina es directo, y cuando solo pasa en CI es el terreno del módulo 3—.
El badge: el estado de la suite, colgado en el README
Hay una última pieza, pequeña y muy visible: el badge de estado. Es esa imagen —una etiqueta que dice tests passing en verde o tests failing en rojo— que ves arriba en el README de tantos proyectos. Comunica, de un vistazo y sin entrar a la pestaña Actions, si la suite del proyecto está pasando ahora mismo en la rama principal.
El badge no es una imagen que subes; es una imagen viva que GitHub genera y actualiza solo. Cada workflow tiene una URL de badge con esta forma:
https://github.com/<owner>/<repo>/actions/workflows/tests.yml/badge.svg
Cambias <owner> por tu usuario u organización, <repo> por el nombre del repositorio, y tests.yml por el nombre de tu archivo de workflow. Esa URL devuelve una imagen SVG que refleja el último estado del workflow: verde si la última corrida en la rama por defecto pasó, rojo si falló. Para colgarlo en el README, lo pones como una imagen de Markdown, normalmente enlazada a la pestaña Actions:
[](https://github.com/<owner>/<repo>/actions/workflows/tests.yml)
Léelo por partes:  es la imagen del badge (el tests es el texto alternativo); envolverlo en [...](...actions/workflows/tests.yml) lo convierte en un enlace, de modo que quien haga clic en el badge llega directo a las corridas del workflow. Qué esperar en el README renderizado: una etiquetita que dice tests seguida de passing en verde (o failing en rojo), que se actualiza sola cada vez que el workflow corre.
Para qué sirve, más allá de lo decorativo: el badge es la señal pública de salud del proyecto. Un colaborador que llega al repo ve el badge verde y sabe, sin investigar, que la suite pasa y que puede confiar en el estado de la rama principal. Un badge rojo es una alarma visible para todo el que entre: algo está roto en main y hay que atenderlo. Es el "total del recibo" del proyecto entero, puesto en la portada.
Errores comunes
Leer el log de arriba abajo entero en vez de ir al step con ✗ (de lectura sin estrategia). Qué pasa: alguien abre un CI rojo y empieza a leer desde la primera línea, cajón por cajón, perdiéndose en la salida de pip y del setup antes de llegar al fallo. Por qué pasa: uno trata el log como un texto lineal en vez de como una lista de cajones con marcas. Cómo detectarlo: si llevas rato leyendo salida de steps que están en ✓, estás en el lugar equivocado. Cómo corregirlo: mira primero las marcas —ve directo al step con ✗—, ábrelo, y dentro busca el short test summary info de pytest. Los steps en ✓ no necesitan tu atención; el ✗ es el que la pide. Lee el total, baja al renglón señalado.
No nombrar los steps y navegar un log de cajones anónimos (de log ilegible). Qué pasa: alguien escribe todos los steps sin name:, y en el log cada cajón se titula con su comando crudo o un genérico, volviendo difícil saber cuál abrir. Por qué pasa: nombrar los steps parece opcional cuando escribes el YAML, y lo es —hasta que tienes que leer el log—. Cómo detectarlo: si en tu log no distingues "instalar dependencias" de "correr tests" sin leer cada comando, te faltan nombres. Cómo corregirlo: pon name: a cada step con una descripción clara ("Install dependencies", "Run the test suite"). El nombre que escribes una vez se convierte en el título del cajón que lees muchas veces. Es cortesía con tu yo futuro y con tu equipo.
Confundir el badge con "el estado de mi rama actual" (de alcance del badge). Qué pasa: alguien mira el badge verde del README mientras trabaja en una rama con tests rotos, y cree que su rama está bien. Por qué pasa: el badge, por defecto, refleja el estado del workflow en la rama por defecto (main), no en la rama donde estás parado. Cómo detectarlo: si tu rama de trabajo tiene el CI en rojo pero el badge del README sigue verde, no es una contradicción: el badge habla de main, tu rama es otra cosa. Cómo corregirlo: recuerda que el badge reporta la rama por defecto salvo que le pidas otra (la URL admite un parámetro de rama). Para saber cómo va tu rama, mira la corrida de tu rama en la pestaña Actions, no el badge del README.
Ejercicios
Ejercicio 1 — Ubica el fallo en el log. Te muestran esta lista de steps de un job rojo. Sin ver nada más, di: ¿en qué step está el problema?, ¿qué te dice sobre la infraestructura el hecho de que los steps anteriores estén en ✓?, y ¿qué cajón abrirías y qué buscarías dentro?
✗ test
✓ Set up job
✓ Check out the code
✓ Set up Python
✗ Install dependencies
⊘ Run the test suite (no se ejecutó)
Ver solución
- El problema está en "Install dependencies" (el único con ✗).
- Sobre la infraestructura: que "Check out the code" y "Set up Python" estén en ✓ me dice que el código sí llegó al runner y que Python 3.14 se instaló bien. El fallo no es de esos pasos; es específicamente de instalar las dependencias —quizá un paquete que no existe en la versión pedida, un
requirements.txtcon un error, o un problema de red bajando un paquete—. - Qué abriría: el cajón "Install dependencies", y dentro buscaría la salida de pip —específicamente un
ERROR:o "Could not find a version that satisfies the requirement...", que es como pip reporta que no pudo resolver o instalar algo—.
Detalle clave: "Run the test suite" aparece con ⊘ (no se ejecutó), no con ✗. Cuando un step falla, los steps siguientes no corren —el job se detiene en el fallo—. Por eso pytest ni siquiera llegó a ejecutarse: no tenía sentido correr los tests si las dependencias no se instalaron. El fallo está aguas arriba de pytest, y arreglarlo (el requirements.txt) es lo que desbloquea el resto.
Ejercicio 2 — Escribe el badge. Tu usuario de GitHub es ana-dev, tu repositorio se llama reservo, y tu workflow está en .github/workflows/tests.yml. Escribe la línea de Markdown que cuelga el badge en el README, enlazado a la pestaña de corridas del workflow.
Ver solución
[](https://github.com/ana-dev/reservo/actions/workflows/tests.yml)
Desglose:
— la imagen del badge;testses el texto alternativo (lo que se lee si la imagen no carga).- La URL de la imagen:
https://github.com/ana-dev/reservo/actions/workflows/tests.yml/badge.svg— con tu owner (ana-dev), tu repo (reservo) y tu archivo de workflow (tests.yml), terminando en/badge.svg. - El
[...](...)que envuelve la imagen la convierte en un enlace haciahttps://github.com/ana-dev/reservo/actions/workflows/tests.yml, la página de corridas del workflow. Así, quien haga clic en el badge ve el historial de corridas.
Renderizado, el README mostrará una etiquetita tests | passing en verde (o failing en rojo) que se actualiza sola con cada corrida en main, y que lleva a la pestaña Actions al hacer clic.
Ejercicio 3 — Verde en el badge, rojo en tu rama. Un compañero está desconcertado: el badge del README dice tests passing en verde, pero la corrida de su rama add-discount-tier está en rojo en la pestaña Actions. Cree que el CI se contradice. Explícale por qué las dos cosas son ciertas a la vez.
Ver solución
No hay contradicción: el badge y la corrida de su rama hablan de ramas distintas.
- El badge del README, por defecto, refleja el estado del workflow en la rama por defecto del repositorio (normalmente
main). Verde en el badge significa "la última corrida enmainpasó" —ymainpuede estar perfectamente sana—. - La corrida roja que ve en la pestaña Actions es la de su rama
add-discount-tier, donde está trabajando y donde, evidentemente, algún test falla todavía.
Las dos son ciertas: main está verde (por eso el badge lo está) y su rama de trabajo está roja (por eso su corrida lo está). Es exactamente el estado normal de un trabajo en curso: rompes algo en tu rama, el CI de tu rama se pone rojo y te avisa, y main sigue protegida y verde porque tu cambio aún no se fusionó. Cuando arregle los tests de su rama y la fusione, entonces sí el badge (que mira main) reflejará el estado con su cambio dentro.
La moraleja: para saber cómo va tu rama, mira la corrida de tu rama en Actions; el badge del README te habla de main, no de donde estás parado.
Resumen y siguiente paso
En esta lección aprendiste a leer el resultado del pipeline que construiste. Un log de CI se lee como un recibo: primero el "total" (el estado del job, verde o rojo), y cuando es rojo, bajas al renglón señalado. Entendiste su estructura de tres niveles —workflow → jobs → steps colapsables—, que la salida de cada comando vive dentro de su step (por eso conviene nombrarlos con name:), y que el ✓/✗ de cada step es la representación visual del exit code de la lección 6 (0 → ✓, no-cero → ✗). Viste el log verde con la salida real de pytest incrustada (11 passed), el log rojo donde solo el step de pytest tiene ✗ —revelando que el fallo está en los tests, no en la infraestructura—, y cómo el short test summary info y la anotación te llevan directo a la línea culpable. Y colgaste el badge: la imagen viva (badge.svg) que refleja el estado del workflow en main y comunica la salud del proyecto de un vistazo.
Mantuvimos la honestidad del módulo con precisión: la estructura del log es "así se vería" (no levantamos un runner), pero la salida de pytest dentro de él es la real que corriste. Esa es la combinación que hace el módulo confiable.
Antes de avanzar deberías poder: navegar un log yendo directo al step con ✗; explicar por qué nombrar los steps hace legible el log; encontrar el short test summary info dentro del cajón de pytest; escribir la línea de Markdown del badge; y distinguir qué rama reporta el badge de la rama donde trabajas.
Tienes todas las piezas: sabes escribir el workflow completo, entiendes cada step, conoces el exit code, y sabes leer el log y colgar el badge. Lo que sigue es juntarlo todo con tus propias manos. En la lección 8, el mini-proyecto: escribes desde cero el tests.yml que corre la suite de Reservo en cada push, con steps nombrados; estableces la paridad local corriendo en tu máquina los mismos comandos que el CI y pegando la salida verde real; y redactas la nota de qué cubre tu pipeline y qué queda para los módulos siguientes. Es el examen práctico del módulo.
Recursos
- Ver el historial de ejecuciones de workflows — cómo navegar la pestaña Actions, abrir una corrida, y expandir los steps para ver su salida. La referencia oficial de la parte "leer el log" de esta lección.
- Añadir un badge de estado del workflow — la guía oficial de cómo construir la URL del badge, enlazarlo, y apuntarlo a una rama o evento específico. La fuente exacta de la sección del badge.
- Cómo invocar pytest — salida y reportes — cómo pytest formatea su salida, incluido el
short test summary infoque resulta tan útil en un log de CI, y cómo controlar cuánto detalle imprime. Para afinar qué ves dentro del cajón de pytest.