https://pixabay.com/de/grafitti-d%C3%BCsseldorf-kiefernstra%C3%9Fe-3533499/

25. January 2019 / von Alexander Weisel

Wenn der 

Postmann

an der API 

klingelt

Schlanke und automatisierbare REST-API-Tests mit Postman und Newman

Was es ist

Entwickelt man ein Client-Server-System, das über eine REST-API kommuniziert, ist es sinnvoll, die (serverseitige) API losgelöst vom Client zu testen. Mit den Entwicklungstools moderner Browser geht das ganz gut. Sollen die Tests aber automatisiert von einem CI-System nach jedem Build ausgeführt werden, stößt die Browserlösung an ihre Grenzen. Eine Lösung wäre, dass man aus wget- und grep-Befehlen API-Tests baut. Das funktioniert zwar, hat aber Nachteile: Einerseits ist dann nicht sichergestellt, dass die Entwickler die gleichen Tests wie das CI-System nutzen (Entwicklertools vs. Shellskripte) und je nach Organisation der Testentwickler hat man im schlimmsten Fall ein monolithisches, blob-artiges Shellskript, das eine halbe Stunde durchläuft und keine Einzeltests ermöglicht. Solche Konstrukte neigen dazu, nicht weiterentwickelt und nicht genutzt zu werden. In diese Lücke möchte Postman springen, mit dem Anspruch intuitiver nutzbar zu sein als Browsertools und dazu auch noch automatisierbar. Wird es diesen Ansprüchen gerecht? Ein kleiner Durchstich soll in diesem Artikel Antworten liefern.

Postman gibt es als native Anwendung für Windows, Linux und Mac. Es ist ein kommerzielles Produkt und in der Basisversion kostenlos. Im Folgenden werden wir ausschließlich die Basisversion nutzen. Nach der Installation ist bereits ein Beispiel-Set importiert, die Postman-Echo-Tests. Mit ihrer Hilfe lassen sich typische REST-API-Anfragen durchführen und das „Echo“ sind verschiedene Statuscodes und Antworten. Das Tutorial auf der Herstellerseite ist in jedem Fall lesenswert.

Womit wir es tun

In diesem Artikel möchten wir aber etwas anderes ausprobieren, nämlich die Fahrplan-API der DB Vertrieb GmbH. Mit ihrer Hilfe kann man sich Abfahrtspläne verschiedener Bahnhöfe und Zugverläufe anzeigen lassen. Sie ist unter folgender Adresse zu finden:

https://developer.deutschebahn.com/store/apis/info?name=Fahrplan-Free&version=v1&provider=DBOpenData

Aus einem Bahnhofsnamen lässt sich eine ID generieren, die anschließend in einen Abfahrtsfahrplan zu einer bestimmten Zeit eingetauscht werden kann. Diesen Testschritt werden wir durchführen und später automatisierbar machen.

Der erste Aufruf, mit dem ein Bahnhofsname auf eine ID gemappt wird, ist (für Darmstadt) unter

https://api.deutschebahn.com/freeplan/v1/location/Darmstadt

zu erreichen. Ruft man den Link im Browser auf, erhält man eine Liste im JSON-Format, die eine Reihe von Bahnhöfen enthält, die die DB-API für passend hält. Immerhin ist der erste Eintrag Darmstadt.

Wie man anfängt

Wie erreicht man diesen Aufruf aus Postman? Nach der Installation von Postman und dem ersten Öffnen überspringen wir zunächst die Anmeldung oder Erstellung eines Accounts (ganz unten im Hauptfenster gibt es die Option „Skip signing in and take me straight to the app“). Im Assistenten wählen wir „Request“, da wir als Basis einen GET-Request nutzen wollen.

Assistent
Abbildung 2: Der Assistent unterstützt die Erstellung unseres ersten Tests, hier wird „Request“ gewählt.

Im folgenden Fenster vergeben wir den Namen „Location“ und erstellen eine neue Collection namens „DB API“ und wählen sie aus. Erst wenn der Button „Save to DB API“ anzeigt wird, gelangt man weiter. Die Anfrage kann nun im Hauptfenster bearbeitet werden.

Die URL
https://api.deutschebahn.com/freeplan/v1/location/Darmstadt
fügen wir in das Feld „Enter request URL“ ein, der Rest bleibt erst mal unverändert. Nach einem Klick auf „Send“ sollte ungefähr die gleiche Liste wie im Browser erscheinen. Schön, aber noch kein Mehrwert.

Hier wird lediglich der Rückgabewert angezeigt, Ziel ist jedoch das Testen der API. Ob der Statuscode gleich 200 ist, ist ein sinnvoller Test einer REST-API. Ist das nicht der Fall, liegt ein Fehler vor. Einigermaßen schnell soll die Antwort auch kommen, gut wäre unter 200ms. Für beide Fälle hat Postman schon etwas vorbereitet. Im oberen Tab „Tests“ (dort wo aktuell „Authorization“ ausgewählt ist) können Tests in JavaScript notiert werden. Rechts in der Liste befinden sich Vorlagen. Ein Klick auf „Status code: Code is 200“ und „Response time is less than 200ms“ füllt den Editor mit dem Code und ein erneuter Klick auf „Send“ sollte nun zwei erfolgreiche Tests vermelden (je nach Auslastung des Bahnservers). Stellt man fest, dass die Antwortzeit länger ist, kann man den Wert erhöhen, beispielsweise auf 1000ms.

Location erster Wurf
Abbildung 4 Nach Eingabe der URL und Betätigen von „Send“ ist das Ergebnis im unteren Fenster analog zum Browser sichtbar
Location erste Tests
Abbildung 5 Erste einfache Tests sind in Postman bereits hinterlegt und müssen nur ausgewählt werden.

Wie man es intelligenter macht

Nun wollen wir prüfen, ob die Bahn-API immer der Meinung ist, dass unsere Suche nach dem Darmstädter Bahnhof tatsächlich zuerst einen „Darmstadt Hbf“ zurück liefert. Die Vorlage „Response body: JSON value check“ klingt dafür vielversprechend:

pm.test("Your test name", function () {
    var jsonData = pm.response.json();
    pm.expect(jsonData.value).to.eql(100);
});

Einmaliges Ausführen offenbart, dass der neue Test „Your test name“ fehlschlägt, weil etwas nicht 100 sei. Postman versucht im erhaltenen JSON das Feld value zu finden und zu vergleichen. Unser Test muss natürlich folgendermaßen lauten (und lesbarer benannt ist er auch):

pm.test("Erster Eintrag ist Darmstadt Hbf", function () {
    var jsonData = pm.response.json();
    pm.expect(jsonData[0].name).to.eql("Darmstadt Hbf");
});

Wird der Request erneut gesendet, ist unser Test erfolgreich. Die nächste API-Funktion, die wir nutzen möchten, ist die Darstellung des aktuellen Abfahrzeitplans. Die URL

https://api.deutschebahn.com/freeplan/v1/arrivalBoard/

in Verbindung mit der eben ermittelten ID führt uns hierbei zum Ziel. Ein Klick auf die orangene Schaltfläche „New“ mit dem Plus oben links lässt den bekannten Assistenten erscheinen. Als Name vergeben wir dieses Mal „Arrival Board“, als Collection die zuvor angelegte.

Fügt man die entsprechende URL

https://api.deutschebahn.com/freeplan/v1/arrivalBoard/8000068

ein und ruft sie ab, erhält man jedoch lediglich die Fehlermeldung, dass Datum und Zeit „invalide“ seien. Der Blick in die API-Dokumentation verrät, warum. Es ist notwendig, einen Parameter date an die URL anzuhängen, der die Zeitangabe enthält. Links des „Send“-Buttons in Postman existiert ein Button „Params“, der die Eingabe und Verwaltung von URL-Parametern vereinfacht.  Dort erzeugen wir einen Eintrag namens date, der den Wert 2019-04-01 enthält. Postman erzeugt automatisch die passende URL, nach deren Aufruf man eine Liste der abfahrenden Züge erhält:

https://api.deutschebahn.com/freeplan/v1/arrivalBoard/8000068

Wie man es automatisch macht

Wenn der Test automatisiert und mit verschiedenen Bahnhöfen durchgeführt werden soll, ist es unabdingbar, dass die ausgelesene ID automatisch in den nächsten Schritt übernommen wird. Postman nutzt dazu globale Variablen. Global deshalb, da sie über Testschritte hinweg konstant sind. Gesetzt wird sie in dem Test zur Bahnhofs-ID-Abfrage, dort fügen wir die Vorlage „Set a global variable“ ein. Die zu schreibende ID zu erhalten ist etwas trickreich: die Variable jsonData, die in der Bahnhofsnamens-Test-Funktion geschrieben wurde, liegt außerhalb des Scopes. Hier parsen wir das Beispiel erneut, um alles in einer Zeile aufzuschreiben (für größere Tests würde man die Variable einmal zu Beginn der Tests erzeugen).

pm.globals.set("bahnhof_id", pm.response.json()[0].id);

Nach dem Ausführen des Tests kann das Ergebnis im Menü des „Auge-Buttons“ eingesehen werden: Die Variable wurde angelegt und mit der ID befüllt. Die Nutzung der Variable gestaltet sich noch einfacher: Sie wird in die URL mittels doppelter geschweifter Klammer eingerahmt, was im Ergebnis so aussieht:

https://api.deutschebahn.com/freeplan/v1/arrivalBoard/{{bahnhof_id}}?date=2019-04-01

Führt man den Test mit einem anderen Bahnhofsnamen und anschließend die Abfrage des Abfahrtsplans aus, sieht man, dass sich die gewünschte Wirkung eingestellt hat. Es bleibt jedem selbst überlassen, die inhaltliche Richtigkeit des Abfahrtsplans (schließlich ändert er sich laufend) zu testen. Hier sei die Prüfung zunächst auf den Statuscode 200 beschränkt.

Abbildung 6 Globale Variablen zeigt Postman an, wenn man den „Auge-Button“ anklickt.
Abbildung 7: Postman ist in der Lage, URL-Parameter selbständig zu kodieren und anzuhängen.

Wie man es robust macht

Fügt man statt der korrekten ID eine offensichtlich falsche ID ein (so etwas wie „aaaa“), bemerkt man, dass die DB-API trotzdem eine Liste von abfahrenden Zügen zurück gibt. Eine Prüfung auf ein gefülltes Rückgabearray ist also nicht zielführend. Da scheinbar eine Bahnhofszuordnung vorgenommen wird (immerhin sind alle Ausgangsbahnhöfe gleich), kann man prüfen, ob der erste Ausgangsbahnhof in der Liste mit dem gewünschten Ergebnis übereinstimmt. Für Darmstadt ergibt sich folgender Test:

pm.test("Bahnhofsname in Stopp identisch", function () {
    var jsonData = pm.response.json();
    pm.expect(jsonData[0].stopName).to.eql("Darmstadt Hbf");
});

Wie auch vorher gilt hier: Keine hinreichende, aber notwendige Bedingung für eine korrekte Funktion. Ergänzt wird dieser Test wiederum durch den obligatorischen „Statuscode 200“-Test.

Wie man es ferngesteuert macht

Für die direkte Ausführung und Ergebnisinterpretation ist die Bedienung über die Postman-Oberfläche hilfreich, allerdings lassen sich Tests damit nicht automatisieren.

Für diesen Zweck eignet sich der kleine Bruder von Postman, genannt Newman. Er dient lediglich zur Ausführung der Tests, kann über die Kommandozeile angesteuert werden und führt dann die Tests einer oder mehrerer Collections aus. Standardmäßig gibt er die Ergebnisse ebenfalls in der Kommandozeile aus, er kann jedoch die Ergebnisse in verschiedenen Formaten in Dateien exportieren. Über seinen Exit-Code zeigt er an, ob die Tests erfolgreich waren oder nicht. Das wird von vielen Build-Servern genutzt um anzuzeigen, dass ein Build die Tests nicht bestanden hat.

Da wir nun mit zwei Programmen arbeiten, müssen die Tests von Postman in Newman übertragen werden. Der Export der Tests erfolgt pro Collection, die man sich hier wie eine Art Ordner vorstellen kann. Im Gegensatz zu einem Ordner ist jedoch die Reihenfolge der Ausführung festgelegt und wird sowohl von Postman als auch von Newman beachtet.

Für den Export aus Postman den Knopf mit den drei Punkten neben einer Collection anklicken und „Export“ wählen. Als Versionsnummer ist idealerweise die höchste zu nutzen. Das Ergebnis ist eine JSON-Datei, die an einem beliebigen Ort gespeichert werden kann. Unsere wird der Einfachheit halber „collection.json“ heißen.

Newman selbst muss mittels npm installiert werden, dem Node.js-Package-Manager. Dieser Weg sei hier für Windows gezeigt. Node.js von der Homepage herunterladen und installieren.

Zur eigentlichen Installation von Newman muss der „Node.js command prompt“ genutzt werden, der sich im Startmenüeintrag von Node.js befindet. In der Konsole das Kommando npm install newman --global ausführen, so wie auf der Homepage von Newman beschrieben. Im Idealfall wird angezeigt, dass mehrere hundert Pakete installiert wurden. Ein Aufruf des Kommandos newman -v sollte nun die Versionsnummer des installierten Newman anzeigen.

 

Wie man es komplett macht

Zur Abarbeitung der Tests in einer Collection bewegt man Newman, indem man ihn mit folgendem Kommando startet:

newman run collection.json

Das gilt natürlich nur unter der Bedingung, dass die Datei „collection.json“ auch im aktuellen Arbeitspfad der Konsole liegt. Als Ergebnis sollten alle Tests abgearbeitet sowie eine Ergebnistabelle angezeigt werden, die keine Einträge in der Spalte „Failed“ hat. Schlagen Tests fehl, sollte man auch die Sortierung der Testfälle überprüfen. Die Reihenfolge ist identisch mit der Anordnung in Postman. Das kann zu Problemen führen, wenn Tests Variablen anlegen, die andere Tests benötigen, so wie in unserem Fall der Bahnhofsname auf die ID gemappt wird, die wiederum vom Fahrplan benötigt wird. Die Reihenfolge innerhalb der Collections kann per Drag & Drop geändert werden. Nach einer solchen Änderung muss der Export natürlich erneut ausgeführt werden.

Wie man es flexibel macht

Die Tests sind aktuell sehr starr und hartkodierte Bahnhofsnamen lassen bei jedem Entwickler zurecht Gänsehaut entstehen. Praktischer ist es, sie von außen (also beispielsweise über die Kommandozeile) mitzugeben. Newman bietet dafür die Möglichkeit, globale Variablen festzulegen, die dann in den Testabläufen zur Verfügung stehen. Als Vorbereitung muss jedoch der starre Aufruf des Bahnhofsnamens durch eine Variable ersetzt werden, sodass die Ansteuerung der Location-API sich zu folgendem ergibt:

https://api.deutschebahn.com/freeplan/v1/location/{{location_name}}

Damit die Prüfung auf den Bahnhofsnamen ebenfalls noch funktioniert, müssen auch die Tests angepasst werden. Die Information, wie man eine Variable auslesen kann, erhält man durch Einfügen des Snippets „Get a variable“.

pm.test("Erster Eintrag ist " + pm.variables.get("location_name"), function () {
    var jsonData = pm.response.json();
    pm.expect(jsonData[0].name).to.eql(pm.variables.get("location_name"));
});

Der Test im Abfahrtsfahrplan muss natürlich ebenfalls angepasst werden.

pm.test("Bahnhofsname in Stopp identisch", function () {
    var jsonData = pm.response.json();
    pm.expect(jsonData[0].stopName).to.eql(pm.variables.get("location_name"));
});

Die Variableninjektion lässt sich folgendermaßen nutzen:

newman run collection.json --global-var "location_name=Bonn Hbf"

Dieser sollte nun durchlaufen und anzeigen, dass alles erfolgreich ist. (Abb. 11)

Gewissheit, dass der Test korrekt funktioniert, liefert die Gegenprobe (Abb. 12):

newman run collection.json --global-var "location_name=Umpfelmumpf Nord"

Dieser nichtexistente Bahnhof führt dazu, dass der Test fehlschlägt. Die DB-API liefert stattdessen klaglos einen anderen Bahnhof, aktuell „Norddeich“.

Über seinen Exit-Code zeigt Newman an, ob ein Testdurchlauf erfolgreich war oder Fehler brachte. Jenkins beispielsweise zeigt somit an, ob ein Buildschritt erfolgreich war oder nicht. Andere Buildsysteme, wie der Team-Foundation-Server von Microsoft, gehen jedoch bei einem Exit-Code ungleich 0 davon aus, dass der Testdurchlauf selbst nicht abgeschlossen werden konnte. Mittels der Kommandozeilenoption --suppress-exit-code lässt sich Newman dazu bewegen, auch bei fehlgeschlagenen Testfällen 0 zurück zu geben. Einen anderen Wert liefert er nur zurück, wenn die Tests nicht abgearbeitet werden konnten, weil beispielsweise die Collection nicht verfügbar ist.

Was man daraus lernt

Postman hält tatsächlich viele Versprechen ein. Zusammen mit Newman erhält man eine vielseitige, aber einfache Testsuite, die der natürlichen Evolution von Tests in der Entwicklung folgt: Von starren, einfachen Tests, die zunächst gegen hartkodierte Werte prüfen, mutieren sie später zu dynamischen Varianten, die auch aus CSVs gespeiste Lasttests mühelos bewältigen.

Die Bedienung ist so intuitiv, dass tatsächlich jeder, der bereits ein Entwicklertool im Browser genutzt hat, auf Anhieb mit Postman zurechtkommt. Die vorgefertigten Test-Snippets lassen einen im Handumdrehen Testfälle zusammenstellen, die relativ genau das Scheitern eines Testablaufs erklären können. Das Ausführen von der Kommandozeile wirkt dazu schon eher als logische Konsequenz statt zusätzliches Feature. Durch die niedrige Hürde können Entwickler auch lokal einzelne Tests anstarten und ihre Änderungen sofort überprüfen.

Doch wo Licht ist, ist bekanntermaßen auch Schatten: In der kostenlosen Version lässt sich nicht vernünftig in Teams arbeiten. Im allgemeinen, versionierbaren Format kommen die Daten nur per Export, der jedoch immer manuell angesteuert werden muss. Einige versuchten, den Datenhaltungsordner von Postman in eine Versionsverwaltung (wie git) zu packen, davon ist aber im Allgemeinen abzuraten – zu groß ist die Gefahr, dass die automatischen Merges unbrauchbare Konfigurationen erstellen. Der Hersteller bietet in seiner kostenpflichtigen Version (ab 8 Dollar pro Monat und Nutzer) genau dieses Feature, Tests über Teams synchron zu halten. Das ist nur legitim, schließlich ist es ein kommerzielles Produkt.

Durch die großen Freiräume in der Testgestaltung mittels JavaScript muss man sich in einem Team auf jeden Fall gemeinsame Standards für Tests überlegen, ansonsten steigt die Varianz. In einem unserer Projekte fügten einige Entwickler zu jedem Test eine Überprüfung des Statuscodes, der Antwortzeit sowie der Wohlgeformtheit der Inhalte hinzu, während der Rest verzichtete. Ein Gleichziehen der Tests zu einem späteren Zeitpunkt kostet unnötig Zeit, daher sollten ein Styleguide für Tests direkt mit der Architektur entworfen werden.

Alles in allem ist und bleibt Postman ein sehr gutes Tool. Ob sich die Teamfunktionen so verhalten, wie man es aufgrund des restlichen Produkts vermuten kann, muss sich zeigen. Postman bietet darüber hinaus weitere Funktionen, wie einen lokalen API-Mockup (sowie das gleiche in der Cloud), um die Entwicklung von Clients zu unterstützen. Viele weitere Features, die wir hier noch nicht untersuchen konnten.

Autor

Weitere Artikel

Das könnte Sie auch interessieren