Prueba unitaria de scripts de bash

112

Tenemos un sistema que tiene algunos scripts bash ejecutándose además del código Java. Dado que estamos tratando de probar todo lo que posiblemente pueda romperse, y esos scripts de bash pueden romperse, queremos probarlos.

El problema es que es difícil probar los scripts bash.

¿Existe una forma o una mejor práctica para probar scripts de bash? ¿O deberíamos dejar de usar scripts bash y buscar soluciones alternativas que sean probables?

nimcap
fuente
ver también: stackoverflow.com/questions/1315624/…
Chen Levy
posible duplicado de la prueba unitaria para scripts de shell
usuario
Descripción general de las herramientas existentes: medium.com/wemake-services/…
sobolevn

Respuestas:

48

En realidad , existe un shunit2 , un marco de prueba unitario basado en xUnit para scripts de shell basados ​​en Bourne. No lo he usado yo mismo, pero valdría la pena echarle un vistazo.

Se han hecho preguntas similares antes:

ire_and_curses
fuente
3
Puedo afirmar (juego de palabras) que shunit2 (versión 2.1.6) está un poco roto hasta la fecha. AsertNull y assertNotNull no funcionan, incluso si les da valores directos. asertEquals funciona bien, pero creo que voy a tener que lanzar el mío por ahora.
laberinto
@labyrinth, ¿estás seguro de que el problema no fue un caso de esto: github.com/kward/shunit2/issues/53 "¿Cómo usar assertNull correctamente?"
Victor Sergienko
1
@Victor Es definitivamente posible que no haya sido lo suficientemente cuidadoso con mis comillas dobles. Pronto volveré a un rol en el que shunit2 o algún sistema de prueba de unidades bash será muy útil. Lo intentaré de nuevo.
laberinto
5
Soy usuario y, a veces, colaborador de shunit2, y puedo confirmar que el proyecto está vivo y bien en 2019.
Alex Harvey
31

Obtuve la siguiente respuesta de un grupo de discusión:

es posible importar (incluir, lo que sea) un procedimiento (función, como se llame) desde un archivo externo. Esa es la clave para escribir un script de prueba: divide su script en procedimientos independientes que luego pueden importarse tanto en su script en ejecución como en su script de prueba, y luego tiene su script en ejecución lo más simple posible.

Este método es como una inyección de dependencia para scripts y suena razonable. Es preferible evitar los scripts bash y utilizar un lenguaje más comprobable y menos oscuro.

nimcap
fuente
4
No estoy seguro de si debería votar hacia arriba o hacia abajo, por un lado, dividir en partes más pequeñas es bueno, pero por otro lado, necesito un marco, no un conjunto de scripts personalizados
mpapis
10
Si bien bash no tiene nada de malo (escribí muchos, muchos scripts), es un lenguaje difícil de dominar. Mi regla general es que si un script es lo suficientemente grande como para necesitar pruebas, probablemente debería pasar a un lenguaje de script que se pueda probar fácilmente.
Doug
1
Pero a veces es necesario tener algo que se pueda obtener en el shell de un usuario. No tengo claro cómo lo haría sin recurrir a un script de shell
Itkovian
@Itkovian: podría, por ejemplo, usar npm para exportar un ejecutable a la ruta, por lo que no es necesario el suministro (su paquete npm tendrá que instalarse globalmente)
Eliran Malka
1
Voy a seguir los consejos sobre no usar bash. :)
Maciej Wawrzyńczuk
30

Prueba Bash compatible con TAP : Sistema de prueba automatizado Bash

TAP, el protocolo Test Anything, es una interfaz simple basada en texto entre módulos de prueba en un arnés de prueba. TAP comenzó su vida como parte del arnés de prueba para Perl, pero ahora tiene implementaciones en C, C ++, Python, PHP, Perl, Java, JavaScript y otros.

Janus Troelsen
fuente
14
Vale la pena revelar lo TAP es y por qué debería uno cuidado, de lo contrario es simplemente sentido copiar y pegar
om-nom-nom
@ om-nom-nom: Lo vinculé al sitio TAP ahora.
Janus Troelsen
7
Dado que nadie más decía lo indecible: TAP = Test Anything Protocol
JW.
9

Nikita Sobolev escribió una excelente publicación de blog comparando algunos marcos de prueba de bash diferentes: Prueba de aplicaciones Bash

Para los impacientes: la conclusión de Nikita fue usar Bats, pero parece que Nikita se perdió el proyecto Bats-core , que me parece que es el que debe usar en el futuro, ya que el proyecto Bats original no se ha mantenido activamente desde 2013.

cb2
fuente
7

Epoxy es un marco de prueba Bash que diseñé principalmente para probar otro software, pero también lo uso para probar módulos bash, incluidos él mismo y Carton .

Las principales ventajas son una sobrecarga de codificación relativamente baja, un anidamiento de aserciones ilimitado y una selección flexible de aserciones para verificar.

Hice una presentación comparándolo con BeakerLib , un marco utilizado por algunos en Red Hat.

spbnick
fuente
6

¿Por qué dice que es "difícil" probar los scripts de bash?

¿Qué pasa con los envoltorios de prueba 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
fuente
4
Primero, los scripts de bash no son muy legibles. En segundo lugar, las expectativas son complicadas, como comprobar si se crea un archivo de bloqueo con el PID del script bash que lo creó.
nimcap
10
Más importante aún, es difícil probar scripts de shell porque generalmente tienen una gran cantidad de efectos secundarios y utilizan recursos del sistema como sistema de archivos, red, etc. Idealmente, las pruebas unitarias no tienen efectos secundarios y no dependen de los recursos del sistema.
jayhendren
4

He creado shellspec porque quería una herramienta fácil de usar y útil.

Está escrito por puro script de shell POSIX. Ha probado con muchos proyectiles más que shunit2. Tiene características poderosas que el núcleo de murciélagos / murciélagos.

Por ejemplo, admite bloques anidados, fáciles de simular / stub, fáciles de omitir / pendientes, pruebas parametrizadas, número de línea de afirmación, ejecución por número de línea, ejecución paralela, ejecución aleatoria, formateador TAP / JUnit, integración de cobertura e CI, perfilador, etc. .

Vea la demostración en la página del proyecto.

Koichi Nakashima
fuente
3

Me gusta bastante shell2junit , una utilidad para generar resultados similares a JUnit a partir de pruebas de script Bash. Esto es útil porque el informe generado se puede leer mediante sistemas de integración continua, como los complementos JUnit para Jenkins y Bamboo.

Si bien shell2junit no proporciona el marco de scripting Bash completo como shunit2 , le permite tener buenos informes de los resultados de la prueba.

Steve HHH
fuente
3

Prueba bashtest . Es una forma sencilla de probar sus scripts. Por ejemplo, tienes do-some-work.shque cambiar algunos archivos de configuración. Por ejemplo, agregue una nueva línea PASSWORD = 'XXXXX'al archivo de configuración/etc/my.cfg .

Escribe los comandos de bash línea por línea y luego verifica la salida.

Instalar en pc:

pip3 install bashtest

Crear pruebas es simplemente escribir comandos bash.

Archivo 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

Ejecutar pruebas:

bashtest *.bashtest

Puedes encontrar algunos ejemplos aquí y aquí

pahaz
fuente
3

Tal vez esto se pueda utilizar o contribuir a

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

Destinado a escribir resultados en el protocolo TAP que imagino que es bueno para CI y bueno para aquellos que quieren entornos de shell. Imagino que algunas cosas se ejecutan en entornos de shell, por lo que algunos podrían argumentar que deberían probarse en su entorno de shell.

hrob
fuente
3

Dale una oportunidad a assert.sh

source "./assert.sh"

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

¡Espero eso ayude!

marca
fuente
3

¡No puedo creer que nadie hablara de OSHT ! Es compatible tanto con TAP como con JUnit, es shell puro (es decir, sin otros lenguajes involucrados), también funciona de forma independiente, y es simple y directo.

La prueba se ve así (fragmentos tomados de la página del proyecto):

#!/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

Una carrera simple:

$ 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

La última prueba muestra "no está bien", pero el código de salida es 0 porque es un TODO. También se puede establecer detallado:

$ 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

Cambie el nombre para usar una .textensión y colóquelo en un tsubdirectorio, y puede usar prove(1)(parte de Perl) para ejecutarlo:

$ 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

Establecer OSHT_JUNITo pasar -jpara producir una salida JUnit. JUnit también se puede combinar con prove(1).

He usado esta biblioteca para probar funciones obteniendo sus archivos y luego ejecutando afirmaciones con IS/ OKy sus negativos, y scripts usando RUN/ NRUN. Para mí, este marco proporciona la mayor ganancia con la menor cantidad de gastos generales.

Daniel C. Sobral
fuente
1

Probé muchas de las soluciones presentadas aquí, pero encontré que la mayoría de ellas eran voluminosas y difíciles de usar, así que construí mi propio pequeño marco de prueba: https://github.com/meonlol/t-bash

Es solo un archivo en el repositorio que simplemente puede ejecutar directamente, con un conjunto básico de afirmaciones de estilo JUnit.

Lo he usado profesionalmente en varios proyectos internos y pude hacer que nuestros scripts de bash sean súper estables y resistentes a la regresión.

leondepeon
fuente
0

Eche un vistazo a Outthentic , es simple, extensible en muchos lenguajes (Perl, Python, Ruby, Bash a elección) y marco multiplataforma (Linux, Windows) para probar cualquier aplicación de línea de comandos.

Alexey Melezhik
fuente
-2

Me ha resultado difícil justificar el uso de bash para scripts más grandes cuando Python tiene ventajas tan enormes:

  • Try / Except permite escribir scripts más robustos con la capacidad de deshacer cambios en caso de error.
  • No es necesario utilizar una sintaxis oscura como " if [ x"$foo" = x"$bar"]; then ...", que es propensa a errores.
  • Fácil análisis de opciones y argumentos usando el getoptmódulo (y hay un módulo aún más fácil para analizar argumentos, pero el nombre se me escapa).
  • Python le permite trabajar con listas / dictados y objetos en lugar de cadenas y matrices básicas.
  • Acceso a herramientas de lenguaje adecuadas, como expresiones regulares, bases de datos (seguro que puede canalizar todo al mysqlcomando en bash, pero no es la mejor forma de escribir código).
  • No hay necesidad de preocuparse sobre el uso de la forma correcta $*o "$*"o "$@"o $1o "$1", espacios en los nombres de archivo no es un problema, etc, etc, etc.

Ahora solo uso bash para los scripts más simples.

demasiado php
fuente
3
Sin negar el hecho de que Python tiene ventajas, pero su segundo punto no está muy bien planteado. Se podría haber hecho la misma comparación que if [[ $foo = $bar ]]; then .... Esto todavía no es mejor que lo que Python tiene para ofrecer, pero es mejor que lo que presentó.
Shrikant Sharat
8
Algunos sistemas (embebidos, por ejemplo) no tienen Python disponible y no puedes / no quieres instalar cosas adicionales.
Rui Marques
2
Personalmente, me encanta bash, pero estoy de acuerdo en que puede ser un poco irritante. Por lo general, debe ser mucho más proactivo, mientras que en Python puede abordar los errores después de que aparezcan. Sin embargo, bash tiene trap(para limpiar / deshacer en caso de error) así como regex (es decir [[ $1 =~ ^[1-3]{3}$ ]]). Estoy bastante seguro de que la sintaxis oscura que usó se refiere a implementaciones antiguas de test, no a bash. Bash es excelente para interactuar con herramientas de línea de comandos existentes ... Con frecuencia, una sola tubería awko grepes mucho más fácil que la alternativa de Python.
seis
1
Por cierto, el módulo analizador al que se refería es probable optparseo su sucesor argparse. Nunca he visto a nadie usar el getoptmódulo, ni lo he usado personalmente. La getoptutilidad es bueno, no obstante. El análisis de argumentos desde el shell no es un problema en absoluto una vez que tenga un buen patrón. A menos que esté intentando implementar subcomandos de estilo git o algo así, no hay muchos problemas.
6 de
Python no se ejecutará en todos los lugares donde bash pueda llegar. Digo eso porque probamos bash versus python, la misma lógica de código, y les pedimos a ambos que hicieran algo. Bash entró en todos los directorios a los que tenía acceso. Por otro lado, Python no podía manejar algunos permisos de directorios y archivos y también directorios que estaban aumentando y disminuyendo muy rápidamente.
vianna77