In den 1960er Jahren galt es als grundlegende gute Praxis in der Softwareentwicklung, den Code zu testen, während man ihn schrieb. Die Pioniere der Softwareentwicklung in dieser Ära waren Befürworter verschiedener Testebenen; einige befürworteten „Unit“-Tests, andere nicht, aber alle erkannten die Bedeutung des Testens von Code an.
Ausführbare Tests wurden möglicherweise zuerst von Margaret Hamilton beim Apollo-Projekt Mitte der 1960er Jahre eingeführt, wo sie eine Art von ausführbarer Prüfung entwickelte, die wir heute „statische Codeanalyse“ nennen. Sie nannte es „Software höherer Ordnung“, womit sie Software meinte, die gegen andere Software und nicht direkt gegen den Problembereich arbeitet. Ihre Software höherer Ordnung untersuchte den Quellcode, um nach Mustern zu suchen, von denen bekannt war, dass sie zu Integrationsproblemen führten.
Bis 1970 hatte man das Testen von ausführbaren Dateien weitgehend vergessen. Sicher, man führte Anwendungen aus und stocherte hier und da von Hand darin herum, aber solange das Gebäude nicht um sie herum abbrannte, hielten sie den Code für „gut genug“. Das Ergebnis ist, dass seit über 35 Jahren weltweit Code in Produktion ist, der unzureichend getestet ist und in vielen Fällen nicht ganz so funktioniert, wie beabsichtigt, oder in einer Art und Weise, die die Kunden zufrieden stellt.
Die Idee, dass Programmierer während ihrer Arbeit testen, kam ab Mitte der 1990er Jahre wieder auf, obwohl die große Mehrheit der Programmierer dies bis heute nicht tut. Infrastrukturingenieure und Systemadministratoren testen ihre Skripte sogar noch weniger sorgfältig als Programmierer ihren Anwendungscode.
Da wir uns auf eine Ära zubewegen, in der die schnelle Bereitstellung komplizierter Lösungen, die aus zahlreichen autonomen Komponenten bestehen, zur Norm wird, und „Cloud“-Infrastrukturen von uns verlangen, dass wir Tausende von „come-and-go“-VMs und Containern in einem Umfang verwalten, der mit manuellen Methoden nicht zu bewältigen ist, kann die Bedeutung von ausführbaren, automatisierten Tests und Prüfungen während des gesamten Entwicklungs- und Bereitstellungsprozesses nicht ignoriert werden; nicht nur für Anwendungsprogrammierer, sondern für alle, die an der IT-Arbeit beteiligt sind.
Mit dem Aufkommen von Devops (der gegenseitigen Befruchtung von Entwicklungs- und Betriebsfähigkeiten, -methoden und -werkzeugen) und Trends wie „Infrastruktur als Code“ und „alles automatisieren“ sind Unit-Tests zu einer grundlegenden Fähigkeit für Programmierer, Tester, Systemadministratoren und Infrastrukturingenieure gleichermaßen geworden.
In dieser Serie von Beiträgen werden wir die Idee des Unit-Testens von Shell-Skripten vorstellen und dann verschiedene Unit-Test-Frameworks untersuchen, die dabei helfen können, diese Aufgabe praktisch und nachhaltig zu gestalten.
Eine weitere Praxis, die vielen Infrastrukturingenieuren unbekannt sein mag, ist die Versionskontrolle. Im weiteren Verlauf dieser Serie werden wir uns mit Versionskontrollsystemen und Arbeitsabläufen befassen, die von Anwendungsentwicklern verwendet werden und auch für Infrastrukturingenieure effektiv und nützlich sein können.
- Ein Skript zum Testen
- Automatisierte Funktionsprüfungen
- Bestanden, nicht bestanden und Fehler
- Was sollten wir überprüfen?
- Was sollten wir nicht überprüfen?
- Nachahmen des df-Befehls
- Mocking des mail-Befehls
- Muster für die Ausführung automatisierter Prüfungen
- Warum ein Test-Framework/eine Bibliothek verwenden?
- Unit-Test-Frameworks für Skripte
Ein Skript zum Testen
Vivek Gite hat ein Beispiel-Shell-Skript veröffentlicht, mit dem die Festplattennutzung überwacht und eine E-Mail-Benachrichtigung generiert werden kann, wenn bestimmte Dateisysteme einen Schwellenwert überschreiten. Sein Artikel ist hier zu finden: https://www.cyberciti.biz/tips/shell-script-to-watch-the-disk-space.html. Die erste Version seines Skripts mit der Option -P für den df-Befehl, um Zeilenumbrüche in der Ausgabe zu verhindern, wie in einem Kommentar von Per Lindahl vorgeschlagen, sieht wie folgt aus:
#!/bin/shdf -HP | grep -vE '^Filesystem|tmpfs|cdrom' | awk '{ print " " }' | while read output;do usep=$(echo $output | awk '{ print }' | cut -d'%' -f1 ) partition=$(echo $output | awk '{ print }' ) if ; then echo "Running out of space \"$partition ($usep%)\" on $(hostname) as on $(date)" | mail -s "Alert: Almost out of disk space $usep%" [email protected] fidone
Vivek verfeinert das Skript noch weiter, aber diese Version wird für die Zwecke dieses Beitrags ausreichen.
Automatisierte Funktionsprüfungen
Ein paar Faustregeln für automatisierte Funktionsprüfungen, unabhängig davon, ob wir Anwendungscode, ein Skript oder irgendeine andere Art von Software prüfen:
- die Prüfung muss jedes Mal identisch ablaufen, ohne dass manuelle Anpassungen zur Vorbereitung jedes Durchlaufs erforderlich sind; und
- das Ergebnis darf nicht anfällig für Änderungen der Ausführungsumgebung, der Daten oder anderer Faktoren außerhalb des zu prüfenden Codes sein.
Bestanden, nicht bestanden und Fehler
Sie könnten darauf hinweisen, dass das Skript möglicherweise gar nicht ausgeführt wird. Das ist normal für jede Art von Unit-Test-Framework für jede Art von Anwendung. Statt zwei sind drei Ergebnisse möglich:
- Der zu testende Code zeigt das erwartete Verhalten
- Der zu testende Code läuft, zeigt aber nicht das erwartete Verhalten
- Der zu testende Code läuft nicht
Für praktische Zwecke ist das dritte Ergebnis das gleiche wie das zweite; wir müssen herausfinden, was schief gelaufen ist und es beheben. Daher betrachten wir diese Dinge im Allgemeinen als binär: Bestanden oder nicht bestanden.
Was sollten wir überprüfen?
In diesem Fall sind wir daran interessiert, zu überprüfen, ob sich das Skript bei verschiedenen Eingabewerten wie erwartet verhalten wird. Wir wollen unsere Unit Checks nicht mit weiteren Überprüfungen verunreinigen.
Bei der Überprüfung des zu testenden Codes sehen wir, dass das Skript, wenn die Festplattennutzung einen Schwellenwert von 90 % erreicht, Mail aufruft, um eine Benachrichtigung an den Systemadministrator zu senden.
In Übereinstimmung mit der allgemein anerkannten guten Praxis für Unit Checks wollen wir separate Fälle definieren, um jedes Verhalten zu überprüfen, das wir für jeden Satz von Ausgangsbedingungen erwarten.
Wenn wir unseren „Tester“-Hut aufsetzen, sehen wir, dass dies eine Art Randbedingung ist. Wir müssen nicht zahlreiche verschiedene Prozentsätze der Festplattennutzung einzeln prüfen. Wir müssen nur das Verhalten an den Grenzen überprüfen. Daher ist die minimale Menge an Fällen, die eine sinnvolle Abdeckung bieten, folgende:
- Sie sendet eine E-Mail, wenn die Festplattennutzung den Schwellenwert erreicht
- Sie sendet keine E-Mail, wenn die Festplattennutzung unter dem Schwellenwert liegt
Was sollten wir nicht überprüfen?
In Übereinstimmung mit der allgemein akzeptierten guten Praxis für die Isolierung von Unit-Tests wollen wir sicherstellen, dass jeder unserer Fälle aus genau einem Grund fehlschlagen kann: Das erwartete Verhalten tritt nicht ein. Soweit es möglich ist, wollen wir unsere Prüfungen so einrichten, dass andere Faktoren nicht dazu führen, dass der Fall fehlschlägt.
Es ist vielleicht nicht immer kosteneffektiv (oder sogar möglich) zu garantieren, dass externe Faktoren unsere automatisierten Prüfungen nicht beeinflussen. Es gibt Fälle, in denen wir ein externes Element nicht kontrollieren können oder in denen dies mehr Zeit, Aufwand und Kosten verursachen würde, als die Prüfung wert ist, und/oder in denen es sich um einen obskuren Sonderfall handelt, der nur mit sehr geringer Wahrscheinlichkeit eintritt oder nur sehr geringe Auswirkungen hat, wenn er eintritt. Das ist eine Frage Ihres professionellen Urteils. Als allgemeine Regel gilt, dass Sie es tunlichst vermeiden sollten, Abhängigkeiten von Faktoren zu schaffen, die über den Umfang des zu testenden Codes hinausgehen.
Wir müssen nicht überprüfen, ob die Befehle df, grep, awk, cut und mail funktionieren. Das ist für unsere Zwecke nicht relevant. Wer auch immer die Dienstprogramme pflegt, ist dafür verantwortlich.
Wir wollen wissen, ob die Ausgabe des Befehls df nicht so verarbeitet wird, wie wir es von grep oder awk erwarten. Deshalb wollen wir, dass die echten grep- und awk-Befehle in unseren Prüfungen ausgeführt werden, basierend auf der Ausgabe des df-Befehls, die der Absicht jedes Testfalls entspricht. Das ist möglich, weil die Befehlszeilenargumente für df Teil des Skripts sind, und das Skript ist der zu testende Code.
Das bedeutet, dass wir eine gefälschte Version des df-Befehls für unsere Unit Checks benötigen. Diese Art von gefälschter Komponente wird oft als Mock bezeichnet. Ein Mock steht für eine echte Komponente und liefert vordefinierte Ausgaben, um das Systemverhalten kontrolliert zu steuern, so dass wir das Verhalten des zu testenden Codes zuverlässig überprüfen können.
Wir sehen, dass das Skript eine E-Mail-Benachrichtigung sendet, wenn ein Dateisystem den Schwellenwert für die Nutzung erreicht. Wir wollen nicht, dass unsere Unit Checks einen Haufen nutzloser E-Mails ausspucken, also wollen wir auch den mail-Befehl nachahmen.
Dieses Skript ist ein gutes Beispiel, um das Nachahmen dieser Befehle zu veranschaulichen, da wir es für mail anders machen als für df.
Nachahmen des df-Befehls
Das Skript ist um den df-Befehl herum aufgebaut. Die entsprechende Zeile im Skript lautet:
df -HP | grep -vE '^Filesystem|tmpfs|cdrom' | awk '{ print " " }'
Wenn Sie nur df -HP ausführen, ohne grep einzugeben, erhalten Sie eine ähnliche Ausgabe wie diese:
Filesystem Size Used Avail Use% Mounted onudev 492M 0 492M 0% /devtmpfs 103M 6.0M 97M 6% /run/dev/sda1 20G 9.9G 9.2G 52% /tmpfs 511M 44M 468M 9% /dev/shmtmpfs 5.3M 0 5.3M 0% /run/locktmpfs 511M 0 511M 0% /sys/fs/cgrouptmpfs 103M 8.2k 103M 1% /run/user/1000
Die Befehle grep und awk reduzieren die Ausgabe auf diese:
0% udev52% /dev/sda1
Wir müssen die Ausgabe von df kontrollieren, um unsere Testfälle zu steuern. Wir wollen nicht, dass das Ergebnis der Prüfung von der tatsächlichen Festplattennutzung auf dem System abhängt, auf dem wir die Testsuite ausführen. Wir prüfen nicht die Festplattennutzung, sondern die Logik des Skripts. Wenn das Skript in der Produktion läuft, wird es die Festplattenauslastung prüfen. Was wir hier tun, dient der Validierung, nicht dem Produktionsbetrieb. Deshalb brauchen wir einen gefälschten oder „mock“ df-Befehl, mit dem wir die „Testdaten“ für jeden Fall erzeugen können.
Auf einer *nix-Plattform ist es möglich, den echten df-Befehl durch die Definition eines Alias außer Kraft zu setzen. Wir wollen, dass der Alias-Befehl Testwerte im gleichen Format ausgibt wie die Ausgabe von df -HP. Hier ist eine Möglichkeit, dies zu tun (es handelt sich um eine einzige Zeile; zur besseren Lesbarkeit ist sie unten aufgeteilt):
alias df="shift;echo -e 'Filesystem Size Used Avail Use% Mounted on'; echo -e 'tempfs 511M 31M 481M 6% /dev/shm'; echo -e '/dev/sda1 20G 9.9G 9.2G 52% /'"
Die Verschiebung überspringt das Argument „-HP“, wenn das Skript ausgeführt wird, so dass sich das System nicht beschwert, dass -HP ein unbekannter Befehl ist. Der Aliasing-Befehl df gibt eine Ausgabe in der gleichen Form wie df -HP aus.
Die Testwerte werden bei der Ausführung des Skripts in grep und dann in awk geleitet, so dass wir nur das zur Kontrolle unserer Testfälle erforderliche Minimum nachbilden. Wir wollen, dass unser Testfall so nah wie möglich an der „echten Sache“ ist, damit wir keine falsch-positiven Ergebnisse erhalten.
Mocks, die mit einer Mocking-Bibliothek erstellt werden, können beim Aufruf einen vordefinierten Wert zurückgeben. Unser Ansatz für das Mocking des df-Befehls spiegelt die Funktion eines Mocks wider; wir legen eine vordefinierte Ausgabe fest, die zurückgegeben wird, wenn der zu testende Code df aufruft.
Mocking des mail-Befehls
Wir wollen wissen, ob das Skript unter den richtigen Bedingungen versucht, eine E-Mail zu senden, aber wir wollen nicht, dass es irgendwo eine echte E-Mail sendet. Deshalb wollen wir den mail-Befehl mit einem Alias versehen, so wie wir es zuvor mit dem df-Befehl gemacht haben. Wir müssen etwas einrichten, das wir nach jedem Testfall überprüfen können. Eine Möglichkeit ist, einen Wert in eine Datei zu schreiben, wenn mail aufgerufen wird, und diesen Wert dann in unserem Testfall zu überprüfen. Dies wird im folgenden Beispiel gezeigt. Andere Methoden sind ebenfalls möglich.
Mocks, die mit einer Mocking-Bibliothek erstellt werden, können die Anzahl der Aufrufe durch den zu testenden Code zählen, und wir können die erwartete Anzahl von Aufrufen bestätigen. Wenn der Text „mail“ in der Datei mailsent vorhanden ist, nachdem wir das Skript diskusage.sh ausgeführt haben, bedeutet dies, dass das Skript den Mail-Befehl aufgerufen hat.
Muster für die Ausführung automatisierter Prüfungen
Automatisierte oder ausführbare Prüfungen auf jeder Abstraktionsebene, für jede Art von Anwendung oder Skript, in jeder Sprache, umfassen normalerweise drei Schritte. Diese haben in der Regel folgende Namen:
- Arrange
- Act
- Assert
Der Grund dafür ist wahrscheinlich, dass jeder die Alliteration liebt, besonders auf dem Buchstaben A, da das Wort „Alliteration“ selbst mit dem Buchstaben A beginnt.
Was auch immer der Grund dafür ist, im Arrange-Schritt stellen wir die Vorbedingungen für unseren Testfall auf. Im act-Schritt rufen wir den zu testenden Code auf. Im Assert-Schritt geben wir das erwartete Ergebnis an.
Wenn wir ein Test-Framework oder eine Bibliothek verwenden, übernimmt das Tool den Assert-Schritt für uns, so dass wir in unseren Testsuiten nicht viel umständliche if/else-Logik programmieren müssen. Für unser erstes Beispiel verwenden wir kein Testframework und keine Bibliothek, so dass wir die Ergebnisse jedes Falls mit einem if/else-Block überprüfen. In der nächsten Folge werden wir mit Unit-Test-Frameworks für Shell-Sprachen spielen und sehen, wie das aussieht.
Hier ist unser grobes, aber effektives Testskript zum Testen von Viveks Shell-Skript, das wir diskusage.sh genannt haben:
#!/bin/bashshopt -s expand_aliases# Before allalias mail="echo 'mail' > mailsent;false"echo 'Test results for diskusage.sh' > test_resultstcnt=0# It does nothing when disk usage is below 90%# Before (arrange)alias df="echo 'Filesystem Size Used Avail Use% Mounted on';echo '/dev/sda2 100G 89.0G 11.0G 89% /'"echo 'no mail' > mailsent# Run code under test (act). ./diskusage.sh# Check result (assert)((tcnt=tcnt+1))if ]; then echo "$tcnt. FAIL: Expected no mail to be sent for disk usage under 90%" >> test_resultselse echo "$tcnt. PASS: No action taken for disk usage under 90%" >> test_resultsfi # It sends an email notification when disk usage is at 90%alias df="echo 'Filesystem Size Used Avail Use% Mounted on';echo '/dev/sda1 100G 90.0G 10.0G 90% /'"echo 'no mail' > mailsent. ./diskusage.sh((tcnt=tcnt+1))if ]; then echo "$tcnt. PASS: Notification was sent for disk usage of 90%" >> test_resultselse echo "$tcnt. FAIL: Disk usage was 90% but no notification was sent" >> test_resultsfi # After allunalias dfunalias mail# Display test results cat test_results
Hier ist ein Durchgang durch das Testskript.
Zunächst sehen Sie, dass wir Bash verwenden, um eine einfache alte .sh-Datei zu testen. Das ist völlig in Ordnung. Es ist nicht notwendig, aber in Ordnung.
Als nächstes sehen Sie einen shopt-Befehl. Dieser veranlasst die Shell, unsere Test-Aliase zu erweitern, wenn die Subshell aufgerufen wird, um das Skript diskusage.sh auszuführen. In den meisten Anwendungsfällen würden wir keine Aliase an Subshells übergeben, aber Unit-Tests sind eine Ausnahme.
Der Kommentar „Before all“ ist für Leute, die mit Unit-Test-Frameworks vertraut sind, die Befehle zum Einrichten und Abbauen haben. Diese heißen oft so etwas wie „vorher“ und „nachher“, und es gibt normalerweise ein Paar, das die gesamte Testsuite einklammert, und ein anderes Paar, das für jeden Testfall einzeln ausgeführt wird.
Wir wollten zeigen, dass die Definition des Alias für Mail, die Initialisierung der Testergebnisdatei und die Initialisierung des Testfallzählers alle genau einmal, am Anfang der Testsuite, durchgeführt werden. Solche Dinge sind in ausführbaren Testsuiten normal. Die Tatsache, dass wir ein Shell-Skript und kein Anwendungsprogramm testen, ändert daran nichts.
Der nächste Kommentar, „Es tut nichts…“, zeigt den Beginn unseres ersten einzelnen Testfalls an. Die meisten Unit-Test-Frameworks bieten die Möglichkeit, jedem Fall einen Namen zu geben, damit wir den Überblick behalten und damit andere Tools Testfälle aus verschiedenen Gründen suchen, filtern und extrahieren können.
Als Nächstes folgt ein Kommentar, der lautet: „Before (arrange)“. Dieser Kommentar steht für eine Einstellung, die nur für diesen einen Testfall gilt. Wir stellen den df-Alias so ein, dass er die Ausgabe ausgibt, die wir für diesen speziellen Fall benötigen. Außerdem schreiben wir den Text „no mail“ in eine Datei. So können wir feststellen, ob das Skript diskusage.sh versucht hat, eine Benachrichtigungs-E-Mail zu senden.
Der nächste Schritt ist die Ausführung des zu testenden Codes. In diesem Fall bedeutet das, dass wir das Skript diskusage.sh selbst ausführen. Wir führen es als Quelle aus, anstatt es direkt auszuführen.
Jetzt folgt der Assert-Schritt, den wir in diesem Beispiel auf die harte Tour machen, weil wir noch kein Test-Framework eingeführt haben. Wir inkrementieren den Testzähler, damit wir die Testfälle in der Ergebnisdatei nummerieren können. Andernfalls könnte es bei einer großen Anzahl von Fällen schwierig werden, herauszufinden, welche Fälle fehlgeschlagen sind. Test-Frameworks erledigen das für uns.
Der Alias, den wir für den Befehl mail definiert haben, schreibt den Text ‚mail‘ in die Datei mailsent. Wenn diskusage.sh den Befehl mail aufruft, enthält die Datei mailsent den Text ‚mail‘ anstelle des Anfangswertes ’no mail‘. Sie können die Bedingungen für das Bestehen und Scheitern des Tests erkennen, indem Sie die Zeichenketten lesen, die in der Testergebnisdatei ausgegeben werden.
Beginnen Sie mit dem Kommentar „Es sendet eine E-Mail-Benachrichtigung…“ und wiederholen Sie die Schritte arrange, act, assert für einen weiteren Testfall. Diesmal wird unser gefälschter df-Befehl andere Daten ausgeben, um ein anderes Verhalten des zu testenden Codes zu bewirken.
Wo der Kommentar „After all“ erscheint, räumen wir hinter uns auf, indem wir die Definitionen entfernen, die wir in der „Before all“-Einrichtung am Anfang des Testskripts erstellt haben.
Schließlich geben wir den Inhalt der Datei test_results aus, damit wir sehen können, was wir bekommen haben. Sie sieht so aus:
Test results for diskusage.sh1. PASS: No action taken for disk usage under 90%2. PASS: Notification was sent for disk usage of 90%
Warum ein Test-Framework/eine Bibliothek verwenden?
Wir haben gerade ein paar Unit-Testfälle für ein Shell-Skript geschrieben, ohne ein Test-Framework, eine Mocking-Bibliothek oder eine Assertion-Bibliothek zu verwenden. Wir haben herausgefunden, dass Systembefehle durch die Definition von Aliasen nachgebildet werden können (zumindest auf *Nix-Systemen), dass Assertions als bedingte Anweisungen implementiert werden können und dass die Grundstruktur eines Unit-Tests leicht von Hand zu erstellen ist.
Es war nicht schwierig, dies ohne ein Framework oder eine Bibliothek zu tun. Was ist also der Vorteil?
Test-Frameworks und -Bibliotheken vereinfachen und standardisieren den Testcode und ermöglichen viel besser lesbare Testsuiten als handgeschriebene Skripte mit vielen bedingten Anweisungen. Einige Bibliotheken enthalten nützliche Zusatzfunktionen, z. B. die Möglichkeit, Ausnahmen abzufangen oder tabellen- und datengesteuerte Testfälle zu schreiben. Einige sind auf die Unterstützung bestimmter Produkte zugeschnitten, die für Infrastrukturingenieure von Interesse sind, z. B. Chef und Puppet. Und einige enthalten Funktionen zum Verfolgen der Codeabdeckung und/oder zum Formatieren von Testergebnissen in einer Form, die von Werkzeugen in der CI/CD-Pipeline oder zumindest einem Webbrowser konsumiert werden kann.
Unit-Test-Frameworks für Skripte
In dieser Serie werden wir mehrere Unit-Test-Frameworks für Shell-Skripte und Skriptsprachen untersuchen. Hier ein Überblick:
- shunit2 ist ein sehr solides Open Source Projekt mit einer zehnjährigen Geschichte. Ursprünglich entwickelt von Kate Ward, Site Reliability Engineer und Managerin bei Google in Zürich, wird es von einem sechsköpfigen Team aktiv weiterentwickelt und unterstützt. Von bescheidenen Anfängen als Punktlösung zum Testen einer Logging-Bibliothek für Shell-Skripte hat es sich bewusst zu einem universellen Unit-Test-Framework entwickelt, das mehrere Shell-Sprachen und Betriebssysteme unterstützt. Es enthält eine Reihe nützlicher Funktionen, die über einfache Assertions hinausgehen, einschließlich der Unterstützung von daten- und tabellengesteuerten Tests. Es verwendet den traditionellen „assertThat“-Stil für Assertions. Die Projektseite enthält eine ausgezeichnete Dokumentation. Für allgemeine Unit-Tests von Shell-Skripten ist dies meine beste Empfehlung.
- BATS (Bash Automated Testing System) ist ein Unit-Test-Framework für Bash. Es wurde von Sam Stephenson vor etwa sieben Jahren entwickelt und hat etwa ein Dutzend Mitwirkende gehabt. Die letzte Aktualisierung war vor vier Jahren, aber das ist kein Grund zur Sorge, da diese Art von Werkzeug keine häufigen Aktualisierungen oder Wartung benötigt. BATS basiert auf dem Test Anything Protocol (TAP), das eine konsistente textbasierte Schnittstelle zwischen Modulen in jeder Art von Test-Harness definiert. Es ermöglicht eine saubere, konsistente Syntax in Testfällen, obwohl es nicht viel syntaktischen Zucker über reine Bash-Anweisungen hinaus zu bieten scheint. Es gibt zum Beispiel keine spezielle Syntax für Assertions; Sie schreiben Bash-Befehle für Testergebnisse. In diesem Sinne liegt der Hauptwert in der logischen Organisation von Testsuiten und Testfällen. Beachten Sie auch, dass das Schreiben von Testskripten in Bash uns nicht daran hindert, Nicht-Bash-Skripte zu testen; wir haben das bereits in diesem Beitrag getan. Die Tatsache, dass die BATS-Syntax so nahe an der einfachen Bash-Syntax liegt, gibt uns viel Flexibilität, um mit verschiedenen Shell-Sprachen in unseren Testsuiten umzugehen, möglicherweise auf Kosten der Lesbarkeit (je nachdem, was Sie für „lesbar“ halten; die Zielgruppe dieses Beitrags findet die einfache Shell-Syntax wahrscheinlich ziemlich lesbar). Eine besonders interessante Funktion (meiner Meinung nach) ist, dass Sie Ihren Texteditor mit Syntaxhervorhebung für BATS einrichten können, wie im Projekt-Wiki dokumentiert. Emacs, Sublime Text 2, TextMate, Vim und Atom wurden zum Zeitpunkt dieses Beitrags unterstützt.
- zunit (nicht der IBM-Editor, sondern der andere) ist ein von James Dinsdale entwickeltes Unit-Test-Framework für zsh. Auf der Projektseite heißt es, zunit sei von BATS inspiriert, und es enthält die äußerst nützlichen Variablen $state, $output und $lines. Aber es hat auch eine definitive Assertion-Syntax, die dem Muster „assert actual matches expected“ folgt. Jedes dieser Frameworks hat einige einzigartige Eigenschaften. Eine interessante Eigenschaft von ZUnit ist meiner Meinung nach, dass es alle Testfälle, die keine Assertion enthalten, als „riskant“ kennzeichnet. Sie können dies außer Kraft setzen und die Ausführung der Fälle erzwingen, aber standardmäßig hilft Ihnen das Framework, sich daran zu erinnern, eine Assertion in jeden Testfall aufzunehmen.
- bash-spec ist ein Testframework im Verhaltensstil, das nur Bash unterstützt (oder zumindest wurde es nur mit Bash-Skripten getestet). Es ist ein bescheidenes Nebenprojekt von mir, das es seit über vier Jahren gibt und ein paar „echte“ Benutzer hat. Es wird nicht oft aktualisiert, da es derzeit das tut, wofür es gedacht war. Ein Ziel des Projekts war es, Bash-Funktionen in einem „flüssigen“ Stil zu verwenden. Die Funktionen werden nacheinander aufgerufen, wobei jede die gesamte Argumentliste an die nächste weitergibt, nachdem sie so viele Argumente verbraucht hat, wie sie zur Erfüllung ihrer Aufgabe benötigt. Das Ergebnis ist eine lesbare Testsuite mit Anweisungen wie „expect package-name to_be_installed“ und „expect arrayname not to_contain value“. Wenn es als Leitfaden für die testorientierte Entwicklung von Skripten verwendet wird, führt sein Design den Entwickler dazu, Funktionen zu schreiben, die die Idee der „Modularität“ oder „Einzelverantwortung“ oder „Trennung von Belangen“ (wie auch immer man es nennen will) unterstützen, was zu einer einfachen Wartung und leicht wiederverwendbaren Funktionen führt. „Behavioral style“ bedeutet, dass Assertions die Form „expect this to match that“ annehmen.
- korn-spec ist eine Portierung von bash-spec für die korn shell.
- Pester ist das Unit Test Framework der Wahl für Powershell. Powershell sieht aus und fühlt sich mehr wie eine Anwendungsprogrammiersprache als eine reine Skriptsprache an, und Pester bietet eine vollständig konsistente Entwicklererfahrung. Pester wird mit Windows 10 ausgeliefert und kann auf jedem anderen System installiert werden, das Powershell unterstützt. Es verfügt über eine robuste Assertion-Bibliothek, integrierte Unterstützung für Mocking und sammelt Codeabdeckungsmetriken.
- ChefSpec baut auf rspec auf, um ein verhaltensorientiertes Test-Framework für Chef-Rezepte bereitzustellen. Chef ist eine Ruby-Anwendung, und ChefSpec nutzt alle Vorteile der rspec-Fähigkeiten sowie die eingebaute Unterstützung für Chef-spezifische Funktionen.
- rspec-puppet ist ein verhaltensorientiertes Framework für Puppet, funktional ähnlich wie ChefSpec.