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.
- 1D — camara, 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:
- Contratos validam API pública (DataFrame de saída, requests emitidos), não estrutura interna — sobrevivem refactor.
- Samples são parte integrante do contrato, capturados via scripts versionados em
tests/fixtures/capture/<scraper>.py.
- Offline-first, determinístico, rápido. Contratos não tocam rede, relógio nem TLS real (
mocker.patch("time.sleep") em paginação).
- 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.)
- Cenários mínimos por endpoint: typical, single_page, no_results.
- Matchers de payload sempre que possível:
urlencoded_params_matcher(strict_match=False), json_params_matcher, query_param_matcher.
- Captchas, tokens dinâmicos, lazy imports (ex.:
txtcaptcha, browser_cookie3) são mockados via mocker.patch.dict(sys.modules, ...) — nunca invocados de verdade.
- Fluxos multi-step com ordem obrigatória usam
responses.registries.OrderedRegistry.
- 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:
- Script de captura em
tests/fixtures/capture/<xx>.py que exercita o scraper real e salva respostas cruas.
- Samples commitados em
tests/<xx>/samples/<endpoint>/ (mínimo 3 cenários: typical, single_page, no_results).
- 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.
- Sem
@pytest.mark.integration no contrato.
- Sem rede, relógio ou TLS real. Adapter custom (ex.: SSL desabilitado) é testado só via
isinstance.
- Fluxos multi-step com ordem obrigatória usam
OrderedRegistry.
- Captchas/tokens/lazy imports mockados via
mocker.patch.dict(sys.modules, ...).
- 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
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):responses>=0.25.0,pytest-mock>=3.12.0,pytest-cov.sloweintegration;addoptsexclui integração por padrão;filterwarnings = ["error"].load_sample()eload_sample_bytes()emtests/_helpers.py.tests/fixtures/capture/_util.py(dump_response,attach_capture_hook) + README.CLAUDE.md> "Testes".Cobertura por scraper (0/11 com contrato offline):
⏸ = aguarda refactor (ver Fase 1).
tests/test_utils.pycobreexpand()(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 (raspare auxiliares) ganhatests/<scraper>/test_raspar_contract.pycom no mínimo 3 cenários: typical (paginação), single_page, no_results.Sequenciamento dos sub-PRs (refina o que está em #30):
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:
raspar) e Output (colunas do DataFrame retornado) por scraper, emsrc/raspe/schemas/<scraper>.py(ou agrupados emsrc/raspe/schemas/__init__.pyse ficarem curtos).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, emitemDeprecationWarninge mapeiam para o nome canônico — mesmo padrão usado pelo juscraper.<scraper>.raspar(...)valida kwargs contra o Input pydantic na entrada (_raise_on_extrapara kwargs desconhecidos) e o DataFrame retornado contra o Output na saída.tests/schemas/, espelhando o juscraper:test_schema_coverage.py— todo scraper público tem schema Input/Output registrado.test_signature_parity.py— a assinatura derasparbate 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 sampletypicalbatem 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.test_output_parity.{"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-recordingapenas para fluxos com estado dinâmico (token rotativo, ViewState, JWT) que tornem o contratoresponses-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.mde do modelo juscraper:tests/fixtures/capture/<scraper>.py.mocker.patch("time.sleep")em paginação).{"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.)urlencoded_params_matcher(strict_match=False),json_params_matcher,query_param_matcher.txtcaptcha,browser_cookie3) são mockados viamocker.patch.dict(sys.modules, ...)— nunca invocados de verdade.responses.registries.OrderedRegistry.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:tests/fixtures/capture/<xx>.pyque exercita o scraper real e salva respostas cruas.tests/<xx>/samples/<endpoint>/(mínimo 3 cenários: typical, single_page, no_results).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.@pytest.mark.integrationno contrato.isinstance.OrderedRegistry.mocker.patch.dict(sys.modules, ...).Adicionado; testes para scraper existente é mudança interna e não entra no CHANGELOG.Critério de aceite global
Esta issue fica fechada quando:
pytest -m "not integration"roda em < 10 s sem tocar rede.Referências
CLAUDE.md— checklist obrigatória por scraper.jtrecenti/juscraper#113— issue mãe equivalente, modelo desta.jtrecenti/juscraper#117— entrega dos schemas pydantic Input/Output universais (modelo da Fase 2).jtrecenti/juscraper#104,#106,#119,#123,#142,#146— sub-issues/PRs do modelo, citados em test(fase 1): contratos offline para os 7 scrapers HTTP #30.