Pular para o conteúdo

Testar uma ferramenta de terminal

Sem subprocesso onde não precisa, e com subprocesso onde só ele prova.

Uma CLI tem duas metades, e elas se testam de formas diferentes: a leitura dos argumentos é função pura, e a execução é um processo com código de saída, stdout e stderr.

A metade pura: `ler`, nunca `rodar`#

dataforge
adopt Arcane.Cli as Cli
adopt Arcane.Crucible as Crucible

action montar():
    cmd := Cli.comando("relatorio", "")
    cmd.posicional("entrada", "")
    cmd.opcao("limite", "inteiro", "n", 10, "")
    yield cmd

crucible "os argumentos":

    trial "o padrao vale quando a flag nao vem":
        args := montar().ler(["a.csv"])
        expect args["limite"] is 10

    trial "a forma curta e a longa sao a mesma coisa":
        expect montar().ler(["a.csv", "-n", "5"])["limite"] is 5
        expect montar().ler(["a.csv", "--limite=5"])["limite"] is 5

    trial "o inteiro chega como INTEIRO":
        expect montar().ler(["a.csv", "-n", "7"])["limite"] is 7

Crucible.run()

A metade impura: o processo de verdade#

O que só o subprocesso prova é o que acontece na fronteira: o código de saída, o que foi para cada canal, e o que o shell vê.

dataforge
adopt Arcane.Process as P

// O próprio interpretador serve de cobaia: ele é uma CLI.
r := P.run(["python3", "-m", "dataforge", "--versao"])
assert r["exit_code"] is 0 and r["ok"] is yes
assert len(r["stdout"]) > 0
out r["stdout"]

// 'capture' e o atalho para quando so o stdout importa
assert len(P.capture(["python3", "-m", "dataforge", "--versao"])) > 0
dataforge
adopt Arcane.Process as P

// Um comando que não existe: o código de saída é o que prova.
r := P.run(["python3", "-m", "dataforge", "nao-existe-esse-comando"])
assert r["failed"] is yes
out $"saiu com {r['exit_code']}"

A entrada canalizada#

dataforge
adopt Arcane.Process as P
adopt Arcane.IO as IO
adopt Arcane.OS as OS

pasta := $"{OS.temp_dir()}/df-cli-t-{randint(100000, 999999)}"
IO.mkdir(pasta)
defer:
    IO.remove_tree(pasta)

IO.write($"{pasta}/soma.df", """persist yes:
    linha := input()
    given linha is void:
        halt
    out int(linha) * 2
""")

r := P.run(["python3", "-m", "dataforge", "run", $"{pasta}/soma.df"],
           no, void, void, void, "5\n7\n")
assert r["exit_code"] is 0
assert "10" in r["stdout"] and "14" in r["stdout"]
out r["stdout"]

O que testar, em ordem de retorno#

  • O código de saída em cada caminho — é o que um && no shell lê, e o que um CI usa para reprovar.
  • A ajuda cita cada opção — um teste de três linhas que impede a ajuda de envelhecer.
  • A mensagem de erro de uso — ela é lida por quem está travado, e é a única documentação que essa pessoa vai ler.
  • A saída canalizada não tem cor — o | grep de alguém depende disso.
  • O temporário some — rode duas vezes e confira que nada ficou para trás.