Skip to content

Testes

Esta seção descreve os procedimentos e práticas de teste para a API de Gerenciamento de Carros.

Estratégia de Testes

A API utiliza uma abordagem de testes multilayer para garantir qualidade e confiabilidade:

  • Testes Unitários: Testam funções e métodos individuais
  • Testes de Integração: Testam a interação entre componentes
  • Testes de API: Testam os endpoints da API
  • Testes de Segurança: Testam mecanismos de autenticação e autorização

Framework de Testes

O projeto utiliza pytest como framework principal de testes, complementado por:

  • pytest-asyncio: Para testes assíncronos
  • pytest-mock: Para criação de mocks
  • httpx: Para testes de requisições HTTP
  • SQLAlchemy Testing: Para testes com banco de dados

Estrutura de Testes

Os testes estão organizados no diretório tests/:

tests/
├── __init__.py
├── conftest.py              # Configurações e fixtures compartilhados
├── test_auth.py             # Testes de autenticação
├── test_brands.py           # Testes de marcas
├── test_cars.py             # Testes de carros
├── test_users.py            # Testes de usuários
└── utils/                   # Utilitários para testes

Configurações de Teste

Arquivo conftest.py

Contém fixtures e configurações compartilhadas:

import pytest
from fastapi.testclient import TestClient
from sqlalchemy.ext.asyncio import create_async_engine, AsyncSession
from sqlalchemy.orm import sessionmaker

from car_api.app import app
from car_api.core.database import get_session

@pytest.fixture
def client():
    with TestClient(app) as test_client:
        yield test_client

Banco de Dados de Teste

Para testes, é recomendado usar um banco de dados em memória ou temporário:

@pytest.fixture
async def async_session():
    engine = create_async_engine("sqlite+aiosqlite:///:memory:")
    async with engine.begin() as conn:
        await conn.run_sync(Base.metadata.create_all)

    async_session = sessionmaker(
        engine, class_=AsyncSession, expire_on_commit=False
    )

    async with async_session() as session:
        yield session

Tipos de Testes

Testes de API

Testam os endpoints da API para garantir que respondam corretamente:

def test_create_car(client, authenticated_client):
    response = authenticated_client.post(
        "/api/v1/cars/",
        json={
            "model": "Fusca",
            "factory_year": 1970,
            "model_year": 1970,
            # ... outros campos
        }
    )
    assert response.status_code == 201
    assert response.json()["model"] == "Fusca"

Testes de Autenticação

Verificam se os mecanismos de autenticação funcionam corretamente:

def test_authentication_required(client):
    response = client.get("/api/v1/cars/")
    assert response.status_code == 401  # Não autenticado

def test_valid_token(client, valid_token):
    response = client.get(
        "/api/v1/cars/",
        headers={"Authorization": f"Bearer {valid_token}"}
    )
    assert response.status_code == 200  # Autenticado

Testes de Validação

Garantem que os dados sejam validados corretamente:

def test_invalid_plate_format(client, authenticated_client):
    response = authenticated_client.post(
        "/api/v1/cars/",
        json={
            "model": "Fusca",
            "plate": "AB1",  # Placa inválida
            # ... outros campos
        }
    )
    assert response.status_code == 400  # Dados inválidos

Execução de Testes

Executando Todos os Testes

poetry run pytest

Executando Testes Específicos

# Executar testes de um arquivo específico
poetry run pytest tests/test_cars.py

# Executar um teste específico
poetry run pytest tests/test_cars.py::test_create_car

# Executar com saída detalhada
poetry run pytest -v

# Executar com cobertura de código
poetry run pytest --cov=car_api

Executando Testes com Cobertura

Para verificar a cobertura de código dos testes:

# Instale o plugin de cobertura
poetry add pytest-cov --group dev

# Execute testes com cobertura
poetry run pytest --cov=car_api --cov-report=html

O relatório de cobertura será gerado no diretório htmlcov/.

Melhores Práticas de Teste

1. Testes Isolados

Cada teste deve ser independente e não depender da execução de outros testes:

# Bom: Teste independente
def test_create_car_different_brands(client, authenticated_client):
    # Este teste não depende de outros testes
    pass

# Ruim: Teste dependente de ordem de execução
def test_get_car_after_creation(client, authenticated_client):
    # Este teste assume que um carro já foi criado anteriormente
    pass

2. Dados de Teste Consistentes

Use fixtures para garantir dados consistentes entre testes:

@pytest.fixture
def sample_brand(async_session):
    brand = Brand(name="Volkswagen", description="Marca alemã")
    async_session.add(brand)
    await async_session.commit()
    await async_session.refresh(brand)
    return brand

3. Testes Descritivos

Nomeie os testes de forma clara e descritiva:

# Bom: Nome claro sobre o que está sendo testado
def test_returns_404_when_car_not_found():
    pass

# Ruim: Nome vago
def test_get_car():
    pass

4. Testes de Casos de Erro

Inclua testes para cenários de erro:

def test_returns_404_when_car_does_not_exist(authenticated_client):
    response = authenticated_client.get("/api/v1/cars/99999")
    assert response.status_code == 404

def test_returns_400_when_invalid_data_provided(authenticated_client):
    response = authenticated_client.post(
        "/api/v1/cars/",
        json={"invalid_field": "value"}
    )
    assert response.status_code == 400

Pipelines de Teste

Testes Locais

Execute testes localmente antes de fazer commits:

# Execute todos os testes
poetry run pytest

# Execute testes com cobertura
poetry run pytest --cov=car_api --cov-fail-under=80

Testes em CI/CD

Configure pipelines de integração contínua para executar testes automaticamente:

# Exemplo para GitHub Actions
name: Tests
on: [push, pull_request]
jobs:
  test:
    runs-on: ubuntu-latest
    steps:
    - uses: actions/checkout@v2
    - name: Setup Python
      uses: actions/setup-python@v2
      with:
        python-version: 3.13
    - name: Install dependencies
      run: |
        pip install poetry
        poetry install
    - name: Run tests
      run: poetry run pytest

Métricas de Qualidade

Cobertura de Código

Objetivo: Manter cobertura de código acima de 80%:

poetry run pytest --cov=car_api --cov-report=term-missing --cov-fail-under=80

Qualidade do Código

Combine testes com análise de qualidade de código:

# Execute testes e análise de código
poetry run pytest && poetry run ruff check

Dicas para Testes Eficientes

1. Mocks e Stubs

Use mocks para isolar componentes externos:

from unittest.mock import patch

@patch('car_api.core.security.create_access_token')
def test_login_success(mock_create_token, client, sample_user):
    mock_create_token.return_value = "fake-token"
    # Teste aqui

2. Testes Parametrizados

Use testes parametrizados para testar múltiplos cenários:

@pytest.mark.parametrize("invalid_year", [-1, 100, 1899, 2031])
def test_invalid_year_validation(client, authenticated_client, invalid_year):
    response = authenticated_client.post(
        "/api/v1/cars/",
        json={
            "model": "Fusca",
            "factory_year": invalid_year,
            # ... outros campos
        }
    )
    assert response.status_code == 400

3. Testes Assíncronos

Lide adequadamente com testes assíncronos:

@pytest.mark.asyncio
async def test_async_database_operation(async_session):
    # Teste de operação assíncrona
    pass

Manutenção de Testes

Refatoração de Testes

Quando refatorar código, atualize os testes correspondentes:

  • Mantenha testes alinhados com a lógica de negócio
  • Remova testes obsoletos
  • Adicione testes para novas funcionalidades

Documentação de Testes

Documente testes complexos para facilitar manutenção futura:

def test_concurrent_car_updates():
    """
    Testa atualização concorrente de carros para garantir
    consistência de dados em ambiente multiusuário.
    """
    pass

Com uma estratégia de testes bem definida e bem implementada, a API de Gerenciamento de Carros mantém alta qualidade e confiabilidade ao longo do tempo.