REST-API-Test mit Gurke
1. Überblick
Dieses Tutorial enthält eine Einführung inCucumber, ein häufig verwendetes Tool zum Testen der Benutzerakzeptanz, und dessen Verwendung in REST-API-Tests.
Darüber hinaus verwenden wir WireMock, eine Stubbing- und Mocking-Webservice-Bibliothek, um den Artikel eigenständig und unabhängig von externen REST-Diensten zu machen. Wenn Sie mehr über diese Bibliothek erfahren möchten, lesen Sie bitteintroduction to WireMock.
2. Gurke - Die Sprache der Gurke
Cucumber ist ein Testframework, das Behaviour Driven Development (BDD) unterstützt und es Benutzern ermöglicht, Anwendungsvorgänge im Klartext zu definieren. Es funktioniert basierend aufGherkin Domain Specific Language (DSL). Diese einfache, aber leistungsstarke Syntax von Gherkin ermöglicht es Entwicklern und Testern, komplexe Tests zu schreiben und diese auch für nicht technische Benutzer verständlich zu halten.
2.1. Einführung in Gurke
Gurke ist eine zeilenorientierte Sprache, die zur Definition von Dokumenten Zeilenenden, Einrückungen und Schlüsselwörter verwendet. Jede nicht leere Zeile beginnt normalerweise mit einem Gherkin-Schlüsselwort, gefolgt von einem beliebigen Text, bei dem es sich normalerweise um eine Beschreibung des Schlüsselworts handelt.
Die gesamte Struktur muss in eine Datei mit der Erweiterungfeaturegeschrieben werden, um von Cucumber erkannt zu werden.
Hier ist ein einfaches Beispiel für ein Gurkendokument:
Feature: A short description of the desired functionality
Scenario: A business situation
Given a precondition
And another precondition
When an event happens
And another event happens too
Then a testable outcome is achieved
And something else is also completed
In den folgenden Unterabschnitten werden einige der wichtigsten Elemente einer Gurkenstruktur beschrieben.
2.2. Feature
Eine Gurkendatei wird verwendet, um eine Anwendungsfunktion zu beschreiben, die getestet werden muss. Die Datei enthält ganz am Anfang das SchlüsselwortFeature, gefolgt vom Feature-Namen in derselben Zeile und einer optionalen Beschreibung, die mehrere Zeilen darunter umfassen kann.
Der gesamte Text mit Ausnahme des SchlüsselwortsFeaturewird vom Cucumber-Parser übersprungen und nur zu Dokumentationszwecken eingefügt.
2.3. Szenarien und Schritte
Eine Gurkenstruktur kann aus einem oder mehreren Szenarien bestehen, die durch das SchlüsselwortScenarioerkannt werden. Ein Szenario ist im Grunde ein Test, mit dem Benutzer eine Funktion der Anwendung überprüfen können. Es sollte einen anfänglichen Kontext beschreiben, Ereignisse, die auftreten können, und erwartete Ergebnisse, die durch diese Ereignisse hervorgerufen werden.
Diese Dinge werden in Schritten ausgeführt, die durch eines der fünf Schlüsselwörter gekennzeichnet sind:Given,When,Then,And undBut.
-
Given: In diesem Schritt wird das System in einen genau definierten Zustand versetzt, bevor Benutzer mit der Anwendung interagieren. EineGiven-Klausel kann als Voraussetzung für den Anwendungsfall angesehen werden.
-
When: EinWhen-Schritt wird verwendet, um ein Ereignis zu beschreiben, das mit der Anwendung passiert. Dies kann eine Aktion sein, die von Benutzern ausgeführt wird, oder ein Ereignis, das von einem anderen System ausgelöst wird.
-
Then: In diesem Schritt wird ein erwartetes Testergebnis angegeben. Das Ergebnis sollte sich auf die Geschäftswerte des zu testenden Features beziehen.
-
And undBut: Diese Schlüsselwörter können verwendet werden, um die obigen Schrittschlüsselwörter zu ersetzen, wenn mehrere Schritte desselben Typs vorhanden sind.
Cucumber unterscheidet diese Schlüsselwörter nicht wirklich, sie sind jedoch immer noch vorhanden, um die Funktion lesbarer und mit der BDD-Struktur konsistenter zu machen.
3. Cucumber-JVM-Implementierung
Cucumber wurde ursprünglich in Ruby geschrieben und mit der Cucumber-JVM-Implementierung, die Gegenstand dieses Abschnitts ist, nach Java portiert.
3.1. Maven-Abhängigkeiten
Um Cucumber-JVM in einem Maven-Projekt verwenden zu können, muss die folgende Abhängigkeit im POM enthalten sein:
info.cukes
cucumber-java
1.2.4
test
Um das Testen von JUnit mit Cucumber zu vereinfachen, benötigen wir eine weitere Abhängigkeit:
info.cukes
cucumber-junit
1.2.4
Alternativ können wir ein anderes Artefakt verwenden, um Lambda-Ausdrücke in Java 8 zu nutzen, die in diesem Lernprogramm nicht behandelt werden.
3.2. Schrittdefinitionen
Gurkenszenarien wären nutzlos, wenn sie nicht in Aktionen übersetzt würden, und hier kommen Schrittdefinitionen ins Spiel. Grundsätzlich ist eine Schrittdefinition eine mit Anmerkungen versehene Java-Methode mit einem angehängten Muster, deren Aufgabe es ist, Gherkin-Schritte in Klartext in ausführbaren Code umzuwandeln. Nach dem Parsen eines Feature-Dokuments sucht Cucumber nach Schrittdefinitionen, die den auszuführenden vordefinierten Gherkin-Schritten entsprechen.
Schauen wir uns zur Verdeutlichung den folgenden Schritt an:
Given I have registered a course in example
Und eine Schrittdefinition:
@Given("I have registered a course in example")
public void verifyAccount() {
// method implementation
}
Wenn Cucumber den angegebenen Schritt liest, sucht es nach Schrittdefinitionen, deren Beschriftungsmuster mit dem Gurkentext übereinstimmen. In unserer Abbildung wird festgestellt, dass die MethodetestMethodübereinstimmt, und der Code wird dann ausgeführt, sodass die ZeichenfolgeLet me in!auf der Konsole ausgedruckt wird.
4. Erstellen und Ausführen von Tests
4.1. Schreiben einer Feature-Datei
Beginnen wir mit der Deklaration von Szenarien und Schritten in einer Datei, deren Name auf der Erweiterung.featureendet:
Feature: Testing a REST API
Users should be able to submit GET and POST requests to a web service,
represented by WireMock
Scenario: Data Upload to a web service
When users upload data on a project
Then the server should handle it and return a success status
Scenario: Data retrieval from a web service
When users want to get information on the Cucumber project
Then the requested data is returned
Wir speichern diese Datei jetzt in einem Verzeichnis mit dem NamenFeature, unter der Bedingung, dass das Verzeichnis zur Laufzeit in den Klassenpfad geladen wird, z. src/main/resources.
4.2. JUnit für die Arbeit mit Gurken konfigurieren
Damit JUnit Cucumber kennt und Feature-Dateien beim Ausführen liest, muss die KlasseCucumberalsRunnerdeklariert werden. Wir müssen JUnit auch mitteilen, wo nach Feature-Dateien und Schrittdefinitionen gesucht werden soll.
@RunWith(Cucumber.class)
@CucumberOptions(features = "classpath:Feature")
public class CucumberTest {
}
Wie Sie sehen können, findet dasfeatures-Element vonCucumberOption die zuvor erstellte Feature-Datei. Ein weiteres wichtiges Element,glue genannt, bietet Pfade zu Schrittdefinitionen. Befinden sich die Testfall- und Schrittdefinitionen jedoch im selben Paket wie in diesem Lernprogramm, wird dieses Element möglicherweise gelöscht.
4.3. Schrittdefinitionen schreiben
Wenn Cucumber Schritte analysiert, sucht es nach Methoden, die mit Gherkin-Schlüsselwörtern versehen sind, um die entsprechenden Schrittdefinitionen zu finden. In diesem Lernprogramm werden diese Schrittdefinitionen in einer Klasse innerhalb desselben Pakets mitCucumberTest definiert.
Das Folgende ist eine Methode, die einem Gherkin-Schritt vollständig entspricht. Die Methode wird verwendet, um Daten an einen REST-Webdienst zu senden:
@When("^users upload data on a project$")
public void usersUploadDataOnAProject() throws IOException {
}
Und hier ist eine Methode, die einem Gherkin-Schritt entspricht und ein Argument aus dem Text entnimmt, mit dem Informationen von einem REST-Webdienst abgerufen werden:
@When("^users want to get information on the (.+) project$")
public void usersGetInformationOnAProject(String projectName) throws IOException {
}
Wie Sie sehen können, verwendet die MethodeusersGetInformationOnAProjectdas ArgumentString, bei dem es sich um den Projektnamen handelt. Dieses Argument wird in der Annotation durch(.+) deklariert und entspricht hierCucumber im Schritttext.
Der Arbeitscode für beide oben genannten Methoden wird im nächsten Abschnitt bereitgestellt.
4.4. Erstellen und Ausführen von Tests
Zunächst werden wir mit einer JSON-Struktur beginnen, um die Daten zu veranschaulichen, die durch eine POST-Anforderung auf den Server hochgeladen und mit einem GET auf den Client heruntergeladen wurden. Diese Struktur wird im FeldjsonString gespeichert und unten angezeigt:
{
"testing-framework": "cucumber",
"supported-language":
[
"Ruby",
"Java",
"Javascript",
"PHP",
"Python",
"C++"
],
"website": "cucumber.io"
}
Zur Demonstration einer REST-API kommt ein WireMock-Server ins Spiel:
WireMockServer wireMockServer = new WireMockServer(options().dynamicPort());
Darüber hinaus wird in diesem Lernprogramm die Apache HttpClient-API verwendet, um den Client darzustellen, der zum Herstellen einer Verbindung zum Server verwendet wird:
CloseableHttpClient httpClient = HttpClients.createDefault();
Fahren wir nun mit dem Schreiben von Testcode in Schrittdefinitionen fort. Wir werden dies zuerst für dieusersUploadDataOnAProject-Methode tun.
Der Server sollte ausgeführt werden, bevor der Client eine Verbindung herstellt:
wireMockServer.start();
Verwenden der WireMock-API zum Stub des REST-Service:
configureFor("localhost", wireMockServer.port());
stubFor(post(urlEqualTo("/create"))
.withHeader("content-type", equalTo("application/json"))
.withRequestBody(containing("testing-framework"))
.willReturn(aResponse().withStatus(200)));
Senden Sie nun eine POST-Anfrage mit dem Inhalt aus dem oben deklarierten FeldjsonStringan den Server:
HttpPost request = new HttpPost("http://localhost:" + wireMockServer.port() + "/create");
StringEntity entity = new StringEntity(jsonString);
request.addHeader("content-type", "application/json");
request.setEntity(entity);
HttpResponse response = httpClient.execute(request);
Mit dem folgenden Code wird bestätigt, dass die POST-Anforderung erfolgreich empfangen und verarbeitet wurde:
assertEquals(200, response.getStatusLine().getStatusCode());
verify(postRequestedFor(urlEqualTo("/create"))
.withHeader("content-type", equalTo("application/json")));
Der Server sollte nach der Verwendung anhalten:
wireMockServer.stop();
Die zweite Methode, die wir hier implementieren werden, istusersGetInformationOnAProject(String projectName). Ähnlich wie beim ersten Test müssen wir den Server starten und dann den REST-Service beenden:
wireMockServer.start();
configureFor("localhost", wireMockServer.port());
stubFor(get(urlEqualTo("/projects/cucumber"))
.withHeader("accept", equalTo("application/json"))
.willReturn(aResponse().withBody(jsonString)));
Senden einer GET-Anfrage und Empfangen einer Antwort:
HttpGet request = new HttpGet("http://localhost:" + wireMockServer.port() + "/projects/" + projectName.toLowerCase());
request.addHeader("accept", "application/json");
HttpResponse httpResponse = httpClient.execute(request);
Wir werden die VariablehttpResponse mit einer Hilfsmethode inString konvertieren:
String responseString = convertResponseToString(httpResponse);
Hier ist die Implementierung dieser Konvertierungshilfemethode:
private String convertResponseToString(HttpResponse response) throws IOException {
InputStream responseStream = response.getEntity().getContent();
Scanner scanner = new Scanner(responseStream, "UTF-8");
String responseString = scanner.useDelimiter("\\Z").next();
scanner.close();
return responseString;
}
Folgendes überprüft den gesamten Prozess:
assertThat(responseString, containsString("\"testing-framework\": \"cucumber\""));
assertThat(responseString, containsString("\"website\": \"cucumber.io\""));
verify(getRequestedFor(urlEqualTo("/projects/cucumber"))
.withHeader("accept", equalTo("application/json")));
Beenden Sie abschließend den Server wie zuvor beschrieben.
5. Funktionen parallel ausführen
Manchmal müssen wir die Funktionen parallel ausführen, um den Testprozess zu beschleunigen.
We can use the cucumber-jvm-parallel-plugin to create a separate runner for each feature/scenario. Dann konfigurieren wirmaven-failsafe-plugin so, dass die resultierenden Läufer parallel ausgeführt werden.
Zuerst müssen wircucumber-jvm-parallel-plugin zu unserenpom.xmlhinzufügen:
com.github.temyers
cucumber-jvm-parallel-plugin
5.0.0
generateRunners
generate-test-sources
generateRunners
com.example.rest.cucumber
src/test/resources/Feature/
SCENARIO
Wir können diecucumber-jvm-parallel-plugin leicht anpassen, da sie mehrere Parameter haben. Hier sind die, die wir benutzt haben:
-
glue.package: (obligatorisch) unser Integrationstestpaket
-
featuresDirectory: Der Pfad zum Verzeichnis enthält unsere Feature-Dateien
-
parallelScheme: kann entweder SCENARIO oder FEATURE sein, wobei SCENARIO einen Läufer pro Szenario und FEATURE einen Läufer pro Feature generiert
Jetzt werden wir auchconfigure the maven-failsafe-plugin to execute resulting runners in parallel:
maven-failsafe-plugin
2.19.1
classes
2
integration-test
verify
Beachten Sie, dass:
-
parallel: kannclasses, methods oder beides sein - in unserem Fall lässtclasses jede Testklasse in einem separaten Thread laufen
-
threadCount: gibt an, wie viele Threads für diese Ausführung zugewiesen werden sollen
Dann können wir zum Ausführen der Tests den folgenden Befehl verwenden:
mvn verify
Wir werden feststellen, dass jedes Szenario in einem separaten Thread ausgeführt wird.
6. Fazit
In diesem Lernprogramm wurden die Grundlagen von Cucumber und die Verwendung der domänenspezifischen Sprache von Gherkin zum Testen einer REST-API erläutert.
Die Implementierung all dieser Beispiele und Codefragmente finden Sie ina GitHub project.