🎯 Três níveis, três responsabilidades
Cada nível pega uma classe diferente de bug. Confiar só em E2E é caro e lento; confiar só em unit é cego para integração. Pirâmide tradicional, aplicada ao stack Rust + React + Tauri.
📊 A pirâmide aplicada
Unit (Vitest + cargo test) — base larga
Rápido (segundos), barato, alta granularidade. Pega bugs de lógica isolada.
RPC E2E (json_rpc_e2e.rs) — meio
Médio (dezenas de segundos). Prova o contrato entre Rust e TS pelo transporte real.
App E2E (WDIO) — topo estreito
Lento (minutos). Prova o fluxo completo no app empacotado, com webdriver.
💡 Regra de ouro
Teste comportamento visível ao usuário, não implementação. Se o refactor não muda comportamento, o teste não deve quebrar. Se quebrou, ou o teste estava errado, ou o refactor mudou comportamento — em ambos os casos, decisão de produto.
⚡ Vitest: rápido e co-localizado
Testes ficam ao lado do código, como *.test.ts / *.test.tsx.
Pastas __tests__ separadas viram cemitério — co-localização força disciplina.
# Full suite $ pnpm test # Com coverage $ pnpm test:coverage # Debug runner (output bounded, log em target/debug-logs/) $ pnpm debug unit # tudo $ pnpm debug unit src/components/Foo.test.tsx # um arquivo $ pnpm debug unit -t "renders empty state" # por nome $ pnpm debug unit Foo -t "renders" --verbose # streaming raw # Ver logs salvos $ pnpm debug logs last --tail 100
✓ Bom teste Vitest
- ✓Testa render + interação do usuário
- ✓Usa helpers de
app/src/test/ - ✓Mocka
coreRpcClient, nãofetch - ✓Roda em <100ms cada
✗ Teste frágil
- ✗Asserta estado interno do componente
- ✗Depende de
setTimeoutreal - ✗Faz request HTTP real
- ✗Cobre só happy path
🎭 Mock backend compartilhado
Um único mock backend serve unit, RPC E2E e WDIO. Isso elimina drift de comportamento entre níveis. Você muda o mock uma vez, todos os testes veem a nova realidade.
📦 Arquivos do mock
- •
scripts/mock-api-core.mjs— comportamento (handlers, dados in-memory) - •
scripts/mock-api-server.mjs— server HTTP que expõe o core - •
app/test/e2e/mock-server.ts— wrapper TS pro WDIO
# Subir o mock manualmente (debug) $ pnpm mock:api # Admin endpoints (uso programático nos testes) GET /__admin/health # health check POST /__admin/reset # volta ao estado inicial POST /__admin/behavior # injeta erro / latência / shape GET /__admin/requests # lista requests recebidas
🔄 Padrão de uso em spec
Antes de cada teste, faça POST /__admin/reset e configure o cenário via /__admin/behavior. Depois assertee usando /__admin/requests pra verificar que a UI chamou o que devia.
Resultado: testes determinísticos, sem ordem dependente, sem flake por estado residual.
🌐 WDIO em dois sistemas
E2E roda contra o app empacotado, não contra dev server. CI usa Linux com
tauri-driver; dev local em macOS usa Appium Mac2 sobre o .app. Os dois caminhos têm que
funcionar antes do PR.
🐧 Linux (CI)
- •
tauri-driver+ WebDriver :4444 - • Sem janelas reais (headless)
- • Usa Docker compose se necessário
docker compose -f e2e/docker-compose.yml run --rm e2e
🍎 macOS (local)
- • Appium Mac2 (XCUITest :4723)
- • Roda sobre o
.appbundle - • Requer build prévio (
pnpm test:e2e:build) - • Helpers nativos via
clickNativeButton
# Build + roda spec único (cria workspace temp limpo) $ pnpm test:e2e:build $ bash app/scripts/e2e-run-spec.sh test/e2e/specs/smoke.spec.ts smoke # Suite full de flows $ pnpm test:e2e:all:flows # Debug runner $ pnpm debug e2e test/e2e/specs/cron-jobs-flow.spec.ts cron-jobs --verbose
⚠️ Cuidado
- • Nunca use seletores raw
XCUIElementType*— fragiliza demais. Useelement-helpers.ts. - • Não asserte presença de elemento DOM — asserte outcome (texto na tela, request no mock).
- • Cada spec recebe workspace temp limpo via
OPENHUMAN_WORKSPACE; não vaze estado entre specs.
🦀 cargo + mock: provar o contrato
tests/json_rpc_e2e.rs é onde o contrato RPC vive. Rodando contra o
mock backend, você prova que a serialização, auth e shape do payload funcionam ponta a ponta — sem
precisar de UI.
# Suite Rust completa (sobe o mock automaticamente) $ pnpm test:rust # Um teste específico $ bash scripts/test-rust-with-mock.sh --test json_rpc_e2e # Filtro por nome $ bash scripts/test-rust-with-mock.sh --test json_rpc_e2e -- cron::create # Debug runner $ pnpm debug rust # tudo $ pnpm debug rust json_rpc_e2e # filtrado
🧰 O que testar aqui
- • Cada método RPC novo: happy path + erros de validação
- • Auth: chamada sem bearer → 401
- • Shape: response casa com o que a UI vai parsear
- • Lifecycle: create → list → update → delete numa única sequência
📊 Coverage gate ≥80%
Não é cobertura global — é cobertura nas linhas que você acabou de mudar.
diff-cover roda no CI sobre o LCOV mergeado de Vitest + cargo-llvm-cov (core + Tauri shell).
Abaixo de 80%? PR não merge.
⚙️ Como funciona
- 1. CI roda
pnpm test:coverage→ geraapp/coverage/lcov.info - 2. CI roda
cargo-llvm-covsobre core + Tauri → gera LCOVs adicionais - 3. Workflow merge dos LCOVs
- 4.
diff-covercompara contra o diff do PR - 5. Falha se <80% das linhas alteradas têm cobertura
✓ Estratégia que passa
- ✓Testar happy path e edge cases
- ✓Cobrir
match/ifcom branches - ✓Testar erros (
RpcOutcome::Err) - ✓Rodar coverage local antes de pushar
✗ Como falhar
- ✗Adicionar lógica nova sem teste
- ✗Testar só
render()e ignorar interações - ✗Marcar todo
elsecomo "raro" - ✗Esperar CI mostrar e só depois adicionar testes
💡 Dica prática
Antes de pushar, rode pnpm test:coverage e olhe o lcov-report/index.html. Você vê em vermelho exatamente quais linhas precisam de teste. Resolva ali, evite o vai-e-vem com o CI.
🎓 Resumo do Módulo
Próximo Módulo:
5.3 — Git, PRs e CI (fork model, branch, hooks, i18n parity)