Testing Strategy
Estratégia geral de testes para backend e frontend.
Pirâmide de Testes
graph TD
subgraph pyramid [Pirâmide de Testes]
E2E[E2E Tests - 10%<br/>Playwright]
Integration[Integration Tests - 30%<br/>SAM Local + LocalStack]
Unit[Unit Tests - 60%<br/>Pytest + Jest]
end
Unit --> Backend[Backend: pytest + moto]
Unit --> Frontend[Frontend: Jest + RTL]
Integration --> Lambda[Lambda Integration: SAM Local]
E2E --> UserFlows[User Flows: Playwright]
Níveis de Testes
Unit Tests (60%)
O que testar: - Funções e métodos isolados - Business logic - Validações - Transformações de dados
Características: - Rápidos (< 100ms cada) - Isolados (sem dependências externas) - Determinísticos - Cobertura: 70%+ backend, 60%+ frontend
Ferramentas: - Python: pytest - JavaScript: Jest - Mocking: moto (AWS), pytest-mock, jest.mock()
Integration Tests (30%)
O que testar: - Integração entre componentes - Fluxos completos (API → Service → Database) - Integração com AWS services (via LocalStack) - Processamento de filas
Características: - Moderadamente rápidos (< 1s cada) - Usam banco de teste real - Mocka apenas serviços externos - Cobertura: Fluxos principais
Ferramentas: - SAM Local para Lambda - LocalStack para AWS services - TestContainers para PostgreSQL
E2E Tests (10%)
O que testar: - Jornadas críticas do usuário - Fluxos end-to-end completos - Integração frontend + backend
Características: - Lentos (segundos/minutos) - Rodados em ambiente staging - Podem ser flaky - Cobertura: Fluxos críticos apenas
Ferramentas: - Playwright para testes de browser - Cypress (alternativa)
Quando Rodar Testes
Localmente (Durante desenvolvimento)
# Backend
pytest --lf # Last failed only
pytest -k test_users # Específico
# Frontend
npm test -- --watch
No CI (Pull Requests)
- Todos os unit tests
- Integration tests principais
- Não rodar E2E (economizar tempo)
Pre-deployment (Staging)
- Todos os unit tests
- Todos os integration tests
- Smoke tests E2E
Post-deployment (Production)
- Smoke tests apenas
- Health checks
- Monitoring contínuo
Coverage Targets
Backend (Python)
- Mínimo: 70%
- Target: 80%
- Crítico (core business logic): 90%+
Exceções (não precisa coverage):
- Configuração (settings.py)
- Migrations
- Scripts one-off
Frontend (React)
- Mínimo: 60%
- Target: 70%
- Componentes críticos: 80%+
Exceções: - Arquivos de config - Styled components - Storybook stories
Test Organization
Backend Structure
tests/
├── unit/
│ ├── test_services/
│ │ ├── test_user_service.py
│ │ └── test_product_service.py
│ ├── test_models/
│ └── test_utils/
├── integration/
│ ├── test_api/
│ │ ├── test_users_api.py
│ │ └── test_products_api.py
│ └── test_workers/
├── e2e/
│ ├── test_user_journey.py
│ └── test_checkout_flow.py
└── conftest.py # Fixtures compartilhadas
Frontend Structure
src/
├── components/
│ ├── UserProfile/
│ │ ├── UserProfile.tsx
│ │ ├── UserProfile.test.tsx
│ │ └── UserProfile.stories.tsx
└── __tests__/
├── integration/
└── e2e/
Naming Conventions
Python (pytest)
def test_user_creation_with_valid_data():
"""Test that user is created successfully with valid input."""
...
def test_user_creation_fails_with_duplicate_email():
"""Test that duplicate email raises error."""
...
Pattern: test_<what>_<condition>_<expected_result>
JavaScript (Jest)
describe('UserService', () => {
describe('createUser', () => {
it('creates user with valid data', () => {
...
});
it('throws error when email already exists', () => {
...
});
});
});
Test Data
Fixtures (Python)
# conftest.py
import pytest
@pytest.fixture
def sample_user():
return {
"email": "test@example.com",
"name": "Test User",
"password": "SecurePass123!"
}
@pytest.fixture
async def db_session():
async with async_session_maker() as session:
yield session
await session.rollback()
Factories
# factories.py
from factory import Factory, Faker
from factory.alchemy import SQLAlchemyModelFactory
class UserFactory(SQLAlchemyModelFactory):
class Meta:
model = User
sqlalchemy_session = session
email = Faker('email')
name = Faker('name')
password_hash = 'hashed_password'
is_active = True
# Uso
user = UserFactory.create(email='specific@example.com')
users = UserFactory.create_batch(10)
Continuous Testing
Pre-commit Hooks
# .pre-commit-config.yaml
repos:
- repo: local
hooks:
- id: pytest-quick
name: pytest-quick
entry: pytest tests/unit -x
language: system
pass_filenames: false
CI Pipeline
# .github/workflows/test.yml
name: Tests
on: [push, pull_request]
jobs:
test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-python@v5
with:
python-version: '3.11'
- name: Install dependencies
run: pip install -r requirements-dev.txt
- name: Run tests with coverage
run: pytest --cov --cov-report=xml --cov-report=html
- name: Upload coverage
uses: codecov/codecov-action@v3
Quality Gates
Must Pass
- ✅ Todos os testes passam
- ✅ Coverage >= targets
- ✅ Linter sem erros
- ✅ Security scan (dependências)
Warnings
- ⚠️ Coverage diminuiu
- ⚠️ Testes lentos (> 5min total)
- ⚠️ Testes flaky
Best Practices
✅ Fazer
- Testar comportamento, não implementação
- Testes isolados e independentes
- Nomes descritivos
- Arrange-Act-Assert pattern
- Mock dependências externas
- Um assert principal por teste
❌ Evitar
- Testes acoplados (dependem de ordem)
- Sleeps ou timeouts
- Hardcoded values quando não necessário
- Testar framework (React, FastAPI)
- Testes que dependem de estado global
- Asserts genéricos (
assert xsem mensagem)