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?



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:


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.


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.


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.


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.


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
 }

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.


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.


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


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.


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!


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.


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.



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.


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


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
Ao utilizar nosso site, você reconhece que leu e compreendeu nossa Política de Cookies e nossa Política de Privacidade.
Licensed under cc by-sa 3.0 with attribution required.