diff --git a/README.md b/README.md index 70e4236..39ea1be 100644 --- a/README.md +++ b/README.md @@ -18,52 +18,74 @@ Este projeto implementa um sistema gamificado onde usuários ganham pontos ao us ### Pré-requisitos -- Python 3.12 -- pip +- Python 3.12 +- pip (gerenciador de pacotes Python) +- Git + +### Instalação e Execução com Docker -### Instalação e Execução com docker 1. **Clone o repositório**: ```bash - cd /home/kim/code/estudos/bussp + git clone https://github.com/Bussp/backend.git + cd backend + ``` + +2. **Construa a imagem Docker**: + ```bash + docker build -t backend . ``` -2. **Execute**: +3. **Execute o container**: ```bash docker run -d -p 127.0.0.1:8000:8000 backend ``` -3. **Acesse a API**: +4. **Acesse a API**: - API: http://localhost:8000 - - Documentação interativa: http://localhost:8000/docs - - Documentação alternativa: http://localhost:8000/redoc + - Documentação interativa (Swagger): http://localhost:8000/docs + - Documentação alternativa (ReDoc): http://localhost:8000/redoc -4. **Para matar o container**: - Pegue o id do container em `CONTAINER ID` +5. **Para parar e remover o container**: + + Primeiro, pegue o ID do container: ```bash docker ps ``` - ```bash + + Você verá algo como: + ``` CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES + abc123def456 backend ... ... ... ... ... ``` - Execute: + + Então execute: ```bash - docker stop ${id} - docker rm ${id} + docker stop abc123def456 + docker rm abc123def456 ``` -### Instalação e Execução +### Instalação e Execução sem Docker 1. **Clone o repositório**: ```bash - cd bussp + git clone https://github.com/Bussp/backend.git + cd backend ``` 2. **Crie um ambiente virtual**: ```bash - python3.12 -m venv venv # comando antigo: python -m venv venv - - source venv/bin/activate # No Windows: venv\Scripts\activate + python3.12 -m venv venv ``` + + Ative o ambiente virtual: + - Linux/Mac: + ```bash + source venv/bin/activate + ``` + - Windows: + ```bash + venv\Scripts\activate + ``` 3. **Instale as dependências**: ```bash @@ -71,21 +93,82 @@ Este projeto implementa um sistema gamificado onde usuários ganham pontos ao us ``` 4. **Configure as variáveis de ambiente**: + + Copie o arquivo de exemplo: ```bash cp .env.example .env - # Edite .env e adicione seu token da API SPTrans - # Obtenha em: https://www.sptrans.com.br/desenvolvedores/ + ``` + + Edite o arquivo `.env` e configure as seguintes variáveis: + + **Variáveis obrigatórias:** + + - `SPTRANS_API_TOKEN`: Token de acesso à API da SPTrans + - **Como obter**: Acesse https://www.sptrans.com.br/desenvolvedores/ + - Faça cadastro e solicite um token de desenvolvedor + - Substitua `your_api_token_here` pelo token recebido + - Exemplo: `SPTRANS_API_TOKEN=abc123def456ghi789` + + **Variáveis opcionais (já vêm com valores padrão):** + + - `DATABASE_URL`: URL de conexão com o banco de dados + - Padrão: `sqlite+aiosqlite:///./bussp.db` (SQLite local) + - Para PostgreSQL (produção): `postgresql+asyncpg://usuario:senha@localhost:5432/bussp` + + - `DEBUG`: Modo de depuração + - Padrão: `true` + - Em produção, altere para `false` + + - `HOST` e `PORT`: Endereço e porta do servidor + - Padrão: `0.0.0.0:8000` + - Mantenha os valores padrão para desenvolvimento local + + - `AUTH_DISABLED`: Desabilitar autenticação (apenas para testes) + - Padrão: `false` + - Deixe como `false` em produção + + - `DEFAULT_USER_EMAIL` e `DEFAULT_USER_NAME`: Usuário padrão para testes + - Usado apenas quando `AUTH_DISABLED=true` + - Pode manter os valores padrão ou personalizar + + **Exemplo de arquivo `.env` configurado:** + ```env + DEBUG=true + APP_NAME="BusSP - Gamified Public Transport Tracker" + + DATABASE_URL=sqlite+aiosqlite:///./bussp.db + + SPTRANS_API_TOKEN=seu_token_aqui_obtido_no_site_da_sptrans + SPTRANS_BASE_URL=http://api.olhovivo.sptrans.com.br/v2.1 + + HOST=0.0.0.0 + PORT=8000 + + AUTH_DISABLED=false + DEFAULT_USER_EMAIL="seu_email@exemplo.com" + DEFAULT_USER_NAME="Seu Nome" ``` 5. **Inicie o servidor**: ```bash python -m src.main ``` + + Você verá algo como: + ``` + INFO: Started server process [12345] + INFO: Waiting for application startup. + INFO: Application startup complete. + INFO: Uvicorn running on http://0.0.0.0:8000 + ``` 6. **Acesse a API**: - API: http://localhost:8000 - - Documentação interativa: http://localhost:8000/docs - - Documentação alternativa: http://localhost:8000/redoc + - Documentação interativa (Swagger): http://localhost:8000/docs + - Documentação alternativa (ReDoc): http://localhost:8000/redoc + +7. **Para parar o servidor**: + - Pressione `Ctrl + C` no terminal > **Nota**: As tabelas do banco de dados são criadas automaticamente na inicialização. @@ -110,12 +193,6 @@ ruff format src/ tests/ # Formatação ## Documentação -### Guias Completos -- [**Arquitetura**](docs/ARQUITETURA.md) - Arquitetura Hexagonal, camadas, responsabilidades e princípios -- [**Testes**](docs/TESTES.md) - Como escrever testes com mocks, padrão AAA, boas práticas -- [**Tipagem Estática**](docs/TIPAGEM.md) - Type hints, MyPy, erros comuns e soluções -- [**Linting**](docs/LINTING.md) - Ruff, qualidade de código, formatação automática - ### Estrutura do Projeto ``` src/ @@ -124,4 +201,4 @@ src/ └── adapters/ # Infraestrutura (database, repositories, external) ``` -Consulte o [**Guia de Arquitetura**](docs/ARQUITETURA.md) para detalhes completos sobre a estrutura e responsabilidades de cada camada. +Consulte o [**Guia de Arquitetura**](docs/ARQUITETURA.md) e [**Diagrama da Arquitetura**](docs/arquitetura.pdf) para detalhes completos sobre a estrutura e responsabilidades de cada camada. diff --git a/docs/ARQUITETURA.md b/docs/ARQUITETURA.md index 14ab2be..8ebc914 100644 --- a/docs/ARQUITETURA.md +++ b/docs/ARQUITETURA.md @@ -1,4 +1,4 @@ -# 🏗️ Arquitetura do BusSP +# Arquitetura do BusSP ## Visão Geral @@ -54,17 +54,17 @@ Isso garante que a lógica de negócio permaneça pura e independente de framewo ``` src/ -├── core/ # 🎯 Camada Core - Lógica de Negócio +├── core/ # Camada Core - Lógica de Negócio │ ├── models/ # Entidades de domínio │ ├── ports/ # Interfaces (contratos) │ └── services/ # Lógica de negócio │ -├── web/ # 🌐 Camada Web - Apresentação +├── web/ # Camada Web - Apresentação │ ├── controllers/ # Endpoints da API │ ├── schemas.py # Modelos Pydantic │ └── mappers.py # Conversão API ↔ Domínio │ -└── adapters/ # 🔌 Camada Adapters - Infraestrutura +└── adapters/ # Camada Adapters - Infraestrutura ├── database/ # Persistência ├── repositories/ # Implementação de portas └── external/ # APIs externas @@ -72,7 +72,7 @@ src/ ## Responsabilidades das Camadas -### 🎯 Camada Core (`src/core/`) +### Camada Core (`src/core/`) O **coração** da aplicação contendo lógica de negócio pura. @@ -161,15 +161,15 @@ class TripService: distance: int, trip_datetime: datetime, ) -> Trip: - # 1. Validar usuário existe + user = await self._user_repo.get_user(email) if user is None: raise ValueError(f"User {email} not found") - # 2. Calcular pontuação (lógica de negócio) + score = distance // 100 - # 3. Criar viagem + trip = Trip( email=email, bus_line=bus_line, @@ -177,10 +177,10 @@ class TripService: trip_datetime=trip_datetime, ) - # 4. Salvar viagem + saved_trip = await self._trip_repo.save_trip(trip) - # 5. Atualizar score do usuário + await self._user_repo.add_user_score(email, score) return saved_trip @@ -188,7 +188,7 @@ class TripService: **Princípio Chave**: Sem importações de frameworks web, bancos de dados ou bibliotecas externas. Apenas biblioteca padrão do Python e lógica de domínio. -### 🌐 Camada Web (`src/web/`) +### Camada Web (`src/web/`) Trata requisições e respostas HTTP. @@ -217,7 +217,7 @@ async def create_trip( request: CreateTripRequest, trip_service: TripService = Depends(get_trip_service), ) -> TripResponse: - # 1. Chamar serviço (lógica está no core) + trip = await trip_service.create_trip( email=request.email, bus_line=request.bus_line, @@ -225,7 +225,7 @@ async def create_trip( trip_datetime=request.trip_datetime, ) - # 2. Mapear domínio → API schema + return TripResponse.from_domain(trip) ``` @@ -276,7 +276,7 @@ def trip_response_from_domain(trip: Trip) -> TripResponse: **Princípio Chave**: Controllers são finos. Eles delegam toda lógica de negócio aos serviços do core. -### 🔌 Camada Adapters (`src/adapters/`) +### Camada Adapters (`src/adapters/`) Implementa aspectos de infraestrutura. diff --git a/docs/LINTING.md b/docs/LINTING.md deleted file mode 100644 index 06573d3..0000000 --- a/docs/LINTING.md +++ /dev/null @@ -1,372 +0,0 @@ -# 🎨 Guia de Linting e Qualidade de Código - BusSP - -## O que é Linting? - -**Linting** é o processo de analisar código para encontrar: -- **Erros de programação**: Bugs potenciais, código não utilizado -- **Problemas de estilo**: Formatação inconsistente, convenções não seguidas -- **Construções suspeitas**: Padrões que podem causar problemas -- **Más práticas**: Código que funciona mas pode ser melhorado - -Pense no linter como um **revisor de código automatizado** que verifica seu código 24/7. - -### Por que Linting é Importante? - -- **Consistência**: Código uniforme em todo o projeto -- **Manutenibilidade**: Código mais fácil de ler e entender -- **Prevenção de bugs**: Detecta problemas antes de chegar à produção -- **Padrões**: Segue convenções da comunidade Python (PEP 8) -- **Colaboração**: Facilita trabalho em equipe - -## Usando Ruff - -### Comandos Básicos - -```bash -# Verificar problemas (apenas reporta) -ruff check src/ tests/ - -# Corrigir problemas auto-corrigíveis -ruff check --fix src/ tests/ - -# Formatar código (estilo consistente) -ruff format src/ tests/ - -# Verificar E formatar de uma vez -ruff check --fix src/ tests/ && ruff format src/ tests/ -``` - -### Comandos Avançados - -```bash -# Verificar arquivo específico -ruff check src/core/services/user_service.py - -# Mostrar explicação de regras -ruff check --show-source src/ - -# Ver diferenças antes de aplicar correções -ruff check --diff src/ - -# Verificar apenas regras específicas -ruff check --select F,E src/ # F=pyflakes, E=pycodestyle errors - -# Ignorar regras específicas -ruff check --ignore E501 src/ # Ignora linha muito longa - -# Gerar relatório em formato JSON -ruff check --output-format=json src/ > report.json -``` - -## O que Ruff Verifica - -### 1. Importações Não Utilizadas - -```python -# ❌ Ruim - import não usado -import os -from typing import List, Dict # Dict não é usado - -def get_items() -> List[str]: - return ["a", "b"] - -# ✅ Bom - apenas imports necessários -from typing import List - -def get_items() -> List[str]: - return ["a", "b"] - -# Ruff corrige automaticamente com --fix -``` - -### 2. Variáveis Não Utilizadas - -```python -# ❌ Ruim -def calculate_score(distance: int, bonus: int) -> int: - base = distance // 100 - multiplier = 2 # Declarado mas não usado - return base - -# ✅ Bom -def calculate_score(distance: int) -> int: - base = distance // 100 - return base -``` - -### 3. Complexidade Excessiva - -```python -# ❌ Ruim - muito complexo (complexidade ciclomática alta) -def process(x: int) -> str: - if x > 0: - if x < 10: - if x % 2 == 0: - if x != 6: - return "valid" - return "invalid" - -# ✅ Bom - simplificado -def process(x: int) -> str: - if 0 < x < 10 and x % 2 == 0 and x != 6: - return "valid" - return "invalid" - -# Ou melhor ainda -def process(x: int) -> str: - is_valid = ( - 0 < x < 10 - and x % 2 == 0 - and x != 6 - ) - return "valid" if is_valid else "invalid" -``` - -### 4. Formatação Inconsistente - -```python -# ❌ Ruim -def calculate(x:int,y:int)->int: - result=x+y - return result - -# ✅ Bom - formatado pelo Ruff -def calculate(x: int, y: int) -> int: - result = x + y - return result -``` - -### 5. Ordenação de Imports - -```python -# ❌ Ruim - ordem incorreta -from src.core.models.user import User -import os -from typing import List -import sys - -# ✅ Bom - ordenado pelo Ruff -import os -import sys -from typing import List - -from src.core.models.user import User -``` - -**Ordem correta**: -1. Imports da biblioteca padrão -2. Imports de terceiros -3. Imports locais do projeto - -### 6. Strings de Aspas Inconsistentes - -```python -# ❌ Ruim - mistura de aspas -name = "João" -email = 'joao@test.com' -message = "Olá, " + name - -# ✅ Bom - consistente (Ruff padroniza para aspas duplas) -name = "João" -email = "joao@test.com" -message = f"Olá, {name}" -``` - -### 7. Comparações Problemáticas - -```python -# ❌ Ruim - comparação com None usando == -if user == None: - return - -# ✅ Bom - use 'is' para None -if user is None: - return - -# ❌ Ruim - comparação com True/False -if is_active == True: - process() - -# ✅ Bom - teste direto -if is_active: - process() -``` - -### 8. List/Dict Comprehensions - -```python -# ❌ Ruim - loop desnecessário -result = [] -for item in items: - result.append(item.upper()) - -# ✅ Bom - list comprehension -result = [item.upper() for item in items] - -# ❌ Ruim -users = {} -for user in user_list: - users[user.id] = user.name - -# ✅ Bom - dict comprehension -users = {user.id: user.name for user in user_list} -``` - -### 9. F-strings vs Concatenação - -```python -# ❌ Ruim - concatenação -message = "Olá, " + name + "! Você tem " + str(score) + " pontos." - -# ❌ Ruim - .format() -message = "Olá, {}! Você tem {} pontos.".format(name, score) - -# ✅ Bom - f-string (mais rápido e legível) -message = f"Olá, {name}! Você tem {score} pontos." -``` - -### 10. Código Morto - -```python -# ❌ Ruim - código nunca executado -def process(value: int) -> str: - return "processed" - print("Isso nunca executa") # Código morto - -# ✅ Bom -def process(value: int) -> str: - return "processed" -``` - -## Configuração no `pyproject.toml` - -```toml -[tool.ruff] -# Comprimento máximo de linha -line-length = 88 - -# Versão do Python -target-version = "py311" - -# Excluir arquivos/diretórios -exclude = [ - ".git", - ".mypy_cache", - ".ruff_cache", - "venv", - "__pycache__", -] - -[tool.ruff.lint] -# Regras ativadas -select = [ - "E", # pycodestyle errors - "W", # pycodestyle warnings - "F", # pyflakes - "I", # isort (ordenação de imports) - "B", # flake8-bugbear (bugs comuns) - "C4", # flake8-comprehensions - "UP", # pyupgrade (modernização) -] - -# Regras ignoradas (se necessário) -ignore = [ - "E501", # linha muito longa (deixe o formatter lidar) -] - -# Permitir correções automáticas -fixable = ["ALL"] - -# Nunca corrigir automaticamente -unfixable = [] - -[tool.ruff.lint.per-file-ignores] -# Ignorar regras específicas em arquivos de teste -"tests/**/*.py" = [ - "S101", # Permitir asserts em testes -] - -[tool.ruff.format] -# Estilo de aspas (double ou single) -quote-style = "double" - -# Indentação -indent-style = "space" - -# Preferir aspas duplas em docstrings -docstring-code-format = true -``` - -## Principais Categorias de Regras - -### Pyflakes (F) -Detecta erros de programação: -- Imports não utilizados -- Variáveis não definidas -- Código não alcançável - -### pycodestyle (E, W) -Verifica estilo PEP 8: -- Espaçamento -- Linhas em branco -- Indentação - -### isort (I) -Ordena imports: -- Agrupa por tipo -- Ordena alfabeticamente -- Remove duplicatas - -### flake8-bugbear (B) -Encontra bugs comuns: -- Argumentos mutáveis padrão -- Uso incorreto de assert -- Loops problemáticos - -### pyupgrade (UP) -Moderniza código: -- Sintaxe antiga → moderna -- Type hints modernos -- F-strings - -## Integração com Editor - -### VS Code - -Instale a extensão Ruff e configure em `settings.json`: - -```json -{ - "[python]": { - "editor.formatOnSave": true, - "editor.codeActionsOnSave": { - "source.fixAll": true, - "source.organizeImports": true - }, - "editor.defaultFormatter": "charliermarsh.ruff" - }, - "ruff.lint.enable": true, - "ruff.format.args": ["--config=pyproject.toml"] -} -``` - -## Workflow Recomendado - -### Antes de Commitar - -```bash -# 1. Verificar problemas -ruff check src/ tests/ - -# 2. Corrigir automaticamente -ruff check --fix src/ tests/ - -# 3. Formatar código -ruff format src/ tests/ - -# 4. Verificar tipos (MyPy) -mypy src/ - -# 5. Executar testes -pytest -``` \ No newline at end of file diff --git a/docs/TESTES.md b/docs/TESTES.md deleted file mode 100644 index 2cf2829..0000000 --- a/docs/TESTES.md +++ /dev/null @@ -1,349 +0,0 @@ -# 🧪 Guia de Testes - BusSP - -## Executando os Testes - -Execute a suite de testes com pytest: - -```bash -# Execute todos os testes -pytest - -# Execute com cobertura -pytest --cov=src --cov-report=html - -# Execute arquivo de teste específico -pytest tests/core/test_trip_service_example.py - -# Execute com saída detalhada -pytest -v -``` - -## Como Escrever Testes com Mocks - -Este projeto utiliza **testes unitários isolados** com mocks para testar a lógica de negócio sem depender de banco de dados ou APIs externas. - -### Por que usar Mocks? - -Mocks permitem: -- **Isolar o código testado**: Testar apenas o serviço, sem depender de repositórios reais -- **Testes mais rápidos**: Sem I/O de banco de dados ou chamadas de rede -- **Controle total**: Simular diferentes cenários (sucesso, erro, casos extremos) -- **Testes determinísticos**: Resultados consistentes e previsíveis - -### Exemplo Prático: Testando `TripService` - -```python -import pytest -from unittest.mock import AsyncMock, create_autospec -from src.core.services.trip_service import TripService -from src.core.ports.user_repository import UserRepository -from src.core.ports.trip_repository import TripRepository -from src.core.models.user import User - -@pytest.mark.asyncio -async def test_create_trip_calculates_score_correctly() -> None: - """Testa cálculo de pontuação com mocks.""" - # 1. Arrange (Preparar) - Criar mocks - user_repo = create_autospec(UserRepository, instance=True) - trip_repo = create_autospec(TripRepository, instance=True) - - # Definir comportamento dos mocks - test_user = User( - name="Teste", - email="test@example.com", - score=0, - password="hash" - ) - user_repo.get_user_by_email = AsyncMock(return_value=test_user) - user_repo.add_user_score = AsyncMock(return_value=test_user) - trip_repo.save_trip = AsyncMock(side_effect=lambda t: t) - - # Injetar mocks no serviço - service = TripService(trip_repo, user_repo) - - # 2. Act (Agir) - Executar a função testada - trip = await service.create_trip( - email="test@example.com", - bus_line="8000", - bus_direction=1, - distance=1000, - trip_datetime=datetime.now() - ) - - # 3. Assert (Verificar) - Checar resultados - assert trip.score == 10 # 1000m = 10 pontos - assert trip.email == "test@example.com" - - # Verificar que os métodos foram chamados corretamente - user_repo.get_user_by_email.assert_awaited_once_with("test@example.com") - trip_repo.save_trip.assert_awaited_once() - user_repo.add_user_score.assert_awaited_once_with("test@example.com", 10) -``` - -### Ferramentas Utilizadas - -- **`create_autospec()`**: Cria um mock que respeita a interface original (detecta chamadas incorretas) -- **`AsyncMock`**: Mock para funções assíncronas (`async def`) -- **`return_value`**: Define o valor retornado pelo mock -- **`side_effect`**: Define comportamento customizado ou lança exceções -- **`assert_awaited_once_with()`**: Verifica que uma função async foi chamada uma vez com argumentos específicos - -### Padrão AAA (Arrange-Act-Assert) - -Organize seus testes em três seções: - -1. **Arrange (Preparar)**: Configure mocks, dados de teste e estado inicial -2. **Act (Agir)**: Execute a função/método sendo testado -3. **Assert (Verificar)**: Confirme que o resultado está correto e mocks foram usados adequadamente - -### Testando Casos de Erro - -```python -@pytest.mark.asyncio -async def test_create_trip_fails_for_nonexistent_user() -> None: - """Testa erro quando usuário não existe.""" - user_repo = create_autospec(UserRepository, instance=True) - trip_repo = create_autospec(TripRepository, instance=True) - - # Mock retorna None (usuário não encontrado) - user_repo.get_user_by_email = AsyncMock(return_value=None) - - service = TripService(trip_repo, user_repo) - - # Verifica que ValueError é lançado - with pytest.raises(ValueError, match="not found"): - await service.create_trip( - email="ghost@example.com", - bus_line="8000", - bus_direction=1, - distance=1000, - trip_datetime=datetime.now() - ) - - # Verifica que save_trip NÃO foi chamado - trip_repo.save_trip.assert_not_awaited() -``` - -### Testando Múltiplas Chamadas - -```python -@pytest.mark.asyncio -async def test_multiple_trips(mocker: "MockerFixture") -> None: - """Testa múltiplas chamadas sequenciais ao serviço.""" - # Arrange - user_repo = mocker.create_autospec(UserRepository, instance=True) - trip_repo = mocker.create_autospec(TripRepository, instance=True) - - test_user = User( - name="Bob", - email="bob@example.com", - score=0, - password="hash", - ) - user_repo.get_user_by_email = AsyncMock(return_value=test_user) - user_repo.add_user_score = AsyncMock(return_value=test_user) - trip_repo.save_trip = AsyncMock(side_effect=lambda t: t) - - service = TripService(trip_repo, user_repo) - - # Act - Criar duas viagens - trip1 = await service.create_trip( - email="bob@example.com", - bus_line="8000", - bus_direction=1, - distance=500, - trip_datetime=datetime.now(), - ) - - trip2 = await service.create_trip( - email="bob@example.com", - bus_line="8000", - bus_direction=2, - distance=1500, - trip_datetime=datetime.now(), - ) - - # Assert - assert trip1.score == 5 - assert trip2.score == 15 - assert trip_repo.save_trip.await_count == 2 - assert user_repo.add_user_score.await_count == 2 - user_repo.add_user_score.assert_any_await("bob@example.com", 5) - user_repo.add_user_score.assert_any_await("bob@example.com", 15) -``` - -### Testando Erros de Infraestrutura - -```python -@pytest.mark.asyncio -async def test_handles_repository_save_error(mocker: "MockerFixture") -> None: - """Testa tratamento de erro quando repositório falha.""" - # Arrange - user_repo = mocker.create_autospec(UserRepository, instance=True) - trip_repo = mocker.create_autospec(TripRepository, instance=True) - - test_user = User( - name="Charlie", - email="charlie@example.com", - score=0, - password="hash", - ) - user_repo.get_user_by_email = AsyncMock(return_value=test_user) - user_repo.add_user_score = AsyncMock() - trip_repo.save_trip = AsyncMock( - side_effect=RuntimeError("Database connection lost!") - ) - - service = TripService(trip_repo, user_repo) - - # Act & Assert - with pytest.raises(RuntimeError, match="Database connection lost"): - await service.create_trip( - email="charlie@example.com", - bus_line="8000", - bus_direction=1, - distance=1000, - trip_datetime=datetime.now(), - ) - - trip_repo.save_trip.assert_awaited_once() - # Score não deve ser adicionado se o save falhar - user_repo.add_user_score.assert_not_awaited() -``` - -## Boas Práticas em Testes - -### ✅ FAÇA: - -1. **Use `create_autospec()` para garantir type safety nos mocks** - - Detecta chamadas incorretas em tempo de teste - - Mantém compatibilidade com a interface - -2. **Teste um comportamento por vez (testes focados)** - ```python - # Bom - testa apenas cálculo de score - def test_calculates_score_correctly(): - ... - - # Bom - testa apenas validação de usuário - def test_validates_user_exists(): - ... - ``` - -3. **Nomeie testes descritivamente** - - Padrão: `test___` - - Exemplos: - - `test_create_trip_calculates_score_correctly` - - `test_create_trip_fails_for_nonexistent_user` - - `test_handles_repository_save_error` - -4. **Teste casos de sucesso E casos de erro** - ```python - # Teste o caminho feliz - def test_create_user_success(): - ... - - # Teste validações - def test_create_user_with_invalid_email_fails(): - ... - - # Teste erros de infraestrutura - def test_create_user_handles_database_error(): - ... - ``` - -5. **Verifique que mocks foram chamados corretamente** - ```python - user_repo.get_user_by_email.assert_awaited_once_with("test@example.com") - trip_repo.save_trip.assert_not_awaited() - ``` - -6. **Use pytest-mock para sintaxe mais limpa** - ```python - def test_something(mocker: "MockerFixture") -> None: - mock = mocker.create_autospec(UserRepository, instance=True) - ``` - -### ❌ NÃO FAÇA: - -1. **Testar implementações internas** - - Teste comportamento, não implementação - - Se refatorar sem mudar comportamento, testes devem passar - -2. **Criar testes que dependem de ordem de execução** - ```python - # Ruim - testes dependentes - def test_step1(): - global user - user = create_user() - - def test_step2(): - user.update() # Depende de test_step1 - ``` - -3. **Ignorar casos extremos (edge cases)** - - Teste valores nulos, vazios, negativos - - Teste limites (0, -1, max_int) - - Teste strings vazias, listas vazias - -4. **Mockar tudo** - - Mock apenas dependências externas (DB, APIs, I/O) - - Não mock objetos de domínio simples - ```python - # Bom - mock apenas repositórios - user_repo = create_autospec(UserRepository) - test_user = User(name="Test") # User real, não mock - - # Ruim - mockar tudo - user_repo = create_autospec(UserRepository) - test_user = create_autospec(User) # Desnecessário - ``` - -5. **Usar valores mágicos sem explicação** - ```python - # Ruim - assert trip.score == 10 - - # Bom - deixe claro de onde vem o valor - distance = 1000 - expected_score = distance // 100 - assert trip.score == expected_score # 10 pontos - ``` - -## Estrutura de Testes - -``` -tests/ -├── __init__.py -├── adapters/ # Testes de adaptadores (integração) -│ ├── test_user_repository_adapter.py -│ └── test_sptrans_adapter.py -├── core/ # Testes de lógica de negócio (unitários) -│ ├── test_trip_service.py -│ ├── test_user_service.py -│ └── test_score_service.py -└── web/ # Testes de controllers (integração) - ├── test_trip_controller.py - └── test_user_controller.py -``` - -## Cobertura de Testes - -Mantenha cobertura alta na camada Core (lógica de negócio): - -```bash -# Gerar relatório de cobertura -pytest --cov=src --cov-report=html - -# Abrir relatório no navegador -open htmlcov/index.html -``` - -**Meta**: Mínimo 80% de cobertura na camada `src/core/`. - -## Referências - -- Exemplo completo: `tests/core/test_trip_service_example.py` -- Documentação pytest: https://docs.pytest.org/ -- Documentação unittest.mock: https://docs.python.org/3/library/unittest.mock.html -- pytest-mock: https://pytest-mock.readthedocs.io/ diff --git a/docs/TIPAGEM.md b/docs/TIPAGEM.md deleted file mode 100644 index a071cd7..0000000 --- a/docs/TIPAGEM.md +++ /dev/null @@ -1,55 +0,0 @@ -# 🔍 Guia de Tipagem Estática - BusSP - -## O que é Tipagem Estática? - -**Tipagem estática** significa que os tipos de variáveis são verificados **antes** da execução do código. Python é uma linguagem dinamicamente tipada, mas com **type hints** podemos adicionar anotações de tipo que ferramentas como MyPy verificam. - -### Benefícios - -- **Detecção precoce de erros**: Encontre bugs de tipo antes de rodar o código -- **Melhor IDE/editor**: Autocompletar mais inteligente e sugestões precisas -- **Documentação viva**: Tipos documentam o código automaticamente -- **Refatoração segura**: Mudanças de tipo são detectadas em todo o codebase -- **Manutenibilidade**: Código mais claro e fácil de entender - -## Como Funciona MyPy - -MyPy analisa seu código Python e verifica se os tipos estão sendo usados corretamente: - -```python -# ✅ Correto -def calculate_score(distance: int) -> int: - return distance // 100 - -score: int = calculate_score(1000) # OK: retorna int - -# ❌ Erro detectado pelo MyPy -score: str = calculate_score(1000) # ERRO: int não é compatível com str -result = calculate_score("1000") # ERRO: str não é compatível com int -``` - -## Executando MyPy - -```bash -# Verificar todos os arquivos -mypy src/ - -# Verificar arquivo específico -mypy src/core/services/user_service.py - -# Verificar com relatório detalhado -mypy --show-error-codes src/ - -# Verificar e mostrar apenas erros (sem warnings) -mypy --no-error-summary src/ -``` - -## Configuração Rigorosa - -Este projeto usa configuração **strict** no `mypy.ini`: - -Isso significa: -- **Todas** as funções precisam de type hints -- **Nenhum** `Any` implícito é permitido -- Retornos devem ter tipos explícitos -- Parâmetros devem ter tipos explícitos diff --git a/docs/arquitetura.pdf b/docs/arquitetura.pdf new file mode 100644 index 0000000..5e09698 Binary files /dev/null and b/docs/arquitetura.pdf differ diff --git a/docs/arquitetura_backend.drawio.html b/docs/arquitetura_backend.drawio.html deleted file mode 100644 index 2243dda..0000000 --- a/docs/arquitetura_backend.drawio.html +++ /dev/null @@ -1,11 +0,0 @@ - - - - -Diagrama sem nome - - -
- - - \ No newline at end of file