Implementa tests unitarios y de integración en tu pipeline

Video
25 min~5 min lectura

Reproductor de video

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:

  1. Ejecutar tests unitarios para cada función y componente individual
  2. Realizar tests de integración que verifiquen la interacción entre módulos
  3. Generar reportes de cobertura de código
  4. 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

  1. Mi pipeline ejecuta automáticamente tests unitarios y de integración en cada commit
  2. Los tests de integración usan servicios reales (BD, APIs) en entornos aislados
  3. Tengo configurado un umbral mínimo de cobertura de código (ej: 80%)
  4. Los jobs de tests están paralelizados para ejecución más rápida
  5. Los reportes de tests son accesibles y fáciles de interpretar
  6. El pipeline falla claramente cuando los tests no pasan
  7. 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.

  1. 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
  2. Crea el workflow de GitHub Actions:
    • En la carpeta .github/workflows/, crea un archivo tests.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
  3. 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
  4. 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
  5. 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.