Karate vs. REST-Assured: Automatisierte API-Tests mit Java

Das Testen von APIs ist eine kritische Phase im QA-Prozess, die die Analyse von Anwendungsprogrammschnittstellen (APIs) umfasst, um deren Funktionalität, Sicherheit, Leistung und Zuverlässigkeit zu bewerten.

Insbesondere bei komplexen Anwendungen kann die API aus Dutzenden von Endpunkten und Auslösern bestehen, die sich gegenseitig beeinflussen. Um zu verhindern, dass in der Produktion Regressionsprobleme auftreten, ist ein Satz automatisierter End-to-End-Tests von großem Vorteil. Bevor diese Tests jedoch implementiert werden, müssen die Ingenieure der automatisierten Qualitätssicherung (AQA) und das Management das Framework festlegen, auf dem diese Tests basieren sollen.

In diesem Artikel werden wir die beiden beliebtesten API-Test-Frameworks auf dem Markt untersuchen und vergleichen: Karate und REST-Assured. Beide Frameworks sind Java-basiert. Der Vergleich hilft Ihnen bei der Entscheidung, welches Framework am besten für Ihre spezifischen Anforderungen geeignet ist. Fangen wir an!

Karate Überblick

Karate ist ein relativ neues Open-Source-Framework, das es Testern auch ohne Programmierkenntnisse ermöglicht, API-, Web-Service- und Microservice-Tests durchzuführen. Man könnte annehmen, dass es aus Japan stammt, oder? Tatsächlich wurde dieses Framework 2017 von Peter Thomas entwickelt und wird seitdem von Unternehmen weltweit eingesetzt.

Schauen wir uns die Details des Karate-Tools an. Was die Installation betrifft, so ist Karate als einzelne, ausführbare JARJAR-Datei verfügbar. Es kann auch als Maven-Abhängigkeit zur pom.xml-Datei hinzugefügt werden. Wenn Sie es nicht in ein Test-Framework integrieren möchten, können Sie sich für die Kernversion entscheiden:

<!-- https://mvnrepository.com/artifact/com.intuit.karate/karate-core -->
<dependency>
<groupId>com.intuit.karate</groupId>
 	<artifactId>karate-core</artifactId>
    	<version>1.2.0</version>
</dependency>

Ansonsten gibt es Versionen, die mit den TestNG- und JUnit-Test-Frameworks kompatibel sind.

<!-- https://mvnrepository.com/artifact/com.intuit.karate/karate-junit5 -->
<dependency>
    	<groupId>com.intuit.karate</groupId>
    	<artifactId>karate-junit5</artifactId>
    	<version>1.2.0 </version>
</dependency>

<!-- https://mvnrepository.com/artifact/com.intuit.karate/karate-testng -->
<dependency>
    	<groupId>com.intuit.karate</groupId>
    	<artifactId>karate-testng</artifactId>
    	<version>0.8.0.1</version>
</dependency>

Karate verwendet eine eigene Testskriptsprache namens “Karate-Script”. Wenn Sie Erfahrung mit Frameworks für verhaltensgesteuerte Entwicklung (BDD) wie Cucumber haben, werden Sie feststellen, dass die Codestruktur von Karate ihr sehr ähnlich ist:

Feature: sample karate test script
 for help, see https://github.com/karatelabs/karate/wiki/IDE-support

 Background:
   * url 'https://jsonplaceholder.typicode.com/'

   Scenario: get all users and then get the first user by id
     Given path 'users'
     When method get
     Then status 200

     * def first = response[0]

     Given path 'users', first.id
     When method get
     Then status 200
     And assert response.name == "Leanne Graham"
     And assert response.email == "Sincere@april.biz"

In der Tat weist Karate viele Ähnlichkeiten mit Cucumber auf:

  1. Schlüsselwörter wie “Feature”, “Scenario”, “Given”, “When”, “Then”, “And”
  2. Speicherung von Tests in .feature-Dateien

Das Hauptunterscheidungsmerkmal von Karate ist jedoch, dass “alle Schrittdefinitionen bereits für uns geschrieben sind”. Dies ist besonders für API-Tests von Vorteil. Im Gegensatz zu UI-Tests folgen API-Tests in der Regel einem einheitlichen Schema:

  1. Hintergrund
  2. Bereitstellung von Anfrageparametern
  3. Validierung des Antwortstatus

Es ist also sehr sinnvoll, vorgefertigte Schrittdefinitionen zu verwenden, anstatt eigene zu erstellen. Schauen wir uns nun an, wie Karate alle drei genannten Schritte handhabt.

Hintergrund

Hintergrund enthält in der Regel gemeinsame Eigenschaften, die von allen Szenarien in der .feature-Datei genutzt werden. Einige dieser Eigenschaften sind bereits vordefiniert, z. B. die URL:

Hintergrund :
 * url 'https://jsonplaceholder.typicode.com'

Außerdem können Sie mit dem Schlüsselwort “def” Variablen direkt in der Feature-Datei definieren:

* def user =
 """
 {
   "name": "Test User",
   "username": "testuser",
   "email": "test@user.com",
   "address": {
     "street": "Has No Name",
     "suite": "Apt. 123",
     "city": "Electri",
     "zipcode": "54321-6789"
   }
 }
 """

Bereitstellung von Abfrageparametern

Zunächst müssen wir einen Pfad angeben, der auch durch Kommas getrennte Abfrageparameter enthalten kann.

Gegebener Pfad 'users', 1234

Als Nächstes können wir bei Bedarf die Nutzdaten der Anfrage einfügen, die sowohl JSON- als auch XML-Formate unterstützen.

JSON:

Und Anfrage 
{
   "name":"Vidhya",
   "age":29,"locale":"en",
   "twitter":"VidhyaJava",
   "email":"it.vidhyadharan@gmail.com"
}

XML:

 * def payload =
 """
<note>
<to>Tove</to>
<from>Jani</from>
<heading>Reminder</heading>
<body>Don't forget me this weekend!</body>
</note>
"""
 Und Anfrage  payload

Schließlich geben wir die HTTP-Methode für die Anfrage an, die eine der folgenden sein kann: get, post, delete, patch oder put.

Wann Methode  get

Validierung der Antwort

Zunächst validieren wir den Antwortcode sowohl für positive (200, 201) als auch für negative (4xx) Testfälle.

Dann Status 201
Dann Status 404

Als Nächstes fahren wir mit der Validierung des Antwortschemas fort, wobei wir die Nutzdaten der Antwort sowohl im JSON- als auch im XML-Format überprüfen können. Im Fall von JSON:

{
  "id": 1,
  "name": "Leanne Graham",
  "username": "Bret",
  "email": "Sincere@april.biz",
  "address": {
    "street": "Kulas Light",
    "suite": "Apt. 556",
    "city": "Gwenborough",
    "zipcode": "92998-3874",
    "geo": {
      "lat": "-37.3159",
      "lng": "81.1496"
    }
  },
  "phone": "1-770-736-8031 x56442",
  "website": "hildegard.org",
  "company": {
    "name": "Romaguera-Crona",
    "catchPhrase": "Multi-layered client-server neural-net",
    "bs": "harness real-time e-markets"
  }
}

Wenn wir bestimmte Eigenschaften validieren wollen, können die Behauptungen Folgendes beinhalten:

Und behaupten response.name == "Leanne Graham"
Und behaupten response.email == "Sincere@april.biz"

Es ist jedoch wichtig zu beachten, dass Karate im Gegensatz zu REST-Assured einen eingebauten Mechanismus zur Validierung der gesamten JSON-Antwort bietet:

Und Spiel response ==   {"id":1,"name":"LeanneGraham","username":"Bret",
"email":"Sincere@april.biz",
"address":{"street":"Kulas Light","suite":"Apt. 556",
"city":"Gwenborough","zipcode":"92998-3874",
"geo":{"lat":"-37.3159","lng":"81.1496"}},
"phone":"1-770-736-8031x56442",
"website":"hildegard.org",
"company":{"name":"Romaguera-Crona",
"catchPhrase":"Multi-layered client-server neural-net",
"bs":"harness real-time e-markets"}}

Karate enthält ein eingebautes Berichtswerkzeug, das Berichte im Verzeichnis target\karate-reports erstellt. Die wichtigste Berichtsdatei ist die Datei karate-summary.html:

Karate vs. REST-Assured: Automatisierte API-Tests mit Java

REST-Assured Überblick

REST-Assured ist eine Java-basierte Bibliothek, die von JayWay Company entwickelt wurde, um das Testen und die Validierung von Restful Web Services zu rationalisieren. Sie dient als effizienter Katalysator für die Automatisierung des Testprozesses von REST-APIs.

REST-Assured kann einfach durch Hinzufügen der Maven-Abhängigkeit installiert werden:

<dependency>
<groupId>io.rest-assured</groupId>
<artifactId>rest-assured</artifactId>
<version>5.1.1</version>
<scope>test</scope>
</dependency>

Ein Test-Framework wie JUnit oder TestNG ist als Test-Runner erforderlich. Das folgende Codebeispiel demonstriert die Verwendung von JUnit. Werfen wir nun einen Blick auf einen Testfall, der auf REST-Assured basiert:

public class UsersTest {

   @Before
   public void setup() {
       RestAssured.baseURI = "https://jsonplaceholder.typicode.com/";
   }

   @Test
   public void getAllUsersAndThenGetFirstUserById() {
       Response responseAllUsers =
               given()
                       .when()
                       .get("/users")
                       .then()
                       .statusCode(200)
                       .extract()
                       .response();

       int firstUserId = responseAllUsers.jsonPath().getInt("id[0]");

       Response responseFirstUser =
               given()
                       .pathParam("id", firstUserId)
                       .when()
                       .get("/users/{id}")
                       .then()
                       .statusCode(200)
                       .extract()
                       .response();
       Assert.assertEquals(responseFirstUser.jsonPath().getString("name"), 
      "Leanne Graham");
       Assert.assertEquals(responseFirstUser.jsonPath().getString("email"), 
       "Sincere@april.biz");
   }
}

Wir können sehen, dass Tests, die auf REST-Assured basieren, in einem BDD-ähnlichen Format geschrieben sind und der Given-When-Then-Struktur folgen. Im Gegensatz zu Karate sind diese Tests jedoch in den Java-Code eingebettet. Mit anderen Worten: Um die implementierten Testfälle zu sehen, muss man sich in die API-Tests vertiefen und den Code untersuchen. Standardmäßig verwendet REST-Assured keine .feature-Dateien, obwohl es mit Tools wie Cucumber oder anderen BDD-Frameworks integriert werden kann.

REST-Assured behandelt die Stufen des API-Testschemas auf folgende Weise (am Beispiel des JUnit-Testframeworks):

Hintergrund

Wir können einfach die @Before-Annotation verwenden:

@Before
public void setup() {
   RestAssured.baseURI = "https://jsonplaceholder.typicode.com";
}

Übergabe der Anfrageparameter

Dieser Schritt ist durch die Schlüsselwörter given() und when() in zwei Teile unterteilt.

  1. Mit given() können wir den Content-Type und den Request-Body angeben, falls erforderlich. Es ist möglich, JSON- oder XML-Payloads an ein String-Objekt zu übergeben:
public void createUser() {
   String body = "{\"name\": \"Test User\"," +
           "\"username\": \"testuser\", " +
           "\"email\": \"test@user.com\"," +
           "\"address\": " +
           "{ \"street\": \"Has No Name\"," +
           "\"suite\": \"Apt. 123\"," +
           "\"city\": \"Electri\"," +
           "\"zipcode\": \"54321-6789\"}}";
   Response response =
           given()
                   .contentType(ContentType.JSON)
                   .body(body)

Ein genauerer Ansatz ist die Verwendung von Object Mapping bei der Arbeit mit REST-Assured. Definieren Sie das Schema für die Nutzdaten der Anfrage in der entsprechenden Klasse:

public static class User {
   private String name;
   private String username;

   public User(String name, String username) {
       this.name = name;
       this.username = username;
   }

   public String getName() {
       return name;
   }

   public void setName(String name) {
       this.name = name;
   }

   public String getUsername() {
       return username;
   }

   public void setUsername(String job) {
       this.username = username;
   }
}

Dann können Sie einfach eine neue Instanz der Klasse in die Nutzlast der Anfrage einfügen:

@Test
public void createUser() {
   User user = new User("Test User", "testuser");
   Response response =
           given()
                   .contentType(ContentType.JSON)
                   .body(user)

Alternativ können wir die Nutzlast auch direkt aus einer Datei importieren:

@Test
public void createUserXML() {
   File xmlDataInFile = new File("src/test/resources/user.xml");
   Response response =
           given()
                   .contentType(ContentType.XML)
                   .body(xmlDataInFile)
  1. Bei when() müssen wir Pfad und Methode angeben:
.when()
.get("/users")

Der Pfad in REST-Assured lässt auch Variablen zu:

int firstUserId = 123;
*********************
.pathParam("id", firstUserId)
.when()
.get("/users/{id}")

Validierung der Antwort

Die Validierung der Antwort erfolgt mit dem Schlüsselwort then(). Zunächst wird normalerweise der Statuscode der Antwort validiert:

.then()
.statusCode(200)

Dann können wir mit dem Extrahieren des Antwortobjekts fortfahren:

.extract()
.response();

und einige seiner Eigenschaften zu überprüfen:

Assert.assertEquals(responseFirstUser.jsonPath().getString("name"), "Leanne Graham");
Assert.assertEquals(responseFirstUser.jsonPath().getString("email"), "Sincere@april.biz");

REST-Assured verfügt nicht über ein eingebautes Reporting-Tool. Daher ist es notwendig, eine geeignete externe Berichtsbibliothek wie Allure Report zu verwenden.

Karate gegen REST-Assured

Nun ist es an der Zeit, die beiden gegebenen Tools zu vergleichen und zu entscheiden, welches wir für unsere speziellen Bedürfnisse wählen sollen. Vergleichen wir ihre Leistung für das oben erwähnte einfache Testszenario:

Karate vs. REST-Assured: Automatisierte API-Tests mit Java
Karate vs. REST-Assured: Automatisierte API-Tests mit Java

Wie wir sehen, benötigt Karate im Falle eines einzelnen Szenarios viel weniger Zeit für die Ausführung als REST-Assured. Betrachtet man jedoch mehrere Szenarien (d. h. den gesamten Testumfang), so ist zu erwarten, dass der Unterschied nicht mehr so groß ist.

Der Vergleich der beiden Frameworks anhand anderer Hauptkriterien ist in der folgenden Tabelle dargestellt:

Karate
REST-Assured

Sintaxis BDD

Karate

Ja

REST-Assured

Ja

Test-Skripting-Sprache

Karate

Karate-Schrift (Gurke)

REST-Assured

Java

Validierung der gesamten JSON-Antwort

Karate

Ja

REST-Assured

Nein (zusätzliche Java-Bibliothek erforderlich)

Test-Läufer

Karate

Optional (JUnit, TestNG)

REST-Assured

Erforderlich (JUnit, TestNG)

Wiederholungsmöglichkeit

Karate

Ja

REST-Assured

Nein (zusätzliche Java-Bibliothek erforderlich)

Berichtswerkzeug

Karate

Eingebaut

REST-Assured

Externes Tool erforderlich (Allure Report, etc.)

Datei lesen

Karate

Ja

REST-Assured

Nein (zusätzliche Java-Bibliothek erforderlich)

Leistungsprüfung

Karate

Ja (Wiederverwendung von Karate-Tests Gatling-Tests möglich)

REST-Assured

No

Die Testwerkzeuge von Karate und REST-Assured haben beide ihre Stärken und Schwächen. Im Allgemeinen verfügt Karate im Vergleich zu REST-Assured über eine größere Anzahl integrierter Werkzeuge. Das bedeutet, dass Sie mit dem Schreiben von Tests beginnen können, ohne zusätzliche Zeit für die Konfiguration externer Bibliotheken aufwenden zu müssen. Darüber hinaus verwendet Karate eine einfache und unkomplizierte Test-Skriptsprache, die auch für Teams mit begrenzter Java-Erfahrung geeignet ist. Wenn Sie mit den Standardberichten von Karate zufrieden sind und keine Zeit in die Konfiguration von benutzerdefinierten Berichten investieren wollen, ist Karate vielleicht die bessere Wahl.

Andererseits erfordert REST-Assured ein starkes Java-Hintergrundwissen des QA-Automatisierungsteams und die Verwendung von zusätzlichen Bibliotheken. Im Falle komplexer und stark angepasster APIs könnte REST-Assured jedoch die bessere Wahl sein. Das Schreiben von Tests direkt in Java kann in solchen Fällen einfacher sein.

REST-Assured ermöglicht auch die Verwendung von Bibliotheken wie Hamcrest Matchers, die mehr Optionen für zusätzliche Validierungen in bestimmten Szenarien bieten. Wenn Sie BDD bevorzugen und die vorgefertigten Schrittdefinitionen von Karate Ihren Anforderungen nicht genügen, kann REST-Assured in Verbindung mit einem externen BDD-Tool wie Cucumber verwendet werden.

Abschließende Überlegungen

Abschließend ist anzumerken, dass sowohl Karate als auch REST-Assured aktiv entwickelt werden und gut dokumentiert sind. Letztendlich sollte die Wahl zwischen Karate und REST-Assured auf Faktoren wie der Komplexität Ihrer API, dem Niveau der Java-Kenntnisse in Ihrem Team, dem Bedarf an spezifischen Validierungs- oder Anpassungsoptionen und Ihrem bevorzugten Testansatz (z. B. BDD) beruhen.

Mit den oben genannten Informationen können Sie eine fundierte Entscheidung darüber treffen, welches Tool am besten zu Ihren spezifischen Anforderungen passt. Viel Erfolg!