Skip to content

test: plano de testagem 2026 #33

@bdcdo

Description

@bdcdo

Contexto

Esta issue substitui #3 (placeholder vazio) e #4 ("Adicionar testes para todos os scrapers", de 2025, escrita quando o raspe tinha 5 scrapers). Hoje a biblioteca cobre 11 scrapers HTTP — presidencia, camara, senado, ipea, cfm, folha, nyt, datalegis, saudelegis, ans, anvisa — e o escopo de testagem precisa de uma issue guarda-chuva.

Objetivo: rastrear o plano de testagem completo da biblioteca. Cada fase abre sub-issues/sub-PRs com escopo concreto. Espelha o modelo de jtrecenti/juscraper#113, adaptado às particularidades de raspe.

Estado atual

Fase 0 — Fundação entregue via PR #31 (commit 9c02f56):

  • Infraestrutura pytest com responses>=0.25.0, pytest-mock>=3.12.0, pytest-cov.
  • Markers slow e integration; addopts exclui integração por padrão; filterwarnings = ["error"].
  • Helpers load_sample() e load_sample_bytes() em tests/_helpers.py.
  • Captura padrão em tests/fixtures/capture/_util.py (dump_response, attach_capture_hook) + README.
  • Checklist obrigatória de 8 itens documentada em CLAUDE.md > "Testes".

Cobertura por scraper (0/11 com contrato offline):

Scraper Contrato Samples Captura
presidencia
camara ⏸
senado ⏸
ipea
cfm
folha
nyt
datalegis
saudelegis
ans
anvisa

⏸ = aguarda refactor (ver Fase 1).

tests/test_utils.py cobre expand() (8 testes). Sem CI configurado — pre-commit roda lint/types localmente, mas testes não rodam em PRs.

Fases

Fase 0 — Fundação ✅

Entregue em PR #31. Sem trabalho pendente.

Fase 1 — Contratos offline universais (em andamento)

Tracker: #30. Contratos via responses + samples reais commitados, rodam em segundos sem rede. Cada método público (raspar e auxiliares) ganha tests/<scraper>/test_raspar_contract.py com no mínimo 3 cenários: typical (paginação), single_page, no_results.

Sequenciamento dos sub-PRs (refina o que está em #30):

  • 1A — presidencia, ipea. Estabelece o padrão; ajustes em helpers/fixtures podem aparecer aqui.
  • 1B — cfm, folha, nyt. Padrão consolidado; foco em diversidade de payload (form-encoded, JSON, query string).
  • 1C — datalegis, saudelegis, ans, anvisa. Demais scrapers HTTP da família regulatória/legislativa.
  • 1Dcamara, senado (por último, após o refactor de cada um). Capturar samples antes do refactor congelaria contratos em código que vai mudar.

Cada sub-PR mergeia antes do próximo; helpers/samples evoluem incrementalmente. Bug descoberto pelo contrato → issue separada, não misturar fix no PR de testes.

Fase 2 — Wiring de schemas pydantic Input/Output

Espelha jtrecenti/juscraper#117 (que entregou os schemas universais para os 27 scrapers do juscraper). No raspe, com 11 scrapers HTTP e menos divergência entre famílias, deve ser substancialmente mais simples.

Escopo:

  • Definir modelos pydantic Input (parâmetros públicos do método raspar) e Output (colunas do DataFrame retornado) por scraper, em src/raspe/schemas/<scraper>.py (ou agrupados em src/raspe/schemas/__init__.py se ficarem curtos).
  • Padronizar nomes canônicos de entrada (ex.: pesquisa, data_inicio, data_fim, paginas) e de saída (colunas do DataFrame) através dos schemas. Aliases deprecados de versões anteriores, se houver, emitem DeprecationWarning e mapeiam para o nome canônico — mesmo padrão usado pelo juscraper.
  • Wiring: cada <scraper>.raspar(...) valida kwargs contra o Input pydantic na entrada (_raise_on_extra para kwargs desconhecidos) e o DataFrame retornado contra o Output na saída.
  • Suíte anti-drift em tests/schemas/, espelhando o juscraper:
    • test_schema_coverage.py — todo scraper público tem schema Input/Output registrado.
    • test_signature_parity.py — a assinatura de raspar bate com os campos do Input (sem campo público sem schema, sem schema sem campo público).
    • test_output_parity.py — as colunas de cada sample typical batem com o Output canônico (apertando o subset da Fase 1 para igualdade).
    • test_canonical_types.py — tipos de dado bate com o que o schema declara.
  • A Fase 2 depende dos contratos da Fase 1 estarem fechados para os scrapers em questão — os samples já validados servem de fixture para test_output_parity.
  • Os contratos da Fase 1 são apertados quando o schema canônico do scraper entra: o subset ({"col_a"} <= set(df.columns)) vira igualdade contra o Output pydantic. Isso passa a ser regra a partir do PR de wiring de cada scraper.

Ordem sugerida de wiring: mesma da Fase 1 (1A → 1D), começando por scrapers menores (presidencia, ipea) para validar o pipeline de schema antes de aplicar aos demais. camara/senado entram só após o refactor + contrato de cada um.

Resultado esperado: padronização de nomes de entrada/saída entre todos os scrapers do raspe, contrato pydantic ponta-a-ponta, suíte anti-drift que impede regressões silenciosas.

Fase 3 — Cassetes VCR caso a caso

Adoção via pytest-recording apenas para fluxos com estado dinâmico (token rotativo, ViewState, JWT) que tornem o contrato responses-puro inviável. Avaliar caso a caso quando aparecer; medir o peso agregado dos cassetes antes de generalizar (limite indicativo: ~20 MB no repo).

Hoje nenhum scraper de raspe parece exigir VCR — esta fase fica em standby até que algum sub-PR da Fase 1 demonstre necessidade.

Fase 4 — Testes granulares pós-refactor

Após cada refactor de scraper (em particular camara e senado), abrir sub-issue de testes granulares para a parte refatorada. Granulares cobrem funções puras / quase-puras (parser HTML, builder de body, lógica de paginação) e vêm por cima dos contratos da Fase 1 e do wiring da Fase 2, não em substituição.

Princípios

Herdados de CLAUDE.md e do modelo juscraper:

  1. Contratos validam API pública (DataFrame de saída, requests emitidos), não estrutura interna — sobrevivem refactor.
  2. Samples são parte integrante do contrato, capturados via scripts versionados em tests/fixtures/capture/<scraper>.py.
  3. Offline-first, determinístico, rápido. Contratos não tocam rede, relógio nem TLS real (mocker.patch("time.sleep") em paginação).
  4. Schema validado por subset ({"col_a", "col_b"} <= set(df.columns)), nunca igualdade — protege contra adição legítima de colunas. (Apertado para igualdade conforme cada scraper recebe wiring na Fase 2.)
  5. Cenários mínimos por endpoint: typical, single_page, no_results.
  6. Matchers de payload sempre que possível: urlencoded_params_matcher(strict_match=False), json_params_matcher, query_param_matcher.
  7. Captchas, tokens dinâmicos, lazy imports (ex.: txtcaptcha, browser_cookie3) são mockados via mocker.patch.dict(sys.modules, ...) — nunca invocados de verdade.
  8. Fluxos multi-step com ordem obrigatória usam responses.registries.OrderedRegistry.
  9. Bug exposto por contrato vira issue separada — PR de testes não vem com fix embutido.

Checklist obrigatória por scraper novo (ou novo método)

A versão canônica vive em CLAUDE.md > "Testes" > "Checklist obrigatória ao adicionar um novo scraper". Resumo aqui para referência:

  1. Script de captura em tests/fixtures/capture/<xx>.py que exercita o scraper real e salva respostas cruas.
  2. Samples commitados em tests/<xx>/samples/<endpoint>/ (mínimo 3 cenários: typical, single_page, no_results).
  3. Teste de contrato em tests/<xx>/test_raspar_contract.py:
    • @responses.activate.
    • mocker.patch("time.sleep") em qualquer função com paginação.
    • responses.add(..., body=load_sample_bytes("<xx>", "raspar/<cenario>.<ext>")) por request.
    • Matcher de payload sempre que possível.
    • Schema validado por subset.
  4. Sem @pytest.mark.integration no contrato.
  5. Sem rede, relógio ou TLS real. Adapter custom (ex.: SSL desabilitado) é testado só via isinstance.
  6. Fluxos multi-step com ordem obrigatória usam OrderedRegistry.
  7. Captchas/tokens/lazy imports mockados via mocker.patch.dict(sys.modules, ...).
  8. CHANGELOG: novo scraper já é entrada Adicionado; testes para scraper existente é mudança interna e não entra no CHANGELOG.

Critério de aceite global

Esta issue fica fechada quando:

  • Todos os 11 scrapers HTTP têm contrato offline com os 3 cenários mínimos (Fase 1).
  • Todos os 11 scrapers têm schemas pydantic Input/Output canônicos, com wiring ativo e suíte anti-drift verde (Fase 2).
  • pytest -m "not integration" roda em < 10 s sem tocar rede.
  • Bugs expostos pelos contratos viram issues próprias e estão tratados ou priorizados.
  • Decisão registrada sobre Fases 3 (VCR) e 4 (granulares): aplicadas onde fizer sentido ou explicitamente dispensadas.

Referências

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions