Pular para o conteúdo

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#

dataforge
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.

text
__snapshots__/relatorio_test.snap.json

Um 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#

bash
DF_ATUALIZAR_SNAPSHOT=1 dataforge crucible

Quando muda#

text
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.json

A 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#

dataforge
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#

dataforge
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:

text
{"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.

Onde continuar#