Unit-Testing-Bash-Skripte

112

Wir haben ein System, auf dem neben Java-Code einige Bash-Skripte ausgeführt werden. Da wir versuchen, alles zu testen, was möglicherweise kaputt gehen könnte, und diese Bash-Skripte möglicherweise kaputt gehen, möchten wir sie testen.

Das Problem ist, dass es schwierig ist, Bash-Skripte zu testen.

Gibt es eine Möglichkeit oder eine bewährte Methode, um Bash-Skripte zu testen? Oder sollten wir die Verwendung von Bash-Skripten beenden und nach alternativen Lösungen suchen, die testbar sind?

Nimcap
quelle
Mögliches Duplikat von Unit-Tests für Shell-Skripte
Benutzer
Übersicht der vorhandenen Tools: medium.com/wemake-services/…
sobolevn

Antworten:

48

Es gibt tatsächlich ein shunit2 , ein xUnit-basiertes Unit-Test-Framework für Bourne-basierte Shell-Skripte. Ich habe es selbst nicht benutzt, aber es könnte sich lohnen, es auszuprobieren.

Ähnliche Fragen wurden bereits gestellt:

ire_and_curses
quelle
3
Ich kann behaupten (Wortspiel beabsichtigt), dass shunit2 (Version 2.1.6) bis heute etwas kaputt ist. AssertNull und assertNotNull funktionieren nicht, selbst wenn Sie ihnen direkte Werte zuführen. assertEquals funktioniert gut, aber ich denke, ich muss erst einmal meine eigenen rollen.
Labyrinth
@ Labyrinth, sind Sie sicher, dass das Problem nicht der Fall war: github.com/kward/shunit2/issues/53 "Wie verwende ich assertNull richtig?"?
Victor Sergienko
1
@ Victor Es ist definitiv möglich, dass ich mit meinen doppelten Anführungszeichen nicht vorsichtig genug war. Ich werde bald wieder in eine Rolle zurückkehren, in der shunit2 oder ein Bash-Unit-Testing-System sehr nützlich sein wird. Ich werde es noch einmal versuchen.
Labyrinth
5
Ich bin ein Benutzer und manchmal Mitwirkender bei shunit2, und ich kann bestätigen, dass das Projekt im Jahr 2019 lebendig und gut ist.
Alex Harvey
31

Ich habe die folgende Antwort von einer Diskussionsgruppe erhalten:

Es ist möglich, eine Prozedur (Funktion, wie auch immer sie heißt) aus einer externen Datei zu importieren (einschließlich, was auch immer). Das ist der Schlüssel zum Schreiben eines Testskripts: Sie teilen Ihr Skript in unabhängige Prozeduren auf, die dann sowohl in Ihr laufendes Skript als auch in Ihr Testskript importiert werden können, und dann muss Ihr laufendes Skript so einfach wie möglich sein.

Diese Methode ähnelt der Abhängigkeitsinjektion für Skripte und klingt vernünftig. Das Vermeiden von Bash-Skripten und die Verwendung einer besser testbaren und weniger undurchsichtigen Sprache ist vorzuziehen.

Nimcap
quelle
4
Ich bin mir nicht sicher, ob ich nach oben oder unten stimmen soll. Einerseits ist es gut, sich in kleinere Teile zu teilen, andererseits brauche ich ein Framework, keine benutzerdefinierten Skripte
mpapis
10
Während Bash nichts auszusetzen hat (ich habe viele, viele Skripte geschrieben), ist es eine schwer zu beherrschende Sprache. Meine Faustregel lautet: Wenn ein Skript groß genug ist, um Tests zu benötigen, sollten Sie wahrscheinlich zu einer Skriptsprache wechseln, die leicht zu testen ist.
Doug
1
Aber manchmal müssen Sie etwas haben, das in der Shell eines Benutzers bezogen werden kann. Mir ist nicht klar, wie Sie das tun würden, ohne auf ein Shell-Skript zurückzugreifen
Itkovian
@Itkovian - Sie könnten beispielsweise npm verwenden, um eine ausführbare Datei in den Pfad zu exportieren, sodass keine Beschaffung erforderlich ist (Ihr npm-Paket muss global installiert werden)
Eliran Malka
1
Ich werde den Rat befolgen, Bash nicht zu verwenden. :)
Maciej Wawrzyńczuk
29

TAP- konformes Bash-Testen: Bash Automated Testing System

TAP, das Test Anything Protocol, ist eine einfache textbasierte Schnittstelle zwischen Testmodulen in einem Testkabelbaum. TAP wurde als Teil des Test-Harness für Perl eingeführt, verfügt nun jedoch über Implementierungen in C, C ++, Python, PHP, Perl, Java, JavaScript und anderen.

Janus Troelsen
quelle
14
Es lohnt sich zu offenbaren, was TAP ist und warum es einen interessieren sollte, sonst ist es nur bedeutungsloses Kopieren und Einfügen
om-nom-nom
@ om-nom-nom: Ich habe es jetzt mit der TAP-Site verlinkt.
Janus Troelsen
7
Da sonst niemand das Unaussprechliche sagte: TAP = Test Anything Protocol
JW.
8

Nikita Sobolev hat einen ausgezeichneten Blog-Beitrag geschrieben, in dem einige verschiedene Bash-Test-Frameworks verglichen wurden: Testen von Bash-Anwendungen

Für die Ungeduldigen: Nikitas Schlussfolgerung war die Verwendung von Fledermäusen, aber es scheint, dass Nikita das Bats-Kernprojekt verpasst hat, das meiner Meinung nach in Zukunft verwendet werden sollte, da das ursprüngliche Bats-Projekt seit 2013 nicht mehr aktiv gepflegt wurde.

cb2
quelle
7

Epoxy ist ein Bash-Test-Framework, das ich hauptsächlich zum Testen anderer Software entwickelt habe, aber ich verwende es auch zum Testen von Bash-Modulen, einschließlich sich selbst und Carton .

Hauptvorteile sind ein relativ geringer Codierungsaufwand, eine unbegrenzte Verschachtelung von Zusicherungen und eine flexible Auswahl der zu überprüfenden Zusicherungen.

Ich habe eine Präsentation gemacht , in der ich sie mit BeakerLib verglichen habe - einem Framework, das von einigen bei Red Hat verwendet wird.

spbnick
quelle
6

Warum ist es Ihrer Meinung nach "schwierig", Bash-Skripte zu testen?

Was ist los mit Test-Wrappern wie:

 #!/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
quelle
4
Erstens sind Bash-Skripte nicht sehr gut lesbar. Zweitens sind Erwartungen kompliziert, beispielsweise zu überprüfen, ob eine Sperrdatei mit der PID des Bash-Skripts erstellt wurde, mit dem sie erstellt wurde.
Nimcap
10
Noch wichtiger ist, dass es schwierig ist, Shell-Skripte zu testen, da sie im Allgemeinen eine große Anzahl von Nebenwirkungen haben und Systemressourcen wie Dateisystem, Netzwerk usw. verwenden. Im Idealfall sind Komponententests nebenwirkungsfrei und hängen nicht von Systemressourcen ab.
Jayhendren
4

Ich habe Shellspec erstellt, weil ich ein benutzerfreundliches und nützliches Tool haben wollte.

Es wurde von einem reinen POSIX-Shell-Skript geschrieben. Es hat mit vielen Muscheln mehr als shunit2 getestet. Es hat mächtige Funktionen als Fledermäuse / Fledermaus-Kern.

Zum Beispiel Unterstützung für verschachtelte Blöcke, leicht zu verspotten / zu stubben, leicht zu überspringen / ausstehend, parametrisierte Tests, Assertion-Zeilennummer, Ausführung nach Zeilennummer, parallele Ausführung, zufällige Ausführung, TAP / JUnit-Formatierer, Abdeckung und CI-Integration, Profiler und so weiter .

Siehe die Demo auf der Projektseite.

Koichi Nakashima
quelle
3

Ich mag Shell2junit , ein Dienstprogramm zum Generieren von JUnit-ähnlichen Ausgaben aus Bash-Skripttests. Dies ist nützlich, da der generierte Bericht dann von kontinuierlichen Integrationssystemen wie den JUnit-Plug-Ins für Jenkins und Bamboo gelesen werden kann.

Shell2junit bietet zwar nicht das umfassende Bash-Scripting-Framework wie shunit2 , ermöglicht Ihnen jedoch eine gute Berichterstattung über die Testergebnisse.

Steve HHH
quelle
3

Versuchen Sie es mit einem Test . Es ist eine einfache Möglichkeit, Ihre Skripte zu testen. Zum Beispiel haben Sie do-some-work.shwelche Konfigurationsdateien geändert. PASSWORD = 'XXXXX'Fügen Sie beispielsweise der Konfigurationsdatei eine neue Zeile hinzu /etc/my.cfg.

Sie schreiben zeilenweise Bash-Befehle und überprüfen dann die Ausgabe.

Installieren:

pip3 install bashtest

Das Erstellen von Tests ist nur das Schreiben von Bash-Befehlen.

Datei 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

Führen Sie Tests durch:

bashtest *.bashtest

Hier und hier finden Sie einige Beispiele

pahaz
quelle
3

Vielleicht kann dies verwendet oder dazu beigetragen werden

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

Beabsichtigt, Ergebnisse in das TAP-Protokoll zu schreiben, was meiner Meinung nach gut für CI und gut für diejenigen ist, die Shell-Umgebungen wollen. Ich stelle mir vor, dass einige Dinge in Shell-Umgebungen ausgeführt werden, daher könnten einige argumentieren, dass sie in ihrer Shell-Umgebung getestet werden sollten.

hrob
quelle
3

Versuchen Sie es mit assert.sh

source "./assert.sh"

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

Ich hoffe es hilft!

Kennzeichen
quelle
3

Ich kann nicht glauben, dass niemand über OSHT gesprochen hat ! Es ist sowohl mit TAP als auch mit JUnit kompatibel , es ist eine reine Shell (dh keine anderen beteiligten Sprachen), es funktioniert auch eigenständig und es ist einfach und direkt.

Das Testen sieht folgendermaßen aus (Ausschnitte aus der Projektseite):

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

Ein einfacher Lauf:

$ 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

Der letzte Test zeigt "nicht in Ordnung", aber der Exit-Code ist 0, weil es ein ist TODO. Man kann auch ausführlich setzen:

$ 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

Benennen Sie es um, um eine .tErweiterung zu verwenden, und fügen Sie es in ein tUnterverzeichnis ein. Sie können es prove(1)(Teil von Perl) verwenden, um es auszuführen:

$ 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

Setzen OSHT_JUNIToder übergeben -j, um eine JUnit-Ausgabe zu erzeugen. JUnit kann auch mit kombiniert werden prove(1).

Ich habe diese Bibliothek sowohl zum Testen von Funktionen verwendet, indem ich ihre Dateien bezogen und dann Zusicherungen mit IS/ OKund ihren Negativen ausgeführt habe, als auch zu Skripten, indem ich RUN/ verwendet habe NRUN. Für mich bietet dieses Framework den größten Gewinn bei geringstem Overhead.

Daniel C. Sobral
quelle
1

Ich habe viele der hier vorgestellten Lösungen ausprobiert, fand die meisten jedoch sperrig und schwer zu verwenden. Deshalb habe ich mein eigenes kleines Testframework erstellt: https://github.com/meonlol/t-bash

Es ist nur eine Datei im Repo, die Sie einfach direkt ausführen können, mit einem grundlegenden Satz von JUnit-Stil-Asserts.

Ich habe es professionell in mehreren internen Projekten verwendet und konnte unsere Bash-Skripte super stabil und regressionsresistent machen.

leondepeon
quelle
0

Schauen Sie sich Outthentic an , es ist einfach, erweiterbar durch viele Sprachen (Perl, Python, Ruby, Bash on Choice) und plattformübergreifendes Framework (Linux, Windows), um alle Befehlszeilenanwendungen zu testen.

Alexey Melezhik
quelle
-2

Es fällt mir schwer, die Verwendung von Bash für größere Skripte zu rechtfertigen, wenn Python so große Vorteile bietet:

  • Try / Except ermöglicht das Schreiben robusterer Skripte mit der Möglichkeit, Änderungen im Fehlerfall rückgängig zu machen.
  • Sie müssen keine obskure Syntax wie 'if [ x"$foo" = x"$bar"]; then ... ' verwenden, die fehleranfällig ist.
  • Einfaches Parsen von Optionen und Argumenten mit dem getopt Moduls (und es gibt ein noch einfacheres Modul zum Parsen von Argumenten, aber der Name entgeht mir).
  • Mit Python können Sie mit Listen / Dikten und Objekten anstelle von einfachen Zeichenfolgen und Arrays arbeiten.
  • Zugriff auf geeignete Sprachwerkzeuge wie Regex und Datenbanken (Sie können zwar alles in den mysqlBefehl in bash einfügen, aber es ist nicht die beste Art, Code zu schreiben).
  • Sie müssen sich keine Gedanken über die Verwendung der richtigen Form von $*oder "$*"oder "$@"oder $1oder machen "$1". Leerzeichen in Dateinamen sind kein Problem usw. usw. usw.

Jetzt benutze ich bash nur für die einfachsten Skripte.

zu viel php
quelle
3
Nicht zu leugnen, dass Python Vorteile hat, aber Ihr zweiter Punkt ist nicht sehr gut formuliert. Der gleiche Vergleich hätte durchgeführt werden können wie if [[ $foo = $bar ]]; then .... Dies ist immer noch nicht besser als das, was Python zu bieten hat, aber es ist besser als das, was Sie präsentiert haben.
Shrikant Sharat
8
Einige Systeme (z. B. eingebettet) verfügen nicht über Python und Sie können / möchten keine zusätzlichen Inhalte installieren.
Rui Marques
2
Ich persönlich liebe Bash, stimme aber zu, dass es ein wenig gereizt sein kann. Normalerweise müssen Sie viel proaktiver sein, während Sie in Python Fehler beheben können, nachdem sie aufgetreten sind. Bash hat jedoch trap(im Fehlerfall bereinigen / rückgängig machen) sowie Regex (dh [[ $1 =~ ^[1-3]{3}$ ]]). Ich bin mir ziemlich sicher, dass die obskure Syntax, die Sie verwendet haben, sich auf alte Implementierungen von bezieht test, nicht auf Bash. Bash eignet sich hervorragend für die Anbindung an vorhandene Befehlszeilentools ... Häufig ist eine einzelne Pipe zu awkoder grepviel einfacher als die Python-Alternative.
Sechs
1
Übrigens ist das Parser-Modul, auf das Sie sich bezogen haben, wahrscheinlich optparseoder sein Nachfolger argparse. Ich habe noch nie jemanden gesehen, der das getoptModul verwendet hat, noch habe ich es persönlich verwendet. Das getoptDienstprogramm ist jedoch großartig. Das Parsen von Argumenten aus der Shell ist überhaupt kein Problem, wenn Sie ein schönes Muster haben. Wenn Sie nicht versuchen, Unterbefehle im Git-Stil oder ähnliches zu implementieren, ist dies kein großes Problem.
Sechs
Python läuft nicht überall dort, wo Bash erreichen kann. Ich sage das, weil wir Bash gegen Python getestet haben, dieselbe Codelogik, und beide aufgefordert haben, etwas zu tun. Bash gab jedes Verzeichnis ein, auf das es Zugriff hatte. Andererseits konnte Python einige Verzeichnisberechtigungen und -dateien sowie Verzeichnisse, die sehr schnell zunahmen und abnahmen, nicht verarbeiten.
vianna77