2009-08-22 17 views
33

Cuando escribo más que un guión trivial en bash, a menudo me pregunto cómo hacer que el código sea comprobable.Bash and Test-Driven Development

Normalmente es difícil escribir pruebas para el código bash, debido a que tiene pocas funciones que toman un valor y devuelven un valor, y altas funciones que verifican y configuran algún aspecto en el entorno, modifican el sistema de archivos, invocar un programa, etc. - funciones que dependen del entorno o que tienen efectos secundarios. Por lo tanto, el código de configuración y prueba se vuelve mucho más complicado que el código que prueban.


Por ejemplo, considere una simple función a prueba:

function add_to_file() { 
    local f=$1 
    cat >> $f 
    sort -u $f -o $f 
} 

Código de ensayo para esta función podría consistir en:

add_to_file.before:

foo 
bar 
baz 

add_to_file. después de:

código

y de prueba:

function test_add_to_file() { 
    cp add_to_file.{before,tmp} 
    add_to_file add_to_file.tmp 
    cmp add_to_file.{tmp,after} && echo pass || echo fail 
    rm add_to_file.tmp 
} 

Aquí 5 líneas de código son probados por 6 líneas de código de prueba y 7 líneas de datos.


Ahora consideremos un caso un poco más complicado:

function distribute() { 
    local file=$1 ; shift 
    local hosts=("[email protected]") 
    for host in "${hosts[@]}" ; do 
    rsync -ae ssh $file $host:$file 
    done 
} 

ni siquiera puedo decir cómo empezar a escribir una prueba para que ...


Entonces, ¿hay una buena manera de hacer TDD en scripts bash, o debería darme por vencido y poner mis esfuerzos en otra parte?

+0

vea también: http://stackoverflow.com/questions/1339416/testing-bash-scripts –

+0

Vea también: http://stackoverflow.com/questions/971945/unit-testing-for-shell-scripts – blong

+1

de las herramientas existentes: https://medium.com/wemake-services/testing-bash-applications-85512e7fe2de – sobolevn

Respuesta

28

Así que aquí es lo que aprendí:

  1. Hay algunos testingframeworks escrito en bash y para la fiesta, sin embargo ...

  2. No es tanto que Bash no es adecuado para TDD (aunque me vienen a la mente algunos otros que se ajustan mejor), pero las tareas típicas de para las que se usa Bash (instalación, configuración del sistema ), son pruebas difíciles de escribir, y en particularmente difícil de configurar la prueba.

  3. El soporte deficiente estructura de datos en Bash hace que sea difícil separar la lógica de efecto secundario, y de hecho hay normalmente poco de lógica en los scripts Bash. Eso hace que sea difícil romper scripts en trozos verificables. Hay algunas funciones que se pueden probar, pero es la excepción, no la regla.

  4. La función es una buena cosa (tm), pero solo pueden ir tan lejos.

  5. Las funciones anidadas pueden ser incluso mejores, pero también son limitadas.

  6. Al final del día, con gran esfuerzo una cierta cobertura puede obtuvo, pero que pondrá a prueba la parte menos interesante del código, y mantendrá el grueso de la prueba como una buena (o mala) antiguo manual de de prueba.

Meta: decidí contestar (y aceptar) a mi propia pregunta, porque yo era incapaz de elegir entre Sinan Ünür's (votado hacia arriba) y mouviciel's (votado hacia arriba) en donde las respuestas que igualmente útil y perspicaz. Quiero señalar Stefano Borini's respuesta, que aunque no me impresionó inicialmente, aprendí a apreciarlo con el tiempo. También su design patterns or best practices for shell scripts answer (votado) referido anteriormente fue útil.

+0

Es una pena que su respuesta no se muestre más alta. – smonff

5

Si codifica un programa bash lo suficientemente grande como para requerir TDD, está utilizando un lenguaje incorrecto.

Le sugiero que lea mi publicación anterior sobre las mejores prácticas en la programación de bash, probablemente encontrará algo útil para hacer que su programa bash sea comprobable, pero mi afirmación anterior se mantiene.

Design patterns or best practices for shell scripts

+4

No. 1, Bash es un lenguaje capaz, puede ser la herramienta adecuada para muchos trabajos. 2. Incluso si esto es cierto, es posible que no esté en condiciones de elegir la herramienta. Esta respuesta simplemente evita la pregunta. Lo siento. –

+5

bash es la herramienta adecuada para muchos trabajos, pero no para todos. Si una tarea es claramente demasiado exigente para un script bash, está utilizando la herramienta incorrecta. En cuanto al punto 2, puedo estar de acuerdo con usted, pero tenga mucho cuidado con un gerente que lo obligue a usar una herramienta flagrantemente incorrecta para una tarea compleja. Si se estropea, será tu culpa. –

+0

Usted mismo escribió: "Tuve que usarlo debido a factores humanos y legado", no es "un gerente que me obliga a usar una herramienta abiertamente incorrecta". De todos modos, gracias por el enlace a http://stackoverflow.com/questions/78497/design-patterns-or-best-practices-for-shell-scripts/. –

12

Si está escribiendo código al mismo tiempo con las pruebas, tratar de hacerlo lo alto de funciones que no usan nada, además de sus parámetros y no modifican el medio ambiente. Es decir, si su función también podría ejecutarse en una subshell, entonces será fácil de probar. Toma algunos argumentos y produce algo para stdout, o para un archivo, o tal vez hace algo en el sistema, pero la persona que llama no siente efectos secundarios.

Sí, terminará con una gran cadena de funciones que pasan una variable WORKING_DIR que bien podría ser global, pero esto es un inconveniente menor comparado con la tarea de rastrear qué lee y modifica cada función. La habilitación de pruebas unitarias también es una bonificación gratuita.

Intente minimizar los casos en los que necesita resultados. Un pequeño abuso de subshell hará mucho por mantener las cosas bien separadas (a expensas del rendimiento).

En lugar de la estructura lineal, donde se llaman las funciones, configure un entorno, luego se llaman otras, todas prácticamente en un nivel, intente buscar un árbol de llamadas con datos mínimos que se remonten. Devolver cosas en bash es inconveniente si adoptas la abstinencia autoimpuesta de los vars globales ...

+7

Siento que un libro está en camino: "Programación funcional en POSIX sh". –

+1

¡Sí! ¡Necesitamos indicadores de función de primera clase! ¡O evaluaciones muy baratas! – Eugene

8

Desde el punto de vista de la implementación, sugiero shUnit.

Desde un punto de vista práctico, sugiero no darse por vencido. Utilizo TDD en scripts bash y confirmo que vale la pena el esfuerzo.

Por supuesto, obtengo el doble de líneas de prueba que de código, pero con scripts complejos, los esfuerzos en las pruebas son una buena inversión. Esto es cierto en particular cuando su cliente cambia de opinión al final del proyecto y modifica algunos requisitos. Tener un conjunto de pruebas de regresión es una gran ayuda para cambiar el código de bash complejo.

+0

He comenzado a usar shunit2 (https://code.google.com/p/shunit2), que es muy simple y parece funcionar sin problemas. –

+1

¿No sería más fácil usar un lenguaje de programación de nivel superior en lugar de bash? – inf3rno

+2

@ inf3rno - La pregunta es sobre bash. Es posible que los lenguajes de programación de nivel superior no estén disponibles (por ejemplo, un sistema integrado que utiliza el cuadro de ocupado). – mouviciel

3

Escribir lo que Meszaros llama a consumer tests es difícil en cualquier idioma. Otro enfoque es verificar el comportamiento de comandos como rsync manualmente, luego escribir pruebas unitarias para probar funcionalidades específicas sin tocar la red.En este ejemplo modificado ligeramente, $ run se utiliza para imprimir los efectos secundarios si se ejecuta el script con la palabra clave "prueba"

function distribute { 
    local file=$1 ; shift 
    for host in [email protected] ; do 
     $run rsync -ae ssh $file $host:$file 
    done 
} 

if [[ $1 == "test" ]]; then 
    run="echo" 
else 
    distribute schedule.txt $* 
    exit 0 
fi 

#  
# Built-in self-tests 
# 

output=$(mktemp) 
expected=$(mktemp) 
set -e 
trap "rm $got $expected" EXIT 

distribute schedule.txt login1 login2 > $output 
cat <<EOF> $expected 
rsync -ae ssh schedule.txt login1:schedule.txt 
rsync -ae ssh schedule.txt login2:schedule.txt 
EOF 
diff $output $expected 
echo -n '.' 

echo; echo "PASS" 
3

Es posible que desee echar un vistazo a pepino/Aruba. Hizo un buen trabajo para mí.

Además, puede stub casi todo lo que desee hacer algo como esto:

# 
# code.sh 
# 
some_function_calling_some_external_binary() 
{ 
    if ! external_binary action_1; then 
     # ... 
    fi 

    if ! external_binary action_2; then 
     # ... 
    fi 
} 

# 
# test.sh 
# 

# now for the test, simply stub your external binary: 
external_binary() 
{ 
    if [ "[email protected]" = "action_1" ]; then 
     # stub action_1 

    elif [ "[email protected]" = "action_2" ]; then 
     # stub action_2 

    else 
     external_binary [email protected] 
    fi 
} 
0

El advanced bash scripting guide tiene un ejemplo de una función de aserción, pero aquí es una función aserción simple y más flexible - sólo tiene que utilizar eval de $ * para probar cualquier condición.

assert() { 
    if ! eval $* ; then 
     echo 
     echo "===== Assertion failed: \"$*\" =====" 
     echo "File \"$0\", line:$LINENO line:${BASH_LINENO[*]}" 
     echo line:$(caller 0) 
     exit 99 
    fi 
} 

# e.g. USAGE: 
assert [[ $r == 42 ]] 
assert "((r==42))" 

BASH_LINENO y caller bash builtin son bash shell specific.

0

echa un vistazo a Outthentic framework - está diseñado para crear escenarios que ejecute cualquier código Bash y luego analizar el stdout usando DSL formal, es muy fácil construir cualquier conjunto de pruebas Tdd/blackbox sobre esta herramienta.