Nesta aula, vamos explorar como integrar testes no pipeline de CI/CD, garantindo que o código entregue tenha qualidade. Testes são essenciais para detectar regressões e validar funcionalidades antes da implantação. Abordaremos os tipos de testes, o momento ideal para executá-los, como usar métricas de qualidade como gate e a importância da cobertura de código.

Um pipeline bem estruturado executa testes em diferentes estágios, permitindo feedback rápido e evitando que problemas cheguem à produção. Vamos detalhar cada tipo de teste e como configurá-los no pipeline.

Testes unitários, integração, E2E

Testes unitários verificam pequenas unidades de código (funções, métodos) de forma isolada, geralmente com mocks de dependências externas. São rápidos e devem ser executados primeiro no pipeline. Exemplo com pytest (Python):

pytest tests/unit/ --junitxml=report-unit.xml

Testes de integração validam a interação entre componentes, como acesso a banco de dados ou APIs. Eles são mais lentos e exigem ambiente preparado. Exemplo com docker-compose:

docker-compose up -d
pytest tests/integration/ --junitxml=report-integration.xml
docker-compose down

Testes E2E (end-to-end) simulam o fluxo completo do usuário, testando a aplicação como um todo. São os mais lentos e frágeis, mas essenciais para validar cenários críticos. Exemplo com Cypress:

npx cypress run --reporter junit --reporter-options 'mochaFile=report-e2e.xml'

Quando rodar cada um

Em um pipeline, a ordem de execução deve ser: primeiro os testes unitários (rápidos), depois os de integração e, por fim, os E2E. Isso permite falhar rápido e economizar recursos. Em um pipeline de CI/CD, você pode definir stages:

stages:
  - unit
  - integration
  - e2e

unit:
  script: pytest tests/unit/

integration:
  script: docker-compose up -d && pytest tests/integration/ && docker-compose down

e2e:
  script: npx cypress run

Se os testes unitários falharem, o pipeline para e não executa os demais. Isso evita desperdício de tempo e recursos. Em ambientes de deploy contínuo, os testes de integração e E2E podem ser executados em paralelo após os unitários passarem.

Qualidade como gate

Qualidade como gate significa que métricas de qualidade (como cobertura de código, número de falhas, tempo de execução) são usadas para decidir se o pipeline prossegue ou não. Por exemplo, se a cobertura cair abaixo de um limite, o pipeline falha. Ferramentas como SonarQube ou Codecov podem ser integradas. Exemplo de script para verificar cobertura mínima de 80%:

coverage=$(grep -oP 'line-rate=\"\K[^\"]+' coverage.xml | head -1)
threshold=0.8
if (( $(echo "$coverage < $threshold" | bc -l) )); then
  echo "Cobertura $coverage abaixo do limite $threshold"
  exit 1
fi

Outro gate comum é a quantidade de testes falhos. Se houver falhas, o pipeline é interrompido. Isso garante que apenas código de qualidade seja promovido para os próximos estágios.

Cobertura

Cobertura de código mede a porcentagem de linhas, branches ou funções executadas pelos testes. Ferramentas como pytest-cov, Istanbul (JS) ou gcov (C/C++) geram relatórios. Exemplo de geração de relatório com pytest-cov:

pytest --cov=src --cov-report=xml:coverage.xml

O relatório XML pode ser publicado em ferramentas como SonarQube ou Codecov. É importante definir um limite mínimo aceitável (ex: 80%) e monitorar a evolução. No entanto, cobertura não é garantia de qualidade; testes mal escritos podem ter alta cobertura sem testar cenários importantes. Use cobertura como um dos indicadores, não o único.

Boas práticas

  • Mantenha os testes unitários independentes e rápidos.
  • Use mocks para isolar unidades.
  • Execute testes de integração em ambientes limpos (containers).
  • Priorize testes E2E para fluxos críticos.
  • Defina gates realistas e ajuste conforme necessário.
  • Publique relatórios de teste e cobertura para visibilidade.

Referências

Exercícios

  1. Explique a diferença entre teste unitário e teste de integração, com exemplos.
  2. ✓ Resposta: Teste unitário verifica uma unidade isolada (ex: função soma), usando mocks para dependências. Teste de integração verifica a interação entre componentes (ex: função que chama banco de dados real). Exemplo unitário: testar função que calcula imposto sem conectar no banco. Exemplo integração: testar a mesma função com banco real para garantir que a query está correta.
  3. Escreva um script bash que execute testes unitários e, se falharem, interrompa o pipeline.
  4. ✓ Resposta:
    #!/bin/bash
    pytest tests/unit/ --junitxml=report.xml
    if [ $? -ne 0 ]; then
      echo "Testes unitários falharam"
      exit 1
    fi
  5. Como configurar um gate de qualidade baseado em cobertura mínima de 70%? Dê um exemplo de script.
  6. ✓ Resposta:
    cobertura=$(grep -oP 'line-rate="\K[^"]+' coverage.xml | head -1)
    if (( $(echo "$cobertura < 0.7" | bc -l) )); then
      echo "Cobertura $cobertura abaixo de 70%"
      exit 1
    fi
  7. Cite três ferramentas de cobertura de código e para quais linguagens são usadas.
  8. ✓ Resposta: 1. pytest-cov (Python); 2. Istanbul (JavaScript/TypeScript); 3. gcov (C/C++).
  9. Descreva um cenário onde testes E2E são mais importantes que testes unitários.
  10. ✓ Resposta: Em um sistema de e-commerce com checkout, testes E2E validam o fluxo completo: adicionar ao carrinho, login, pagamento, confirmação. Testes unitários não capturam problemas de integração entre frontend, backend e gateway de pagamento. Portanto, E2E são cruciais para cenários críticos de negócio.