Einfaches Mocking von Kommandos in Shellskripten
Stellt euch vor, ihr habt ein Shellskript, das irgendeinen Report
erstellt und irgendwo im Report steht als Quellangabe der Daten
`hostname`
oder $(hostname)
. Ihr schreibt einen Test, der einen
frisch erstellten Report mit einer vorherigen Ausgabe vergleicht.
Das ganze läuft auf eurem eigenen Rechner prima, aber auf dem
zentralen Buildserver in der CI-Pipeline schlägt der Test fehl,
weil hostname
dort ein anderes Ergebnis liefert.
Was tun?
Funktion als Mock
Wenn man hostname
dazu kriegt, einen gewünschten Wert auszugeben,
hat man gewonnen. Natürlich kann man nicht einfach den echten Namen
des Systems nur für den Testdurchlauf verstellen. Stattdessen müssen
wir dem Test irgendwie ein gefälschtes hostname
-Kommando
unterjubeln, das genau das ausgibt, was wir uns wünschen. (Das nennt
man dann ein Mock[1].)
Man könnte ein kleines Shellskript schreiben, hostname
nennen und
irgendwo in den $PATH
vor das echte hostname
-Kommando schieben
(wofür man vermutlich erstmal den $PATH
anpassen muss). Bei
mehreren zu mockenden Kommandos wird das schnell ein kleiner Zoo aus
Mini-Shellskripten. Das kann man so machen, ist mir aber noch zu
umständlich.[2]
Statt eines Shellskripts kann man sich eine Shellfunktion gleichen Namens bauen, die dann exportiert wird, z.B. so:
- hostname()
- {
- echo 'hostname.invalid'
- }
- export -f hostname
Sofern das getestete Skript dann ebenfalls ein Shellskript ist,
übersteuert die definierte Funktion das originale hostname
, egal wo
es im Pfad liegt.
Anwendungen
Wenn man so eine einfache Möglichkeit des Mockings hat, kann man sie vielfältig nutzen:
Ein naheliegender Kandidat ist date
, denn Datum und Uhrzeit sind in
Tests immer fies. Klassischerweise kann man die Datumsermittlung der
glibc per $LD_PRELOAD
übersteuern (z.B. per faketime
). Bei
kompilierten Programmen ist das auch unerlässlich – bei zu testenden
Shellskripten dürfte der Weg über eine Funktion (oder $PATH
und ein
Skript) aber deutlich einfacher und vor allem portabler sein.
Wenn man im Test unterschiedliche Zeiten haben will, kann man innerhalb der Funktion eine Variable hochzählen oder die gewünschte Zeit aus einer durch den Test steuerbaren Variable auslesen.[3]
(Ich merke beim Schreiben dieser Zeilen gerade: Die bash
kann Datum
und Uhrzeit über printf %(fmt)T
recht flexibel ausgeben, das
Kommando ist intern und somit vermutlich schneller ausgeführt als das
externe Programm date
. Bisher war deshalb das eingebaute printf
mein Favorit, aber wenn ich im Test flexibel sein will, sollte ich
lieber date
nutzen.)
Eine andere Variante ist, sich das Aufbauen umfangreicher Testdaten zu
ersparen. Wenn man beispielsweise ein Skript hat, das die Ausgabe von
du
parst und weiterverarbeitet, dann kann man sich für den Test ein
Verzeichnis mit Testdateien anlegen und darin das echte du
aufrufen.
Wenn man allerdings Konstellationen mit 16.000 Dateien und 15 GB
Gesamtgröße durchspielen will, sprengt das etwas den Rahmen eines
Tests – der Buildserver wird sich bedanken. Man kann stattdessen du
durch eine Shellfunktion ersetzen, die einfach den gewünschten
Dateibaum ausgibt, ohne dass er tatsächlich da ist.[4]
Fazit
Exportierte Shell-Funktionen bieten sich an, um externe Kommandos innerhalb von Tests mit gemockter Funktionalität zu überschreiben.
Ich bin bestimmt nicht der erste, der auf diese Idee gekommen ist, aber sie ist mir immerhin selber eingefallen :) Ich finde das recht elegant und praktisch und habe es auch schon praktisch angewandt.
$PATH
, um ein gemocktes ps
mit festen
Testdaten aufzurufen.du | auswertungsskript
), dann ist es
flexibel nutzbar und Testdaten lassen sich einfach aus einer Datei
lesen (auswertungsskript < testdaten
), aber das kann in der Nutzung
umständlicher werden und man kann in dieser Konstellation das Skript
keine Parameter mehr an du
übergeben lassen.Nun könnte man einfach noch ein Skript außenrum machen, das dann nur die Pipe und die Optionen der Befehle kapselt, dann kann man das innere Skript ganz einfach testen und das außere ist so minimal, dass man den Test dann ganz sausen lassen kann.
Aber dann hat man wieder zwei Skripte, die man immer zusammen ausliefern muss …
Da kann man sich lange dran austoben. Ich schweife ab!
Netz - Rettung - Recht am : Wellenreiten 03/2020