Concepto clave
Los tests unitarios y de integración son componentes esenciales en cualquier pipeline de CI/CD moderno. Los tests unitarios verifican que cada pieza individual de tu código funcione correctamente de forma aislada, como probar que un engranaje específico en una máquina gire correctamente. Los tests de integración aseguran que esas piezas trabajen juntas sin problemas, similar a verificar que toda la línea de producción ensamble correctamente un producto.
En GitHub Actions, estos tests se ejecutan automáticamente cada vez que se realiza un cambio en el código, actuando como un sistema de control de calidad en tiempo real. Esto te permite detectar errores temprano, antes de que lleguen a producción, reduciendo costos y mejorando la confiabilidad del software. La automatización de estos tests es lo que diferencia a un pipeline profesional de uno básico.
Cómo funciona en la práctica
Imagina que desarrollas una aplicación web con Node.js. Tu pipeline avanzado debería:
- Ejecutar tests unitarios para cada función y componente individual
- Realizar tests de integración que verifiquen la interacción entre módulos
- Generar reportes de cobertura de código
- Fallar el pipeline si algún test no pasa o si la cobertura es insuficiente
Antes de la automatización, los desarrolladores ejecutaban tests manualmente, lo que era propenso a errores y omisiones. Después de implementar GitHub Actions, cada pull request activa automáticamente la ejecución completa de tests, proporcionando retroalimentación inmediata sobre la calidad del código.
Codigo en accion
Workflow básico de tests:
name: Tests Unitarios y de Integración
on:
push:
branches: [main, develop]
pull_request:
branches: [main]
jobs:
test:
runs-on: ubuntu-latest
strategy:
matrix:
node-version: [18.x, 20.x]
steps:
- uses: actions/checkout@v4
- name: Usar Node.js ${{ matrix.node-version }}
uses: actions/setup-node@v4
with:
node-version: ${{ matrix.node-version }}
cache: 'npm'
- name: Instalar dependencias
run: npm ci
- name: Ejecutar tests unitarios
run: npm test -- --coverage
- name: Ejecutar tests de integración
run: npm run test:integration
- name: Subir reporte de cobertura
uses: codecov/codecov-action@v3
with:
token: ${{ secrets.CODECOV_TOKEN }}Workflow mejorado con paralelización y cache:
name: Tests Avanzados
on:
push:
branches: [main, develop]
pull_request:
branches: [main]
jobs:
unit-tests:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Cache de dependencias
uses: actions/cache@v3
with:
path: ~/.npm
key: ${{ runner.os }}-node-${{ hashFiles('**/package-lock.json') }}
restore-keys: |
${{ runner.os }}-node-
- name: Instalar dependencias
run: npm ci
- name: Ejecutar tests unitarios
run: npm test -- --coverage --maxWorkers=2
- name: Verificar cobertura mínima
run: |
COVERAGE=$(node -e "console.log(require('./coverage/coverage-summary.json').total.lines.pct)")
if [ $(echo "$COVERAGE < 80" | bc) -eq 1 ]; then
echo 'Cobertura insuficiente: $COVERAGE%'
exit 1
fi
integration-tests:
runs-on: ubuntu-latest
needs: unit-tests
services:
postgres:
image: postgres:15
env:
POSTGRES_PASSWORD: postgres
options: >-
--health-cmd pg_isready
--health-interval 10s
--health-timeout 5s
--health-retries 5
steps:
- uses: actions/checkout@v4
- name: Instalar dependencias
run: npm ci
- name: Ejecutar tests de integración
env:
DATABASE_URL: postgres://postgres:postgres@localhost:5432/test_db
run: npm run test:integration
- name: Subir resultados
uses: actions/upload-artifact@v3
if: always()
with:
name: test-results
path: test-results/Errores comunes
- Tests que dependen del orden de ejecución: Los tests deben ser independientes. Solución: Usar bases de datos en memoria o contenedores efímeros para cada test.
- No limpiar recursos después de los tests: Dejar archivos temporales o conexiones abiertas. Solución: Implementar hooks de teardown en tu framework de testing.
- Tests lentos que retrasan el pipeline: Tests que realizan I/O innecesario. Solución: Usar mocks para servicios externos y paralelizar la ejecución.
- No verificar la cobertura de código: Ejecutar tests sin medir qué tanto código está siendo probado. Solución: Configurar un umbral mínimo de cobertura en el pipeline.
- Ignorar tests que fallan intermitentemente: Marcar tests como "skipped" en lugar de arreglarlos. Solución: Investigar y corregir la causa raíz de la intermitencia.
Checklist de dominio
- Mi pipeline ejecuta automáticamente tests unitarios y de integración en cada commit
- Los tests de integración usan servicios reales (BD, APIs) en entornos aislados
- Tengo configurado un umbral mínimo de cobertura de código (ej: 80%)
- Los jobs de tests están paralelizados para ejecución más rápida
- Los reportes de tests son accesibles y fáciles de interpretar
- El pipeline falla claramente cuando los tests no pasan
- Los tests son independientes y no tienen dependencias entre sí
Implementa un pipeline de tests para una API REST
En este ejercicio, implementarás un pipeline completo de tests para una API REST usando GitHub Actions.
- Prepara tu repositorio:
- Crea un nuevo repositorio en GitHub o usa uno existente
- Asegúrate de tener una estructura básica de API REST (puedes usar Express.js para Node.js o el framework de tu preferencia)
- Configura tests unitarios para las funciones de negocio y tests de integración para los endpoints
- Crea el workflow de GitHub Actions:
- En la carpeta
.github/workflows/, crea un archivotests.yml - Configúra el trigger para que se ejecute en push a main y en pull requests
- Implementa dos jobs separados: uno para tests unitarios y otro para tests de integración
- En la carpeta
- Configura servicios para tests de integración:
- En el job de integración, agrega un servicio de base de datos (PostgreSQL o MySQL)
- Configura variables de entorno para la conexión a la base de datos
- Asegúrate de que los tests limpien los datos después de ejecutarse
- Agrega reportes y validaciones:
- Genera reportes de cobertura de código
- Configura una validación que falle el pipeline si la cobertura es menor al 70%
- Sube los reportes como artifact para revisión posterior
- Optimiza el pipeline:
- Agrega cache para las dependencias
- Paraleliza la ejecución de tests cuando sea posible
- Configura timeouts apropiados para evitar jobs colgados
Al finalizar, tu pipeline deberá ejecutarse automáticamente y proporcionar retroalimentación clara sobre el estado de los tests.
Pistas- Usa la acción actions/cache para almacenar dependencias y acelerar ejecuciones posteriores
- Para tests de integración con base de datos, considera usar migraciones para crear el esquema en cada ejecución
- Configura el job de integración con 'needs: unit-tests' para que solo se ejecute si los unitarios pasan
Evalua tu comprension
Completa el quiz interactivo de arriba para ganar XP.