Scripts bash de teste de unidade

112

Temos um sistema que possui alguns scripts bash rodando além do código Java. Como estamos tentando testar tudo que pode possivelmente quebrar, e esses scripts bash podem falhar, queremos testá-los.

O problema é que é difícil testar scripts bash.

Existe uma maneira ou uma prática recomendada para testar scripts bash? Ou devemos parar de usar scripts bash e procurar soluções alternativas testáveis?

nimcap
fonte
veja também: stackoverflow.com/questions/1315624/…
Chen Levy
possível duplicata de teste
usuário
Visão geral das ferramentas existentes: medium.com/wemake-services/…
sobolevn

Respostas:

48

Na verdade, existe um shunit2 , uma estrutura de teste de unidade baseada em xUnit para scripts de shell baseados em Bourne. Eu não usei sozinho, mas pode valer a pena conferir.

Perguntas semelhantes já foram feitas antes:

ire_and_curses
fonte
3
Posso afirmar (trocadilho intencional) que o shunit2 (versão 2.1.6) está um pouco quebrado até hoje. O assertNull e assertNotNull não funcionam, mesmo que você os alimente com valores diretos. assertEquals funciona bem, mas acho que terei que fazer o meu próprio agora.
labirinto de
@labyrinth, tem certeza de que o problema não era o seguinte: github.com/kward/shunit2/issues/53 "Como usar assertNull corretamente?"?
Victor Sergienko
1
@Victor É definitivamente possível que eu não tenha sido cuidadoso o suficiente com minhas aspas duplas. Logo estou voltando para uma função em que o shunit2 ou algum sistema de teste de unidade bash será muito útil. Vou tentar novamente.
labirinto de
5
Eu sou um usuário e às vezes contribuidor para shunit2, e posso confirmar que o projeto está vivo e bem em 2019.
Alex Harvey
31

Recebi a seguinte resposta de um grupo de discussão:

é possível importar (incluir, qualquer coisa) um procedimento (função, qualquer que seja o nome) de um arquivo externo. Esta é a chave para escrever um script de teste: você divide seu script em procedimentos independentes que podem ser importados tanto para o script em execução quanto para o script de teste, e então você tem o script em execução o mais simples possível.

Este método é como injeção de dependência para scripts e parece razoável. É preferível evitar scripts bash e usar uma linguagem mais testável e menos obscura.

nimcap
fonte
4
Não tenho certeza se devo votar a favor ou contra, por um lado, dividir em partes menores é bom, mas por outro lado, eu preciso de uma estrutura, não de um conjunto de scripts personalizados
mpapis
10
Embora não haja nada de errado com o bash (escrevi muitos, muitos scripts), é uma linguagem difícil de dominar. Minha regra é: se um script é grande o suficiente para precisar de testes, você provavelmente deve passar para uma linguagem de script que seja facilmente testada.
Doug
1
Mas às vezes você precisa ter algo que possa ser originado no shell de um usuário. Não está claro para mim como você faria isso sem recorrer a um script de shell
Itkovian
@Itkovian - você poderia, por exemplo, usar o npm para exportar um executável para o caminho, então nenhuma fonte é necessária (seu pacote npm terá que ser instalado globalmente)
Eliran Malka
1
Vou seguir o conselho sobre não usar o bash. :)
Maciej Wawrzyńczuk
30

Teste Bash compatível com TAP : Sistema de teste automatizado Bash

TAP, o Test Anything Protocol, é uma interface simples baseada em texto entre módulos de teste em um equipamento de teste. O TAP começou como parte do equipamento de teste para Perl, mas agora tem implementações em C, C ++, Python, PHP, Perl, Java, JavaScript e outros.

Janus Troelsen
fonte
14
Vale a pena divulgar o que é TAP e por que devemos nos importar, caso contrário, é apenas copiar e colar sem sentido
om-nom-nom
@ om-nom-nom: Liguei-o ao site da TAP agora.
Janus Troelsen
7
Já que ninguém mais dizia o indizível: TAP = Test Anything Protocol
JW.
9

Nikita Sobolev escreveu uma excelente postagem no blog comparando algumas estruturas de teste bash diferentes: Testando aplicativos Bash

Para os impacientes: a conclusão de Nikita foi usar Bats, mas parece que Nikita perdeu o projeto Bats-core , que me parece ser aquele a usar daqui para frente, já que o projeto Bats original não foi mantido ativamente desde 2013.

cb2
fonte
7

Epoxy é uma estrutura de teste Bash que projetei principalmente para testar outro software, mas também o uso para testar módulos bash, incluindo ele próprio e o Carton .

As principais vantagens são a sobrecarga de codificação relativamente baixa, aninhamento de asserções ilimitado e seleção flexível de asserções a serem verificadas.

Fiz uma apresentação comparando-o ao BeakerLib - um framework usado por alguns na Red Hat.

spbnick
fonte
6

Por que você diz que é "difícil" testar scripts bash?

O que há de errado com wrappers de teste como:

 #!/bin/bash
 set -e
 errors=0
 results=$($script_under_test $args<<ENDTSTDATA
 # inputs
 # go
 # here
 #
 ENDTSTDATA
 )
 [ "$?" -ne 0 ] || {
     echo "Test returned error code $?" 2>&1
     let errors+=1
     }

 echo "$results" | grep -q $expected1 || {
      echo "Test Failed.  Expected $expected1"
      let errors+=1
 }
 # and so on, et cetera, ad infinitum, ad nauseum
 [ "$errors" -gt 0 ] && {
      echo "There were $errors errors found"
      exit 1
 }
Jim Dennis
fonte
4
Primeiro, os scripts bash não são muito legíveis. Em segundo lugar, as expectativas são complicadas, como verificar se um arquivo de bloqueio foi criado com o PID do script bash que o criou.
nimcap
10
Mais importante, é difícil testar scripts de shell porque eles geralmente têm um grande número de efeitos colaterais e utilizam recursos do sistema, como sistema de arquivos, rede, etc. Idealmente, os testes de unidade não têm efeitos colaterais e não dependem dos recursos do sistema.
Jayhendren
4

Eu criei shellspec porque queria uma ferramenta útil e fácil de usar.

Escrito por puro script de shell POSIX. Ele foi testado com muitos shells mais do que shunit2. Possui recursos poderosos do que morcegos / núcleo de morcegos.

Por exemplo, suporte a bloco aninhado, fácil de simular / stub, fácil de pular / pendente, testes parametrizados, número de linha de asserção, execução por número de linha, execução paralela, execução aleatória, formatador TAP / JUnit, cobertura e integração de CI, criador de perfil e etc .

Veja a demonstração na página do projeto.

Koichi Nakashima
fonte
3

Eu gosto bastante do shell2junit , um utilitário para gerar saída do tipo JUnit de testes de script Bash. Isso é útil porque o relatório gerado pode ser lido por sistemas de integração contínua, como os plug-ins JUnit para Jenkins e Bamboo.

Embora o shell2junit não forneça a estrutura de script Bash abrangente como o shunit2 , ele permite que você tenha bons relatórios dos resultados do teste.

Steve HHH
fonte
3

Tente bashtest . É uma maneira simples de testar seus scripts. Por exemplo, você tem do-some-work.shque alterar alguns arquivos de configuração. Por exemplo, adicione uma nova linha PASSWORD = 'XXXXX'ao arquivo de configuração/etc/my.cfg .

Você escreve os comandos do bash linha por linha e verifica a saída.

Instalar:

pip3 install bashtest

Criar testes é apenas escrever comandos bash.

Arquivo test-do-some-work.bashtest:

# run the script  
$ ./do-some-work.sh > /dev/null

# testing that the line "PASSWORD = 'XXXXX'" is in the file /etc/my.cfg   
$ grep -Fxq "PASSWORD = 'XXXXX'" /etc/my.cfg && echo "YES"
YES

Execute testes:

bashtest *.bashtest

Você pode encontrar alguns exemplos aqui e aqui

Pahaz
fonte
3

Talvez isso possa ser usado ou contribuído para

https://thorsteinssonh.github.io/bash_test_tools/

Destina-se a escrever resultados em protocolo TAP que imagino ser bom para CI e bom para aqueles que desejam ambientes shell. Imagino que algumas coisas sejam executadas em ambientes de shell, portanto, alguns podem argumentar que devem ser testados em seu ambiente de shell.

Hrob
fonte
3

Experimente assert.sh

source "./assert.sh"

local expected actual
expected="Hello"
actual="World!"
assert_eq "$expected" "$actual" "not equivalent!"
# => x Hello == World :: not equivalent!

Espero que ajude!

Marca
fonte
3

Não acredito que ninguém falou sobre OSHT ! É compatível com ambos TAP e JUnit, é pura shell (isto é, há outros idiomas envolvidos), ele funciona autônomo também, e é simples e direta.

O teste se parece com isto (fragmentos retirados da página do projeto):

#!/bin/bash
. osht.sh

# Optionally, indicate number of tests to safeguard against abnormal exits
PLAN 13

# Comparing stuff
IS $(whoami) != root
var="foobar"
IS "$var" =~ foo
ISNT "$var" == foo

# test(1)-based tests
OK -f /etc/passwd
NOK -w /etc/passwd

# Running stuff
# Check exit code
RUNS true
NRUNS false

# Check stdio/stdout/stderr
RUNS echo -e 'foo\nbar\nbaz'
GREP bar
OGREP bar
NEGREP . # verify empty

# diff output
DIFF <<EOF
foo
bar
baz
EOF

# TODO and SKIP
TODO RUNS false
SKIP test $(uname -s) == Darwin

Uma corrida simples:

$ bash test.sh
1..13
ok 1 - IS $(whoami) != root
ok 2 - IS "$var" =~ foo
ok 3 - ISNT "$var" == foo
ok 4 - OK -f /etc/passwd
ok 5 - NOK -w /etc/passwd
ok 6 - RUNS true
ok 7 - NRUNS false
ok 8 - RUNS echo -e 'foo\nbar\nbaz'
ok 9 - GREP bar
ok 10 - OGREP bar
ok 11 - NEGREP . # verify empty
ok 12 - DIFF <<EOF
not ok 13 - TODO RUNS false # TODO Test Know to fail

O último teste mostra "não está ok", mas o código de saída é 0 porque é um TODO. Pode-se definir verbose também:

$ OSHT_VERBOSE=1 bash test.sh # Or -v
1..13
# dcsobral \!= root
ok 1 - IS $(whoami) != root
# foobar =\~ foo
ok 2 - IS "$var" =~ foo
# \! foobar == foo
ok 3 - ISNT "$var" == foo
# test -f /etc/passwd
ok 4 - OK -f /etc/passwd
# test \! -w /etc/passwd
ok 5 - NOK -w /etc/passwd
# RUNNING: true
# STATUS: 0
# STDIO <<EOM
# EOM
ok 6 - RUNS true
# RUNNING: false
# STATUS: 1
# STDIO <<EOM
# EOM
ok 7 - NRUNS false
# RUNNING: echo -e foo\\nbar\\nbaz
# STATUS: 0
# STDIO <<EOM
# foo
# bar
# baz
# EOM
ok 8 - RUNS echo -e 'foo\nbar\nbaz'
# grep -q bar
ok 9 - GREP bar
# grep -q bar
ok 10 - OGREP bar
# \! grep -q .
ok 11 - NEGREP . # verify empty
ok 12 - DIFF <<EOF
# RUNNING: false
# STATUS: 1
# STDIO <<EOM
# EOM
not ok 13 - TODO RUNS false # TODO Test Know to fail

Renomeie-o para usar uma .textensão e coloque-o em um tsubdiretório, e você pode usar prove(1)(parte do Perl) para executá-lo:

$ prove
t/test.t .. ok
All tests successful.
Files=1, Tests=13,  0 wallclock secs ( 0.03 usr  0.01 sys +  0.11 cusr  0.16 csys =  0.31 CPU)
Result: PASS

Defina OSHT_JUNITou passe -jpara produzir saída JUnit. JUnit também pode ser combinado com prove(1).

Eu usei essa biblioteca tanto funções de teste, fornecendo seus arquivos e, em seguida, executando assertions com IS/ OKe seus negativos, quanto scripts usando RUN/ NRUN. Para mim, essa estrutura fornece mais ganho com o mínimo de sobrecarga.

Daniel C. Sobral
fonte
1

Tentei muitas das soluções apresentadas aqui, mas achei a maioria delas muito volumosas e difíceis de usar, então criei minha própria pequena estrutura de teste: https://github.com/meonlol/t-bash

É apenas um arquivo no repo que você pode simplesmente executar diretamente, com um conjunto básico de declarações de estilo JUnit.

Usei-o profissionalmente em vários projetos internos e fui capaz de tornar nossos scripts bash superestáveis ​​e resistentes a regressões.

Leondepeon
fonte
0

Dê uma olhada no Outthentic , é simples, extensível por várias linguagens (Perl, Python, Ruby, Bash na escolha) e plataforma cruzada (Linux, Windows) para testar qualquer aplicação de linha de comando.

Alexey Melezhik
fonte
-2

Achei difícil justificar o uso do bash para scripts maiores quando o Python tem grandes vantagens:

  • Try / Except permite escrever scripts mais robustos com a capacidade de desfazer alterações em caso de erro.
  • Você não precisa usar uma sintaxe obscura como ' if [ x"$foo" = x"$bar"]; then ...', que está sujeita a erros.
  • Análise fácil de opções e argumentos usando o getoptmódulo (e há um módulo ainda mais fácil para análise de argumentos, mas o nome me escapa).
  • Python permite que você trabalhe com listas / dicts e objetos em vez de strings e arrays básicos.
  • Acesso a ferramentas de linguagem adequadas, como regex, bancos de dados (claro, você pode canalizar tudo para o mysqlcomando no bash, mas não é a maneira mais agradável de escrever código).
  • Não precisa se preocupar em usar a forma correta de $*ou "$*"ou "$@"ou $1ou "$1", os espaços nos nomes dos arquivos não são um problema, etc, etc, etc.

Agora eu uso o bash apenas para os scripts mais simples.

muito php
fonte
3
Não negando o fato de que Python tem vantagens, mas seu segundo ponto não é muito bem colocado. A mesma comparação poderia ter sido feita como if [[ $foo = $bar ]]; then .... Isso ainda não é melhor do que o que o python tem a oferecer, mas é melhor do que o que você apresentou.
Shrikant Sharat
8
Alguns sistemas (embutidos, por exemplo) não têm python disponível e você não pode / não deseja instalar coisas extras.
Rui Marques,
2
Eu pessoalmente adoro o bash, mas concordo que pode ser um pouco irritado. Normalmente, você precisa ser muito mais pró-ativo, enquanto no Python você pode corrigir os erros depois que eles aparecem. No entanto, o bash tem trap(para limpar / desfazer em caso de erro), bem como regex (ou seja [[ $1 =~ ^[1-3]{3}$ ]]). Tenho certeza de que a sintaxe obscura que você usou é uma referência a implementações antigas do test, não ao bash. Bash é ótimo para fazer interface com ferramentas de linha de comando existentes ... Freqüentemente, um único pipe para awkou grepé muito mais fácil do que a alternativa Python.
Seis de
1
BTW, o módulo analisador ao qual você estava se referindo é provável optparseou seu sucessor argparse. Nunca vi ninguém usar o getoptmódulo, nem o usei pessoalmente. A getoptutilidade é ótima. A análise de argumentos a partir do shell não é um problema, uma vez que você tenha um bom padrão. A menos que você esteja tentando implementar subcomandos no estilo git ou algo assim, não é muito problema.
Seis de
Python não será executado em todos os lugares onde o bash pode chegar. Digo isso porque testamos bash versus python, a mesma lógica de código e solicitamos que ambos fizessem algo. Bash entrou em todos os diretórios que tinha acesso. Por outro lado, o python não conseguia lidar com algumas permissões de diretórios e arquivos e também com diretórios que aumentavam e diminuíam muito rapidamente.
vianna77 de