Impact analysis: rodando apenas testes afetados por um PR
1. O Problema: Testes Lentos e Ineficiência no CI
Em projetos de software que crescem ao longo dos anos, a suíte de testes tende a se expandir proporcionalmente ao código-fonte. Um cenário típico em grandes repositórios: a cada push em um Pull Request (PR), dezenas de milhares de testes são executados — incluindo aqueles que nada têm a ver com as alterações propostas. O resultado é um ciclo de feedback lento, consumo excessivo de recursos computacionais e desenvolvedores esperando minutos (ou horas) por uma validação que poderia ser concluída em segundos.
O custo computacional não é trivial. Em empresas com múltiplos pipelines paralelos, rodar a suíte completa a cada commit pode significar milhares de horas de CPU desperdiçadas por mês. Além disso, o tempo de espera desestimula práticas saudáveis como commits frequentes e revisões iterativas. Para projetos monolíticos ou microsserviços interdependentes, essa ineficiência se torna um gargalo crítico no fluxo de desenvolvimento.
2. Fundamentos: Como o Git Permite Identificar Mudanças
O Git oferece ferramentas nativas para detectar exatamente quais arquivos foram alterados entre dois pontos do histórico. A base de qualquer estratégia de análise de impacto é o comando git diff.
# Comparar a branch atual (HEAD) com a branch base (main)
git diff main...HEAD --name-only
Esse comando lista todos os arquivos modificados, adicionados ou deletados no PR, excluindo aqueles que já existem em main. Outra abordagem útil é usar git log para listar arquivos alterados em um intervalo de commits:
# Listar arquivos modificados nos últimos 3 commits
git log --name-only --oneline -3
O mapeamento desses arquivos para módulos ou diretórios específicos é o próximo passo. Se o projeto segue uma estrutura padronizada (ex.: src/module_a/, tests/module_a/), podemos filtrar por diretório:
git diff main...HEAD --name-only | grep '^src/' | sed 's|src/||' | cut -d'/' -f1 | sort -u
Esse comando extrai os nomes dos módulos alterados dentro de src/, que podem então ser usados para selecionar os testes correspondentes.
3. Estratégia de Mapeamento: Arquivo → Teste
Existem três abordagens principais para mapear arquivos de código a seus respectivos testes:
Abordagem manual: baseada em convenções de nomenclatura ou arquivos de configuração. Por exemplo, se todo arquivo src/user.service.ts tem um teste correspondente em tests/user.service.test.ts, a correspondência é direta. Em projetos maiores, um arquivo impact-map.json pode definir explicitamente quais testes são afetados por cada diretório.
Abordagem automatizada: utiliza análise estática de dependências. Ferramentas como dependency-cruiser (JavaScript) ou pydeps (Python) podem rastrear imports e exports, construindo um grafo de dependências. A partir dos arquivos alterados, navega-se pelo grafo para encontrar todos os testes que dependem direta ou indiretamente daquelas mudanças.
Ferramentas existentes: alguns runners de teste já oferecem suporte nativo:
- Jest: jest --onlyChanged (executa apenas testes relacionados a arquivos modificados)
- Pytest: pytest --last-failed (útil para reexecução, mas não para impacto direto)
- Scripts customizados combinando git diff com o runner de teste escolhido
4. Implementação Prática com Git e Scripts
Abaixo, um script shell funcional que extrai arquivos modificados, filtra por diretório relevante e gera uma lista de testes a executar:
#!/bin/bash
# Script: run-impacted-tests.sh
# Uso: ./run-impacted-tests.sh <branch-base>
BASE_BRANCH=${1:-main}
# 1. Obter arquivos modificados no PR
CHANGED_FILES=$(git diff "$BASE_BRANCH"...HEAD --name-only)
# 2. Filtrar apenas arquivos de código-fonte (ex.: .py, .ts)
SOURCE_FILES=$(echo "$CHANGED_FILES" | grep -E '\.(py|ts|js)$')
# 3. Extrair módulos únicos (ex.: "user", "payment")
MODULES=$(echo "$SOURCE_FILES" | sed 's|^src/||' | cut -d'/' -f1 | sort -u)
# 4. Mapear módulos para diretórios de teste
TEST_DIRS=""
for MODULE in $MODULES; do
if [ -d "tests/$MODULE" ]; then
TEST_DIRS="$TEST_DIRS tests/$MODULE"
fi
done
# 5. Executar testes apenas nos diretórios mapeados
if [ -n "$TEST_DIRS" ]; then
echo "Executando testes nos diretórios: $TEST_DIRS"
pytest $TEST_DIRS -v
else
echo "Nenhum teste impactado encontrado. Executando suíte completa como fallback."
pytest -v
fi
Para ambientes JavaScript/TypeScript com Jest, o mapeamento pode ser ainda mais simples:
# Usando Jest com --onlyChanged (requer configuração de dependências)
git diff main...HEAD --name-only | xargs npx jest --onlyChanged --findRelatedTests
5. Integração com CI e Estratégias de Fallback
Em pipelines de CI (GitHub Actions, GitLab CI, Jenkins), a análise de impacto deve ser executada antes da suíte de testes completa. Um exemplo para GitHub Actions:
name: CI Impact Analysis
on:
pull_request:
branches: [main]
jobs:
test-impacted:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
with:
fetch-depth: 0 # necessário para git diff com branch base
- name: Run impacted tests
run: |
chmod +x run-impacted-tests.sh
./run-impacted-tests.sh origin/main
Casos especiais que devem disparar a suíte completa:
- Alterações em package.json, requirements.txt, ou Dockerfile
- Modificações em arquivos de pipeline (.github/workflows/, .gitlab-ci.yml)
- Mudanças em diretórios de configuração global (config/, infra/)
Fallback seguro: se o script de análise falhar (ex.: branch base não encontrada, erro de parsing), execute todos os testes. Isso evita falsos negativos que poderiam passar despercebidos.
6. Limitações e Cuidados com a Abordagem
A análise de impacto baseada apenas em git diff tem limitações importantes:
Falsos negativos: uma alteração em src/utils/helpers.py pode quebrar testes em tests/payment/test_checkout.py se houver dependência indireta. O mapeamento por diretório não captura esses casos.
Dependências transitivas: testes de integração que dependem de múltiplos módulos podem ser afetados por mudanças em qualquer parte da cadeia. Uma análise estática completa é necessária para cobrir esses cenários.
Manutenção do mapeamento: quando arquivos são renomeados ou refatorados, o mapeamento manual precisa ser atualizado. Ferramentas automatizadas reduzem esse problema, mas exigem configuração inicial e podem ter falsos positivos.
7. Casos de Uso Avançados e Ferramentas Especializadas
Para projetos que exigem precisão maior, ferramentas especializadas oferecem análise de impacto em nível de grafo de dependências:
- Nx (monorepos): com o comando
nx affected:test, calcula exatamente quais projetos e testes são impactados por um conjunto de alterações, usando análise de dependências entre pacotes. - Bazel: constrói e testa apenas os targets afetados, com cache distribuído e granularidade fina.
- Knapsack Pro: inteligente para paralelizar testes em CI, mas também oferece detecção de testes impactados.
O git bisect pode ser combinado com análise de impacto para depuração: ao identificar um commit que introduziu uma falha, a análise de impacto mostra quais testes deveriam ter falhado naquele ponto, ajudando a isolar a causa raiz.
Estratégias híbridas são recomendadas para projetos críticos: execute sempre os testes impactados identificados pela análise, mas adicione uma amostra aleatória (ex.: 10%) de testes não impactados para detectar efeitos colaterais inesperados.
Referências
- Git Documentation: git-diff — Documentação oficial do comando
git diff, base para identificar arquivos modificados entre branches. - Jest: Testing Only Changed Files — Documentação oficial do Jest sobre a flag
--onlyChangedpara executar testes relacionados a arquivos modificados. - Nx: Affected Command — Guia oficial do Nx para executar testes apenas em projetos impactados por alterações em monorepos.
- Pytest: Last Failed — Documentação do pytest sobre reexecução seletiva de testes que falharam na última execução.
- Bazel: Test Impact Analysis — Explicação detalhada de como o Bazel realiza análise de impacto para construir e testar apenas targets afetados.
- GitHub Actions: Workflow Syntax for Pull Requests — Referência oficial para configurar workflows que respondem a eventos de pull request no GitHub Actions.
- Knapsack Pro: Impact Analysis — Documentação da ferramenta Knapsack Pro para detecção de testes impactados e paralelização inteligente em CI.