Voltar para Artigos
DevOps10 min de leitura

Integração Contínua (CI): Automatizando Testes com Github Actions

Pipeline de CI completo para Node.js: cache de dependências, jobs paralelos para lint/test/typecheck, services para banco de dados de teste, matrix strategy e Branch Protection Rules.

12 de agosto de 2026

Integração Contínua (CI) é a prática de verificar automaticamente cada mudança de código antes de integrá-la à branch principal. O objetivo é detectar problemas o mais cedo possível — quando ainda é barato corrigir. Um bug encontrado em um PR leva 5 minutos para corrigir; o mesmo bug em produção pode levar horas e impactar usuários reais.

O GitHub Actions transformou CI em algo acessível: gratuito para repositórios públicos, 2.000 minutos/mês gratuitos para privados, e configurado diretamente no repositório via arquivos YAML. Neste artigo vamos montar um pipeline profissional com cache de dependências, jobs paralelos, banco de dados de teste via services, e proteção de branch.

Conceitos Fundamentais do GitHub Actions

  • Workflow — arquivo YAML em .github/workflows/. Define quando e o que executar.
  • Trigger (on) — evento que dispara o workflow: push, pull_request, schedule, workflow_dispatch (manual)
  • Job — unidade de execução isolada. Cada job roda em uma VM separada (runner). Jobs são paralelos por padrão.
  • Step — tarefa dentro de um job. Executados sequencialmente dentro do job.
  • Action — passo reutilizável do marketplace (ex: actions/checkout, actions/setup-node)
  • Runner — a VM onde o job roda. ubuntu-latest é a opção mais comum e gratuita.

Pipeline Básico com Cache

ci.yml
name: CI

on:
  push:
    branches: [main, develop]
  pull_request:
    branches: [main, develop]

# Cancela runs anteriores do mesmo PR (economiza minutos)
concurrency:
  group: ${{ github.workflow }}-${{ github.ref }}
  cancel-in-progress: true

jobs:
  ci:
    name: Lint, Typecheck e Testes
    runs-on: ubuntu-latest

    steps:
      - name: Checkout
        uses: actions/checkout@v4

      - name: Configurar pnpm
        uses: pnpm/action-setup@v4
        with:
          version: 9

      - name: Configurar Node.js
        uses: actions/setup-node@v4
        with:
          node-version: 22
          # Cache do pnpm store — evita baixar dependências em cada run
          cache: 'pnpm'

      - name: Instalar dependências
        # --frozen-lockfile: falha se o lockfile estiver desatualizado
        # Garante que o CI usa exatamente as versões do pnpm-lock.yaml
        run: pnpm install --frozen-lockfile

      - name: Typecheck
        run: pnpm typecheck

      - name: Lint
        run: pnpm lint

      - name: Testes unitários
        run: pnpm test --coverage

      - name: Upload de cobertura para Codecov
        uses: codecov/codecov-action@v4
        with:
          token: ${{ secrets.CODECOV_TOKEN }}
          files: ./coverage/lcov.info

Jobs Paralelos: Reduzindo o Tempo do Pipeline

Quando lint, typecheck e testes são sequenciais, um falha na etapa anterior bloqueia as outras e você não sabe quais outros problemas existem. Com jobs paralelos, todos rodam simultaneamente e você vê todos os erros de uma vez:

ci.yml (jobs paralelos)
jobs:
  typecheck:
    name: TypeScript
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - uses: pnpm/action-setup@v4
      - uses: actions/setup-node@v4
        with: { node-version: 22, cache: 'pnpm' }
      - run: pnpm install --frozen-lockfile
      - run: pnpm typecheck

  lint:
    name: ESLint
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - uses: pnpm/action-setup@v4
      - uses: actions/setup-node@v4
        with: { node-version: 22, cache: 'pnpm' }
      - run: pnpm install --frozen-lockfile
      - run: pnpm lint

  test:
    name: Jest
    runs-on: ubuntu-latest
    needs: [] # Não depende de nada — roda em paralelo com typecheck e lint
    steps:
      - uses: actions/checkout@v4
      - uses: pnpm/action-setup@v4
      - uses: actions/setup-node@v4
        with: { node-version: 22, cache: 'pnpm' }
      - run: pnpm install --frozen-lockfile
      - run: pnpm test --coverage

  # Job de build depende dos três anteriores
  build:
    name: Build
    runs-on: ubuntu-latest
    needs: [typecheck, lint, test] # Só roda se os três passarem
    steps:
      - uses: actions/checkout@v4
      - uses: pnpm/action-setup@v4
      - uses: actions/setup-node@v4
        with: { node-version: 22, cache: 'pnpm' }
      - run: pnpm install --frozen-lockfile
      - run: pnpm build

Services: Banco de Dados Real nos Testes de Integração

Testes de integração que testam a camada de repositório precisam de um banco de dados real. O GitHub Actions suporta service containers — instâncias Docker que sobem junto com o job:

integration-tests.yml
jobs:
  integration:
    name: Testes de Integração
    runs-on: ubuntu-latest

    # Containers de serviço que ficam disponíveis durante o job
    services:
      postgres:
        image: postgres:16-alpine
        env:
          POSTGRES_USER: test
          POSTGRES_PASSWORD: test
          POSTGRES_DB: testdb
        ports:
          - 5432:5432
        # Aguarda o PostgreSQL estar pronto antes de prosseguir
        options: >-
          --health-cmd pg_isready
          --health-interval 10s
          --health-timeout 5s
          --health-retries 5

      redis:
        image: redis:7-alpine
        ports:
          - 6379:6379
        options: --health-cmd "redis-cli ping" --health-interval 10s --health-timeout 5s --health-retries 5

    env:
      # Variáveis de ambiente para os testes
      DATABASE_URL: postgresql://test:test@localhost:5432/testdb
      REDIS_URL: redis://localhost:6379
      JWT_SECRET: test-secret-only-for-ci
      NODE_ENV: test

    steps:
      - uses: actions/checkout@v4
      - uses: pnpm/action-setup@v4
      - uses: actions/setup-node@v4
        with: { node-version: 22, cache: 'pnpm' }
      - run: pnpm install --frozen-lockfile

      - name: Rodar migrations
        run: pnpm typeorm migration:run

      - name: Testes de integração
        run: pnpm test:integration

Matrix Strategy: Testando em Múltiplas Versões

Para pacotes npm ou projetos que precisam suportar múltiplas versões do Node.js, use strategy.matrix para rodar o job em paralelo com versões diferentes:

compatibility.yml
jobs:
  test:
    runs-on: ubuntu-latest
    strategy:
      # Não cancela todas as versões se uma falhar
      fail-fast: false
      matrix:
        node-version: [20, 22, 23]

    name: Node.js ${{ matrix.node-version }}
    steps:
      - uses: actions/checkout@v4
      - uses: actions/setup-node@v4
        with:
          node-version: ${{ matrix.node-version }}
      - run: npm ci
      - run: npm test

Secrets e Variáveis de Ambiente

Nunca coloque chaves de API, senhas ou tokens diretamente no YAML. Use GitHub Secrets (Settings → Secrets and variables → Actions):

Usando secrets no workflow
jobs:
  deploy:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4

      - name: Deploy para VPS
        env:
          # Secrets são mascarados nos logs — nunca aparecem em texto puro
          SSH_PRIVATE_KEY: ${{ secrets.SSH_PRIVATE_KEY }}
          VPS_HOST: ${{ secrets.VPS_HOST }}
          VPS_USER: ${{ secrets.VPS_USER }}
        run: |
          echo "$SSH_PRIVATE_KEY" > /tmp/key.pem
          chmod 600 /tmp/key.pem
          ssh -i /tmp/key.pem -o StrictHostKeyChecking=no $VPS_USER@$VPS_HOST \
            'cd /app && git pull && pm2 restart all'

      # Variáveis públicas (não secretas) ficam em vars.*
      - name: Notificar Slack
        env:
          SLACK_CHANNEL: ${{ vars.SLACK_DEPLOY_CHANNEL }}
          SLACK_TOKEN: ${{ secrets.SLACK_BOT_TOKEN }}

Branch Protection Rules: Tornando o CI Obrigatório

O CI só é efetivo se você não puder fazer merge sem ele passar. Configure em Settings → Branches → Add rule:

  1. Branch name pattern: main (ou main,develop)
  2. Require status checks to pass before merging: habilite
  3. Status checks: selecione os jobs do seu CI (ex: TypeScript, ESLint, Jest)
  4. Require branches to be up to date before merging: habilite para evitar merges desatualizados
  5. Do not allow bypassing the above settings: habilite para que nem admins possam ignorar

Use concurrency no workflow para cancelar runs anteriores do mesmo PR automaticamente. Sem isso, abrir um PR e fazer 3 commits em sequência dispara 3 runs simultâneos — desperdiçando minutos de CI. Com cancel-in-progress: true, apenas o run mais recente continua.

Conclusão

Um pipeline de CI bem configurado muda a cultura de uma equipe: o medo de fazer merge some quando você sabe que qualquer regressão será detectada automaticamente em minutos. Jobs paralelos reduzem o tempo de feedback, services permitem testes realistas contra bancos reais, e Branch Protection Rules garantem que nenhum código quebrado chega ao main. O custo é apenas o tempo de escrever o YAML uma vez.