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.