Testes¶
Guia completo sobre testes no Global-Data-Finance.
Estrutura de Testes¶
A árvore de testes espelha cada fonte. Os subdiretórios dentro de cada feature são organizacionais (agrupam por tópico para legibilidade), não arquiteturais — qualquer teste importa diretamente dos módulos da fonte (from globaldatafinance.brazil.<país>.<fonte>.<módulo> import ...).
tests/
├── application/ # tests do facade público
│ ├── cvm_docs/
│ └── b3_docs/
│ └── result_formatters/
├── brazil/
│ ├── b3_data/
│ │ └── historical_quotes/ # layout plano: 21 test_*.py diretamente na pasta
│ └── cvm/
│ └── fundamental_stocks_data/
│ ├── application/use_cases/ # tests de orquestração (client.py)
│ ├── domain/ # tests de value objects e validators (core.py)
│ ├── infra/adapters/ # tests dos adapters concretos (http.py, extract.py)
│ ├── exceptions/ # tests das exceções (errors.py)
│ └── integration/ # tests integration-marker
├── core/
├── macro_infra/
└── macro_exceptions/
Executando Testes¶
Todos os Testes¶
uv run pytest
Com Cobertura¶
pytest.ini já força fail_under = 70 na cobertura agregada.
uv run pytest --cov=src --cov-report=html
Marcadores¶
Os markers registrados em pytest.ini são: unit, integration, slow, asyncio (com --strict-markers, então qualquer marker não declarado falha).
# Apenas testes unitários
uv run pytest -m unit
# Apenas testes de integração
uv run pytest -m integration
# Combinar markers
uv run pytest -m "integration and not slow"
Escrevendo Testes¶
Teste Unitário¶
import pytest
from globaldatafinance.brazil.cvm.fundamental_stocks_data.core import AvailableDocsCVM
from globaldatafinance.brazil.cvm.fundamental_stocks_data.errors import InvalidDocName
@pytest.mark.unit
class TestAvailableDocs:
def test_validate_valid_doc(self):
"""Testa validação de documento válido."""
docs = AvailableDocsCVM()
docs.validate_docs_name("DFP") # Não deve lançar exceção
def test_validate_invalid_doc(self):
"""Testa validação de documento inválido."""
docs = AvailableDocsCVM()
with pytest.raises(InvalidDocName):
docs.validate_docs_name("INVALID")
Tipos e exceções de cada fonte vivem nos módulos da própria fonte: para CVM em
brazil.cvm.fundamental_stocks_data.coreebrazil.cvm.fundamental_stocks_data.errors; para B3 a divisão é mais granular — entidades emmodels.py, value objects emyears.py/processing.py, validators de filesystem emfilesystem.py, asset services emassets.py, exceções emerrors.py.
Mocking sem ABC¶
Como os adapters não são mais ABCs, tests substituem dependências via stub duck-typed ou monkeypatch.setattr:
class MockRepository:
def download_docs(self, tasks):
return DownloadResultCVM(
success_count_downloads=2,
error_count_downloads=0,
successful_downloads=["DFP_2023", "ITR_2023"],
failed_downloads={},
)
from globaldatafinance.brazil.cvm.fundamental_stocks_data.client import (
DownloadDocumentsUseCaseCVM,
)
use_case = DownloadDocumentsUseCaseCVM(MockRepository())
result = use_case.execute(destination_path="/tmp/cvm")
assert result.success_count_downloads == 2
Teste de Integração¶
import pytest
from globaldatafinance import FundamentalStocksDataCVM
@pytest.mark.integration
class TestFundamentalStocksDataIntegration:
def test_get_available_docs(self):
"""Testa obtenção de documentos disponíveis."""
cvm = FundamentalStocksDataCVM()
docs = cvm.get_available_docs()
assert isinstance(docs, dict)
assert len(docs) > 0
assert "DFP" in docs
Fixtures¶
import pytest
from pathlib import Path
@pytest.fixture
def temp_dir(tmp_path):
"""Cria diretório temporário para testes."""
return tmp_path
@pytest.fixture
def sample_zip_file(tmp_path):
"""Cria arquivo ZIP de exemplo."""
zip_path = tmp_path / "test.zip"
# Criar ZIP...
return zip_path
Cobertura¶
Objetivo: >= 70% de cobertura agregada (enforced via fail_under = 70 em pytest.ini). O alvo prático é >= 80% em módulos novos.
# Gerar relatório
uv run pytest --cov=src --cov-report=term-missing
# Relatório HTML
uv run pytest --cov=src --cov-report=html
open htmlcov/index.html
CI/CD¶
Testes são executados automaticamente em:
- Push para
mainoudevelop - Pull Requests
- Releases
Veja também: