Instantâneos e isolamento
Três ferramentas do Crucible para resultado grande, estado que sobra entre testes e falha que vai e volta.
O que um teste comum não alcança bem.
Instantâneo#
adopt Crucible
crucible "o relatorio":
trial "nao muda sem aviso":
Crucible.snapshot("relatorio_mensal", gerar_relatorio())
trial "e o HTML da pagina tambem":
Crucible.snapshot("pagina_inicial", V.html_da_pagina())Para o que é grande demais para escrever à mão no teste: o HTML de uma página, o relatório de trinta linhas, o JSON de uma rota. Escrever o esperado à mão para isso dá um teste que ninguém mantém — e um teste que ninguém mantém vira um teste que alguém comenta.
Na primeira vez ele grava e passa#
É o único jeito de começar, e por isso o arquivo vai no controle de versão: é no diff do commit que alguém confere se o novo esperado está certo.
__snapshots__/relatorio_test.snap.jsonUm JSON por arquivo de teste, ao lado dele — assim andam junto num git mv, e o diff mostra os dois lado a lado. As chaves saem ordenadas: um vault que muda de ordem de inserção faria o instantâneo falhar sem nada ter mudado de verdade.
Aceitar uma mudança intencional#
DF_ATUALIZAR_SNAPSHOT=1 dataforge crucibleQuando muda#
o instantaneo 'relatorio_mensal' mudou.
@@ -3,7 +3,7 @@
"clientes": 1240,
- "receita": 84200.0,
+ "receita": 91800.0,
"ticket": 67.9,
para aceitar: DF_ATUALIZAR_SNAPSHOT=1 dataforge crucible
o arquivo: __snapshots__/relatorio_test.snap.jsonA mensagem traz o diff, e não os dois textos inteiros: trezentas linhas lado a lado num terminal são ilegíveis, e ter trezentas linhas é justamente o motivo de usar instantâneo.
Banco que se desfaz#
crucible "cadastro de livros":
Crucible.before(lambda suite: Crucible.banco(db))
trial "grava um livro":
Banco.insert(db, "livros", {"titulo": "Duna", "preco": 79.9})
Crucible.expect(Banco.count(db, "livros")).to_be(1)
trial "e o seguinte nao ve o que ele gravou":
Crucible.expect(Banco.count(db, "livros")).to_be(0)O problema: um teste que grava deixa a linha lá, e o teste seguinte a encontra. A suíte passa na ordem em que foi escrita e falha em qualquer outra — e --aleatorio expõe isso de um jeito que parece intermitente.
Crucible.banco(db) abre uma transação e a desfaz no fim do trial, sempre. Apagar tudo entre testes seria a alternativa, e é mais lenta e mais frágil: ela precisa saber a ordem das chaves estrangeiras.
Teste instável#
crucible "integracao":
trial "consulta a API externa":
r := Crucible.flaky(lambda: Http.get(URL).json(), 3, 0.5)
Crucible.expect(r["ok"]).to_be(yes)Existe para o que depende de rede, de relógio ou de escalonamento — e não para esconder um bug. Por isso ele devolve o número de tentativas:
{"ok": yes, "tentativas": 3}Um teste que precisa de três tentativas toda vez não é instável, está quebrado, e o número é o que denuncia isso. Se ele aparece como 3 no seu relatório, o problema não é a rede.