2009-08-22 9 views
33

Quando si scrive più di uno script banale in bash, mi chiedo spesso come rendere il codice verificabile.Sviluppo Bash e Test-driven

In genere è difficile scrivere test per codice bash, poiché è basso sulle funzioni che assumono un valore e restituiscono un valore e su funzioni che controllano e impostano alcuni aspetti nell'ambiente, modificano il valore file system, richiama un programma, ecc. - funzioni che dipendono dall'ambiente o hanno effetti collaterali. In questo modo il codice di setup e test diventa molto più complicato del codice che testano.


Ad esempio, si consideri una semplice funzione di prova:

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

Codice di prova per questa funzione potrebbe consistere in:

add_to_file.before:

foo 
bar 
baz 

add_to_file. dopo:

codice

E prova:

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 
} 

Qui 5 linee di codice sono testati da 6 righe di codice di prova e 7 linee di dati.


Consideriamo ora un caso un po 'più complicato:

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

Non posso nemmeno dire come avviare scrivere un test per questo ...


Quindi, c'è un buon modo per fare TDD in script bash, o dovrei rinunciare e mettere i miei sforzi altrove?

+0

vedere anche: http://stackoverflow.com/questions/1339416/testing-bash-scripts –

+0

Vedere anche: http://stackoverflow.com/questions/971945/unit-testing-for-shell-scripts – blong

+1

Panoramica degli strumenti esistenti: https://medium.com/wemake-services/testing-bash-applications-85512e7fe2de – sobolevn

risposta

28

: ecco ciò che ho imparato:

  1. Ci sono alcuni testingframeworks scritto in bash e per bash, però ...

  2. Non è tanto che Bash non è adatto per TDD (anche se alcuni sono gli che vengono in mente meglio), ma le attività tipiche di Bash (installazione, sistema configurazione), che sono difficili da scrivere, e in, sono le attività tipiche di .particolarmente difficile da configurare il test.

  3. Il supporto povero struttura dati in Bash rende difficile separare logica da effetto collaterale, e in effetti v'è tipicamente poco logica negli script Bash. Ciò rende difficile interrompere gli script nei blocchi verificabili . Ci sono alcune funzioni che possono essere testate, ma è l'eccezione, non la regola.

  4. Le funzioni sono una buona cosa (tm), ma possono solo andare così lontano.

  5. Le funzioni annidate possono essere anche migliori, ma sono anche limitate.

  6. Alla fine della giornata, con grande sforzo un po 'di copertura può essere ottenuto, ma metterà alla prova la parte meno interessante del codice, e manterrà la maggior parte del test come una buona (o cattiva) vecchio manuale test.

Meta: Ho deciso di rispondere (e accetta) la mia domanda, perché non ero in grado di scegliere tra Sinan Ünür's (votato in alto) e mouviciel's (votato su) risposte che dove ugualmente utile e penetranti. Voglio prendere nota della risposta Stefano Borini's, che sebbene non mi abbia colpito inizialmente, ho imparato ad apprezzarlo nel tempo. Anche il suo design patterns or best practices for shell scripts answer (votato in su) era utile.

+0

È un peccato che la tua risposta non venga visualizzata più in alto. – smonff

5

Se si codifica un programma bash abbastanza grande da richiedere TDD, si utilizza la lingua sbagliata.

Ti suggerisco di leggere il mio post precedente sulle best practice in programmazione bash, probabilmente troverai qualcosa di utile per rendere testabile il tuo programma bash, ma la mia affermazione sopra rimane.

Design patterns or best practices for shell scripts

+4

No. 1, Bash è un linguaggio capace, può essere lo strumento giusto per molti lavori. 2. Anche se questo è vero, potrei non essere nella posizione di scegliere lo strumento. Questa risposta evita semplicemente la domanda. Scusate. –

+5

bash è lo strumento giusto per molti lavori, ma non per tutti. Se un'attività è chiaramente troppo impegnativa per uno script bash, stai usando lo strumento sbagliato. Per quanto riguarda il punto 2, posso essere d'accordo con te, ma diffidare di un manager che ti obbliga a usare uno strumento palesemente sbagliato per un compito complesso. Se si rovina, sarà colpa tua. –

+0

Hai scritto tu stesso: "Ho dovuto usarlo a causa di fattori umani e legacy", non è "un gestore costringendo [me] a utilizzare uno strumento palesemente sbagliato". Comunque grazie per il link a http://stackoverflow.com/questions/78497/design-patterns-or-best-practices-for-shell-scripts/. –

12

Se si scrive il codice, allo stesso tempo con i test, cercare di rendere alto sulle funzioni che non usano nulla oltre i loro parametri e non modificano l'ambiente. Cioè, se la tua funzione potrebbe anche essere eseguita in una subshell, allora sarà facile da testare. Richiede alcuni argomenti e restituisce qualcosa su stdout o su un file, o forse fa qualcosa sul sistema, ma il chiamante non avverte effetti collaterali.

Sì, si finirà con una grande catena di funzioni che passa giù alcune variabili WORKING_DIR che potrebbero anche essere globali, ma questo è un piccolo inconveniente rispetto al compito di tenere traccia di ciò che ogni funzione legge e modifica. Abilitare i test unitari è solo un bonus gratuito.

Provare a ridurre i casi in cui è necessario l'output. Un piccolo abuso di subshell andrà lontano per mantenere le cose ben separate (a scapito delle prestazioni).

Invece di una struttura lineare, in cui vengono chiamate le funzioni, impostare un ambiente, quindi altri vengono chiamati, tutti praticamente su un livello, provare ad andare in deep call tree con il minimo ritorno di dati. Restituire materiale in bash è scomodo se si adotta l'astinenza autoimposta da vars globali ...

+7

Sento che un libro sta arrivando: "Programmazione funzionale in POSIX sh". –

+1

Sì! Abbiamo bisogno di puntatori di funzioni di prima classe! O evals molto economici! – Eugene

8

Dal punto di vista dell'implementazione, suggerisco shUnit.

Da un punto di vista pratico, suggerisco di non mollare. Uso TDD su script bash e confermo che ne vale la pena.

Naturalmente, ottengo circa il doppio delle linee di test rispetto al codice ma con script complessi, gli sforzi nei test sono un buon investimento. Questo è vero in particolare quando il cliente cambia idea verso la fine del progetto e modifica alcuni requisiti. Avere una suite di test di regressione è un grande aiuto nel cambiare codice bash complesso.

+0

Ho iniziato a utilizzare shunit2 (https://code.google.com/p/shunit2) che è semplice e sembra funzionare perfettamente. –

+1

Non sarebbe più semplice usare un linguaggio di programmazione di livello superiore invece di bash? – inf3rno

+2

@ inf3rno - La domanda riguarda bash. I linguaggi di programmazione di livello superiore potrebbero non essere disponibili (ad esempio, un sistema incorporato che utilizza una casella occupata). – mouviciel

3

Scrivere ciò che Meszaros chiama consumer tests è difficile in qualsiasi lingua. Un altro approccio è verificare manualmente il comportamento di comandi come rsync, quindi scrivere test di unità per dimostrare funzionalità specifiche senza colpire la rete.In questo esempio un po 'modificato, $ corsa è utilizzata per stampare gli effetti collaterali, se lo script viene eseguito con la parola chiave "test"

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

Si potrebbe voler dare un'occhiata al cetriolo/aruba. Ha fatto un bel lavoro per me.

Inoltre, è possibile stub quasi tutto ciò che si desidera facendo qualcosa di simile a questo:

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

Il advanced bash scripting guide ha un esempio di una funzione assert ma qui è una funzione assert semplice e più flessibile - basta usare eval di $ * per testare qualsiasi condizione.

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 e bash chiamante incorporate sono shell bash specifico.

0

dare un'occhiata al framework Outthentic - è progettato per creare scenari che eseguono qualsiasi codice Bash e quindi analizzare lo stdout utilizzando DSL formale, è abbastanza facile creare qualsiasi suite di test Tdd/blackbox su questo strumento.