Estratégia de Testes (MVP) — KotaJá¶
Este documento descreve a estratégia, o escopo, a abordagem e os casos de teste aplicados ao MVP do KotaJá, com foco no entregável atual: Consulta Pública (média mensal por região e variante).
Objetivo: garantir qualidade, estabilidade e rastreabilidade do MVP com testes automatizados, priorizando core de domínio/serviço/repositórios, e cobrindo a aplicação Streamlit com smoke tests e testes de fluxos críticos (sem depender do ambiente real).
1. Escopo de Teste¶
1.1 Incluído (nesta entrega / MVP)¶
Camada de Domínio¶
-
QuoteFilters/QuoteResult -
criação, defaults, imutabilidade (
frozen=True), igualdade/hash
Camada de Serviço¶
-
QuoteService.get_quote -
retorno
Nonequando não há resultado - conversão de tipos no payload retornado (ex.:
Decimal -> float,UUID -> str) - garantia de log em sucesso e em “sem resultado”
Camada de Repositórios (SQL / acesso a dados)¶
-
CatalogRepository -
list_regions,list_brands,list_models(brand_id),list_variants(model_id) -
QuoteRepository -
fetch_monthly_average(filters)com parâmetros corretos (incluindo duplicação do variant_id por causa doOR) insert_public_query_log(...)executaINSERTecommit-
LogRepository -
insert_public_query_log(...)gera hash do IP (LGPD-friendly) e fazcommit _hash_ipcom SHA-256-
PublicQueryRepository -
list_recent_enriched(limit)executa query enriquecida (LEFT JOIN) e trata retorno vazio
Camada de Infra (DB)¶
-
src/db/connection.py -
carregamento do
.env(quando existe) - validação de
DATABASE_URL get_conn()chamapsycopg.connect(...)comrow_factory=dict_row-
src/db/session.py -
fallback para SQLite quando não há
DATABASE_URL - engine sqlite com
check_same_thread=False - engine não-sqlite com
connect_args={} SessionLocalcomosessionmaker-
src/db/base.py -
BaseéDeclarativeBase, possuimetadata, permite declarar modelos
Aplicação Streamlit (UI)¶
app/bootstrap.py: smoke test do CSS injetado viast.markdown-
app/app.py: -
smoke test garantindo que a aplicação “executa” sem quebrar
-
testes adicionais (quando aplicável) para cobrir fluxos sem dependência de DB real:
- inicialização de filtros em
session_state - caminhos de sucesso/erro no histórico
- (opcional) simulação de clique em botões e branches de UI
- inicialização de filtros em
Nota: os testes de UI são não-end-to-end; são testes estruturais/funcionais usando mock/fakes do Streamlit para cobrir fluxos críticos sem depender do runtime do Streamlit nem do banco real.
1.2 Fora do escopo (próximas entregas)¶
- Login e área privada (Admin/Backoffice)
- RBAC e autorização por perfis
- Batch mensal automatizado (apenas design no MVP)
- Testes end-to-end (Playwright/Selenium) e testes reais de interface
- Observabilidade completa (métricas, tracing, etc.)
2. Abordagem e Princípios¶
2.1 Pirâmide de testes adotada¶
- Unitários (principal): regras de negócio e conversões do serviço
- Unitários com mocks (repositories): valida SQL/params/commit sem DB real
- Smoke tests (Streamlit): garante que o app executa e que estados críticos são criados
2.2 Isolamento e determinismo¶
- Testes não dependem de rede, DB externo, nem Streamlit runtime real.
- Acesso ao DB é isolado via mocks em
get_conn()/cursor(). - O script Streamlit é executado via
runpy.run_pathcom um fake do módulostreamlit.
2.3 Riscos mitigados¶
- Quebras por refactor (assinatura de métodos, alteração de SQL, mudanças em params)
- Falhas silenciosas no log (não registrar consultas)
- Falhas de conversão (Decimal/UUID) na saída pública
- Regressões na inicialização do
session_statedo app
3. Tipos de Teste e Implementação¶
3.1 Testes Unitários (principal)¶
Objetivo: testar regras e fluxos do serviço sem DB real.
Exemplos cobertos:
QuoteService.get_quoteretornaNonequando repo não acha linhaQuoteService.get_quoteconverteDecimal -> floateUUID -> str- logging é chamado em sucesso e em “sem resultado”
3.2 Testes de Repositório com mocks (unitários de SQL)¶
Objetivo: validar que:
- o SQL executado contém as cláusulas esperadas
- os parâmetros enviados são corretos
- quando aplicável,
conn.commit()é chamado
Esses testes não validam “se o SQL roda em Postgres real”; eles garantem que o contrato (query/params/commit) está correto.
3.3 Smoke Tests de Aplicação (Streamlit)¶
Objetivo: garantir que app/app.py:
- inicializa estado (
session_state) corretamente - renderiza sem exceptions (com dependências stubadas)
- cobre branches críticos (histórico vazio/erro, fallback de logo, etc.), quando ativado
4. Casos de Teste (MVP — Consulta Pública)¶
Legenda
- Camada alvo: Service / Repository / App
- Tipo: Unit / Smoke
- Isolamento: Mock/Stubs (sem DB real)
CT-01 — Consulta com resultado (Service)¶
Dado filtros válidos
Quando fetch_monthly_average retorna um registro
Então
- retorno é um
dictcom campos esperados avg_price(Decimal) é convertido parafloatquando aplicávelregion_idevehicle_variant_id(UUID) são convertidos parastrquando aplicável- log é inserido (1x)
CT-02 — Consulta sem resultado (Service)¶
Dado filtros válidos
Quando repo retorna None
Então
- retorno é
None - log é inserido (1x)
CT-03 — Repositório de consulta executa SQL e params corretos (Repository)¶
Dado filtros válidos
Quando fetch_monthly_average é chamado
Então
cur.execute(sql, params)recebemonth_ref,region_idevehicle_variant_idrepetido 2x (por causa do OR)- retorna
dict(row)quandofetchone()existe - retorna
Nonequandofetchone()éNone
CT-04 — Repositório de log (LGPD) calcula hash e faz commit (Repository)¶
Dado IP informado
Quando insert_public_query_log é chamado
Então
ip_hash = sha256(ip)é persistidoconn.commit()é executado
E quando IP não é informado Então
ip_hashéNoneconn.commit()é executado
CT-05 — Catálogo retorna listas e respeita parâmetros (Repository)¶
Dado um brand_id/model_id
Quando chamadas list_models(brand_id) e list_variants(model_id)
Então
- SQL contém
WHERE brand_id = %s/WHERE model_id = %s - params são tuplas com o UUID correto
- retorno é
List[Dict]
CT-06 — Histórico enriquecido retorna lista (Repository)¶
Dado public_quote_queries com joins
Quando list_recent_enriched(limit) é chamado
Então
- SQL contém
LEFT JOINnecessários LIMIT %susa parâmetrolimit- quando
fetchall()éNone, retorna lista vazia
CT-07 — App “sobe” e inicializa filtros (Smoke)¶
Dado Streamlit fake + repos stubados
Quando o script app/app.py é executado
Então
- app não lança exception
st.session_state.filtersexiste e temregion_id,brand_id,model_id,vehicle_variant_id
5. Critérios de Aceite (Qualidade)¶
- A camada
src/deve permanecer com alta cobertura (objetivo ≥ 90% / ideal 100%) - Fluxos principais (CT-01 a CT-06) devem ter testes automatizados
- Aplicação Streamlit deve possuir pelo menos um smoke test garantindo execução e estado inicial
- Cobertura deve ser gerada e revisada antes de release do MVP
6. Ferramentas e Execução¶
6.1 Ferramentas¶
- Test runner: pytest
- Cobertura: coverage.py (relatório no terminal e HTML)
6.2 Comandos (execução local)¶
Rodar testes:
pytest -q
Rodar cobertura focando em src e app:
python -m coverage run --source=src,app -m pytest
python -m coverage report -m
python -m coverage html
Abrir relatório HTML:
- arquivo gerado:
htmlcov/index.html
7. Evidência de Cobertura¶
8. Observações¶
8.1 Melhorias futuras¶
- Testes de integração com Postgres real (Docker) para validar SQL em runtime
- Testes end-to-end da UI (Playwright) simulando interação real
- Métricas/observabilidade para erros (Sentry/OTel)
- Refinar logging (ex.: registrar também
result_foundse for requisito)
