RFC-004: Estratégia de Testes e Coverage Targets
| Campo | Valor |
|---|---|
| Status | Draft |
| Author | Time de Tecnologia |
| Created | 2026-02-05 |
| Updated | 2026-02-05 |
| Reunião | A ser agendada |
| Deciders | Todo o time técnico |
Status
🟡 Draft - RFC em elaboração, aguardando apresentação
Contexto e Problema
Embora tenhamos estratégia de testes documentada em docs/testing/strategy.md, falta formalização de:
- Coverage targets obrigatórios que bloqueiam PRs
- Enforcement da pirâmide de testes (60/30/10)
- Quality gates específicos para CI/CD
- Standards para Django e FastAPI separadamente
- Processo de manutenção de testes (flaky tests, testes lentos)
Por que resolver isso agora?
- Time crescendo, risco de coverage degradar
- CI/CD precisa de gates claros e automáticos
- Testes Django (pytest-django) e FastAPI (pytest + moto) têm necessidades diferentes
- Qualidade dos testes varia entre desenvolvedores
- Falta clareza sobre quando bloquear PR por testes
Impacto de não resolver
- Coverage degrada gradualmente ("boiling frog")
- PRs merged sem testes adequados
- Bugs em produção que poderiam ser evitados
- Testes flaky ou lentos não são tratados
- Inconsistência entre Django e FastAPI testing
Documentação relacionada:
- docs/testing/strategy.md - Pirâmide e coverage atual
- docs/testing/backend-testing.md - pytest, moto, factories
- docs/testing/frontend-testing.md - Jest, RTL
- docs/testing/integration-testing.md - SAM Local, Playwright
- docs/cicd/github-actions.md - CI/CD workflows
Proposta de Solução
Formalizar estratégia de testes com enforcement automático, adaptada para Django e FastAPI.
Coverage Targets (Obrigatórios)
Backend (Python): - Mínimo global: 70% - Target ideal: 80% - Código crítico (auth, payment, data): 90%+ - PR não pode reduzir coverage global
Frontend (TypeScript/React): - Mínimo global: 60% - Target ideal: 70% - Componentes críticos: 80%+
Exceções (não requerem coverage): - Migrations (Django/Alembic) - Scripts one-off - Arquivos de configuração - Generated code (OpenAPI schemas)
Pirâmide de Testes (Enforcement)
Métricas por PR: - Número de unit tests >= 60% dos testes adicionados - Integration tests entre 20-40% - E2E tests <= 15%
Ferramentas e Setup
Django:
# pytest.ini
[tool:pytest]
DJANGO_SETTINGS_MODULE = app.settings.test
python_files = tests.py test_*.py *_tests.py
addopts =
--cov=app
--cov-report=html
--cov-report=term-missing
--cov-fail-under=70
--strict-markers
--tb=short
-p no:warnings
# requirements-test.txt
pytest==8.0.0
pytest-django==4.7.0
pytest-cov==4.1.0
pytest-mock==3.12.0
factory-boy==3.3.0
faker==22.0.0
FastAPI:
# pytest.ini
[tool:pytest]
asyncio_mode = auto
testpaths = tests
addopts =
--cov=app
--cov-report=html
--cov-report=term-missing
--cov-fail-under=70
-p no:warnings
--tb=short
# requirements-test.txt
pytest==8.0.0
pytest-asyncio==0.23.3
pytest-cov==4.1.0
pytest-mock==3.12.0
moto[all]==5.0.0 # AWS mocking
httpx==0.26.0 # FastAPI test client
Frontend:
// jest.config.js
{
"collectCoverageFrom": [
"src/**/*.{ts,tsx}",
"!src/**/*.d.ts",
"!src/**/*.stories.tsx",
"!src/index.tsx"
],
"coverageThresholds": {
"global": {
"branches": 60,
"functions": 60,
"lines": 60,
"statements": 60
}
},
"coverageReporters": ["html", "text", "lcov"]
}
Quality Gates (CI/CD)
# .github/workflows/test.yml
name: Tests
on: [push, pull_request]
jobs:
backend-tests:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Run tests
run: |
pytest --cov --cov-fail-under=70
- name: Check coverage diff
run: |
# Bloqueia se coverage diminuir
python scripts/check_coverage_diff.py
- name: Upload coverage
uses: codecov/codecov-action@v3
frontend-tests:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Run tests
run: |
npm test -- --coverage --coverageThreshold='{"global":{"lines":60}}'
Bloqueios obrigatórios: - ❌ Coverage < 70% (backend) ou < 60% (frontend) - ❌ Coverage diminui em PR - ❌ Testes falhando - ❌ Testes flaky (falha intermitente)
Boas Práticas
Unit Tests:
# Django
def test_user_creation_with_valid_data(db):
"""Test user is created with valid input."""
# Arrange
user_data = {
"email": "test@example.com",
"password": "SecurePass123!",
"name": "Test User"
}
# Act
user = User.objects.create_user(**user_data)
# Assert
assert user.email == user_data["email"]
assert user.check_password(user_data["password"])
assert not user.is_superuser
# FastAPI
@pytest.mark.asyncio
async def test_create_user_endpoint(client):
"""Test POST /api/v1/users creates user."""
# Arrange
payload = {
"email": "test@example.com",
"name": "Test User"
}
# Act
response = await client.post("/api/v1/users", json=payload)
# Assert
assert response.status_code == 201
assert response.json()["email"] == payload["email"]
Integration Tests:
# Django + Database
def test_user_order_creation_flow(db):
"""Test complete flow: user creates order."""
user = UserFactory()
product = ProductFactory(price=100)
order = create_order(user=user, items=[product])
assert order.total == 100
assert order.user == user
assert order.status == OrderStatus.PENDING
# FastAPI + AWS (mocked)
@mock_sqs
@pytest.mark.asyncio
async def test_publish_user_event():
"""Test event is published to SQS."""
sqs = boto3.client('sqs', region_name='us-east-1')
queue = sqs.create_queue(QueueName='events')
await publish_user_created_event(user_id=123)
messages = sqs.receive_message(QueueUrl=queue['QueueUrl'])
assert len(messages['Messages']) == 1
E2E Tests:
// Playwright
test('user can complete checkout flow', async ({ page }) => {
await page.goto('/products');
await page.click('[data-testid="add-to-cart"]');
await page.click('[data-testid="checkout"]');
await page.fill('[name="card_number"]', '4242424242424242');
await page.click('[data-testid="submit-payment"]');
await expect(page).toHaveURL('/order-confirmation');
await expect(page.locator('.success-message')).toBeVisible();
});
Alternativas Consideradas
Opção 1: Coverage mais agressivo (85%+)
Prós: - Maior garantia de qualidade - Menos bugs
Contras: - Overhead de manutenção de testes - Diminishing returns (últimos 15% são os mais caros) - Pode levar a testes ruins apenas para coverage
Por que não escolhemos: 70-80% é sweet spot entre qualidade e produtividade.
Opção 2: Coverage mais permissivo (50%)
Prós: - Menos overhead - Mais flexibilidade
Contras: - Coverage degrada rapidamente - Muitos bugs passam sem testes
Por que não escolhemos: 50% é muito baixo para produção séria.
Opção 3: Sem enforcement de pirâmide
Prós: - Mais flexibilidade - Devs escolhem tipo de teste
Contras: - Tendência a escrever apenas E2E (lentos e frágeis) - CI lento - Testes flaky
Por que não escolhemos: Pirâmide é best practice comprovada.
Análise de Impacto
Impacto Técnico
- CI/CD 30-50% mais lento (executar testes)
- Coverage reports gerados automaticamente
- PRs podem ser bloqueados por coverage
- Testes Django e FastAPI separados mas consistentes
Impacto em Negócio
- ✅ Menos bugs em produção
- ✅ Refatoração mais segura
- ✅ Onboarding (testes como documentação)
- ⚠️ Desenvolvimento inicial ~15% mais lento
Riscos
Risco: Testes ruins só para coverage
Mitigação: Code review de qualidade de testes, não apenas quantidade
Plano de Implementação
Fase 1: Configuração (Semana 1)
- Configurar pytest.ini, jest.config
- Adicionar quality gates no CI/CD
- Documentar standards
Fase 2: Baseline (Semana 2)
- Medir coverage atual
- Identificar gaps críticos
- Criar plano para chegar a 70%
Fase 3: Implementation (Semanas 3-8)
- Adicionar testes faltantes (priorizar críticos)
- Refatorar testes ruins
- Remover testes flaky
Fase 4: Enforcement (Semana 9)
- Ativar gates que bloqueiam merge
- Monitorar e ajustar
Métricas de Sucesso
Após 1 mês: - ✅ Coverage backend >= 70% - ✅ Coverage frontend >= 60% - ✅ Zero PRs merged abaixo do threshold
Após 3 meses: - ✅ Coverage backend >= 80% - ✅ Bugs de produção -50% - ✅ Pirâmide 60/30/10 respeitada