Pular para conteúdo

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 x sem mensagem)

Referências