8 Minuten Lesezeit (1636 Worte)

Ein Ansatz für CDC-Tests mit Gauge

In einer Software-Architektur kann es passieren, dass eine Schnittstelle nicht mehr wie erwartet funktioniert. Ursächlich kann hier die Änderung einer Schnittstelle sein. Diese Änderung wird im Normalfall durch Integrationstests innerhalb der Test-Suites erkannt. Im schlimmsten Fall kann es passieren, dass diese Änderung erst gar nicht berücksichtigt wird in diesen Tests. Ein Fehler tritt dann häufig erst sehr spät auf, wenn die Schnittstelle innerhalb der Produktion genutzt wird. Das führt wiederum zu hohem Aufwand, um den Fehler zu beheben. Der Integrationstest ist auch nicht optimal, da eine Analyse zunächst feststellen muss, ob es sich um eine Änderung der API handelt oder ein Problem der Infrastruktur.

Eine effiziente Möglichkeit zu testen, bieten die Consumer-Driven-Contract (CDC)-Tests. Hierbei definieren die Consumer ihre Erwartungen in Form eines Tests an die API eines Dienstes. Diese API wird von einem Provider zur Verfügung gestellt. Aus diesen Erwartungen geht dann ein Vertrag (Contract) zwischen den Diensten hervor. Dieser Contract verhindert, dass Schnittstellenänderungen unbemerkt bleiben und diese frühzeitig erkannt werden. Wenn der Provider nun an seiner API eine Änderung vornimmt, die nicht in dem Contract enthalten ist, kommt es zu einem Fehler.

Grundlagen des Consumer-Driven-Contract-(CDC)-Tests

Bei einem Consumer-Driven-Contract-(CDC)-Test handelt es sich um eine Form der Vertragstests, welche sicherstellt, dass ein Provider mit den Erwartungen des Consumers an ihn kompatibel ist. Zu unterscheiden ist hier zwischen Consumer (Verbraucher) und Provider (Anbieter). Der Anbieter stellt einen Dienst bereit, der von den Consumern genutzt wird. Der Consumer ruft diesen Dienst auf. Der CDC-Test validiert die Korrektheit der Request- und Response-Nachrichten zwischen Consumer und Provider. Dies geschieht isoliert voneinander auf Basis eines Contracts (Vertrags). Innerhalb des Contracts werden die Erwartungen an die jeweiligen Services gestellt. Diese Erwartungen umfassen Requests und zugehörige Response zwischen den Diensten. Der Test erfolgt aus Sicht des Consumers, um sicherzustellen, dass der Provider seine Schnittstelle auf eine Art bereitstellt, die von allen Consumern verarbeitet werden kann.

Neben den CDC-Tests gibt es weitere Teststufen, die sich zu einer Testpyramide zusammenfassen lassen. Im Folgenden wird darauf eingegangen, wo sich der CDC-Test in die Testpyramide einordnen lässt. In Abbildung 1 kann dazu die Testpyramide eingesehen werden. Die Testpyramide ist ein grafisches Modell, welches Teststufen und deren Umfang abbildet und die Teststufen nach isoliertem und integriertem Testen unterteilt. Ganz unten stehen Modultests, die nach dem Modell eine hohe Anzahl ausmachen und schnell in der Ausführung sind. Bei den Integrationstests ist die Ausführungszeit deutlich erhöht und die Wartung aufwändiger. Zu erkennen ist, dass sich der CDC-Test noch unterhalb der Integrationstests einordnen lässt und zu den isolierten Tests gehört. Die Integrationstests benötigen weitere System-Teile. Bei den CDC-Tests wird hingegen auf Mocks gesetzt, um bestimmte System-Teile zu simulieren.

Der Aufbau eines Contracts in Pseudocode kann in folgendem Listing 1 eingesehen werden. Die Erwartungen für Request und Response werden in dem Contract festgehalten. Im Falle eines Verstoßes der Parameter entsteht ein Fehler. 

Abbildung 1: Testpyramide mit CDC-Tests
{
    request {
        method “POST“
        url “/user-service/users“
        body (
            firstName: “Max“
            lastName: “Mustermann“
        )
        header { contentType: application/json }
    }
    response {
        status 201 
        body (
            id: 42
        )
        header { contentType: application/json }
    }
}
 

Listing 1: Aufbau eines Contracts mit Pseudocode 

Das Testautomatisierungsframework Gauge

Gauge ist ein Open-Source Testautomatisierungs-Framework, welches unterschiedliche Programmiersprachen unterstützt. Hierzu gehören JavaScript, C#, Java, Python und Ruby. Das Framework verwendet eine simple Syntax, um Tests zu formulieren. Für einen Test wird eine Spezifikation und eine Implementierung benötigt. Die Spezifikation ist in menschenlesbarer und selbsterklärender Markdown-Sprache formuliert. Es können somit in ihr Beschreibungen für den Test angelegt werden. In Abbildung 2 ist die Architektur von Gauge zu sehen, um ein Verständnis für Begriffe zu schaffen, mit denen Gauge arbeitet. Jede Spezifikation benötigt ein „Specification heading“, welche eine Überschrift für die Spezifikation darstellt. Außerdem werden in der Spezifikation „Steps“ angelegt. Diese werden in Gauge Spezifikationen mit einem „*“ eingeleitet und werden bei der Testausführung von oben nach unten abgearbeitet. Die Steps können ebenfalls zu einem Szenario zusammengefasst werden. Im Falle dessen lassen sie sich nur im Rahmen der Durchführung des Szenarios zusammen ausführen.

Für die Logik der Schritte werden Implementierungen mit Quellcode benötigt. In diesem Beispiel wird dabei die Programmiersprache Java verwendet. Eine Methode, welche mit einem Step aus der Spezifikation verbunden werden soll, erhält den Tag „@Step“. Eine genauere Anwendung einer Spezifikation und einer Implementierung erfolgt in dem nachfolgenden Kapitel. 

Abbildung 2: Architektur von Gauge

Umsetzung der CDC-Tests mit Gauge

Im Folgenden wird auf ein Beispiel eingegangen, wie eine Umsetzung von CDC-Tests mit Gauge aussehen kann. Dafür wird eine simple Spring-Boot-Anwendung zur Verwaltung von Urlaub genutzt, welche REST-Schnittstellen bereitstellt. Mit den Schnittstellen ist es möglich, Urlaub zu beantragen und diesen zu genehmigen. Zuvor ist es erforderlich, Gauge zu installieren. Eine gute Anleitung dazu kann in der offiziellen Gauge-Dokumentation entnommen werden.

Für die CDC-Tests wird ein Client simuliert, der die Requests an die Anwendung sendet. Dies geschieht über Gauge, indem über Steps, Methoden aufgerufen werden, welche einen Request mittels RestTemplate senden. Um den Client für die CDC-Tests zu erstellen, wird das Plugin Swagger Codegen in der Version 3.0.41 verwendet. Auf Basis einer OpenAPI-Spezifikation der Anwendung lässt sich ein auf RestTemplate basierender Client generieren. Die OpenAPI-Datei wurde zuvor ebenfalls durch Swagger erstellt. Eine passende Anleitung dazu kann auf folgender Seite entnommen werden: https://www.baeldung.com/swagger-2-documentation-for-spring-rest-api

Um Swagger Codegen zu nutzen, wird das Plugin in der pom.xml des Projektes hinzugefügt. Dieses ist in Listing 2 zu finden. Das Plugin und eine Dokumentation dazu kann unter folgendem Link gefunden werden: https://github.com/swagger-api/swagger-codegen

Anschließend kann das Plugin innerhalb der Oberfläche für die Maven-Plugins der jeweiligen IDE oder als Befehl in der Kommandozeile ausgeführt werden, um den Client zu generieren.

<plugin>
    <groupId>io.swagger.codegen.v3</groupId>
    <artifactId>swagger-codegen-maven-plugin</artifactId>
    <version>3.0.41</version>
    <configuration>
        <inputSpec>${basedir}/src/main/resources/openApi.json</inputSpec>
        <output>${project.build.directory}/generated-sources</output>
        <language>java</language>
        <library>resttemplate</library>
        <generateApiTests>false</generateApiTests>
        <generateModelTests>false</generateModelTests>
        <generateApiDocumentation>false</generateApiDocumentation>
        <generateModelDocumentation>false</generateModelDocumentation>
        <configOptions>
            <dateLibrary>java8</dateLibrary>
            <sourceFolder>restclient</sourceFolder>
            <delegatePattern>true</delegatePattern>
            <useTags>true</useTags>
        </configOptions>
    </configuration>
    <executions>
        <execution>
            <goals>
                <goal>generate</goal>
            </goals>
        </execution>
    </executions>
</plugin>
 

Listing 2: Swagger Codegen V3 Plugin in der pom.xml

In einem weiteren Schritt wird die Gauge-Spezifikation für die CDC-Tests der Anwendung verfasst. Diese kann wie in Listing 3 aussehen. Für die Daten von Response und Request wird das Tabellen-Konstrukt der Gauge-Markdown-Sprache genutzt. Hier lassen sich, wie in dem Beispiel zu sehen, Testdaten für die Tests anlegen. Diese Daten sollen das widerspiegeln, was ein Client an den Server (also an die Urlaubsverwaltung) sendet und anschließend von diesem erhält. In einem weiteren Schritt erfolgt dann der eigentliche Request an den Server, wo die Testdaten validiert werden, um zu schauen, ob eine Vertragsverletzung vorliegt.  

Urlaubsverwaltung
=====================


Urlaub nehmen
----------------
Es wird Urlaub genommen für einen bestimmten Zeitraum. Anschließend wird ein entsprechender Genehmiger benachrichtigt.

* Testdaten für Request erstellen vom Typ "ApplicationRequest"
    | key | value | type |
    |-----|--------------|------|
    | mitarbeiter | ma1 | string |
    | genehmiger | ge1 | string |
    | von | 2023-10-16 | string |
    | bis | 2023-10-18 | string |
    | bemerkung | testbemerkung | string |
* Testdaten für Response erstellen vom Typ "Application"
    | key | value | type |
    |-----|--------------|------|
    | id | 1 | string |
    | bemerkung | testbemerkung | string |
    | von | 2023-10-16 | string |
    | bis | 2023-10-18 | string |
    | genehmiger | ge1 | string |
    | mitarbeiter | ma1 | string |
    | status | OFFEN | string |
* Es wird ein Urlaubsantrag gestellt über die Schnittstelle /application und erwartet 201
 

Listing 3: Exemplarische Gauge-Spezifikation

Nun erfolgt die Implementierung der Schritte mithilfe von Java. Um die Tabellen in Quellcode zu überführen, wird eine Hash-Map genutzt. Diese kann folgendem Listing 4 entnommen werden. Um die Methode von der Spezifikation auszuführen, werden die Step-Annotationen genutzt. Diese rufen die fuelleDaten-Methode auf, welche die Daten aus der Spezifikation in die Hash-Map überführt. Die Variablen responseBody und requestBody sind vom Typ HashMap und müssen vorab initialisiert werden.

@Step("Testdaten für Request erstellen vom Typ <requestBody> <table>")
public void erstelleRequest(String tableName, Table table) {
    this.requestBody = fuelleDaten(table);
}

@Step("Testdaten für Response erstellen vom Typ <requestBody> <table>")
public void erstelleResponse(String tableName, Table table) {
    this.responseBody = fuelleDaten(table);
}

private HashMap<String, String> fuelleDaten(Table table) {
    HashMap<String, String> daten = new HashMap<>();
    List<TableRow> rows = table.getTableRows();
    for (TableRow row : rows) {
        List<String> cellValues = row.getCellValues();
        daten.put(cellValues.get(0), cellValues.get(1));
    }
    return daten;
}
 

Listing 4: Implementierung für die Überführung der Testdaten in Java-Code

Für die Ausführung des Requests an die Urlaubsverwaltungs-Anwendung wird die Methode von Listing 5 benötigt. Diese wird ebenfalls durch die Spezifikation aufgerufen. In dieser werden die von Swagger Codegen generierten Klassen genutzt, die auf RestTemplate basieren. Die in Java überführten Daten der Spezifikation werden zunächst einem Request-Objekt hinzugefügt. Anschließend erfolgt der Request an die Anwendung. Zum Schluss wird der Response validiert, indem Statuscode und Informationen des Bodys geprüft werden. Im Falle einer Abweichung entsteht ein Fehler. 

@Step("Es wird ein Urlaubsantrag gestellt über die Schnittstelle /application und erwartet 201")
public void urlaubNehmen() {
    ApiClient client = new ApiClient();
    AntragApi api = new AntragApi(client);

    ApplicationRequest request = new ApplicationRequest();
    request.setMitarbeiter(requestBody.get("mitarbeiter"));
    request.setGenehmiger(requestBody.get("genehmiger"));
    request.setVon(requestBody.get("von"));
    request.setBis(requestBody.get("bis"));
    request.setBemerkung(requestBody.get("bemerkung"));

    ResponseEntity<Application> response = api.createApplicationWithHttpInfo(request);

    Assertions.assertEquals(response.getStatusCodeValue(), 201);

    Application application = response.getBody();
    id = application.getId();
    Assertions.assertEquals(application.getBemerkung(), responseBody.get("bemerkung"));
Assertions.assertEquals(application.getVon(), responseBody.get("von"));
    Assertions.assertEquals(application.getBis(), responseBody.get("bis"));
    Assertions.assertEquals(application.getGenehmiger(), responseBody.get("genehmiger"));
    Assertions.assertEquals(application.getStatus(), responseBody.get("status"));
    Assertions.assertEquals(application.getMitarbeiter(), responseBody.get("mitarbeiter"));

}
 

Listing 5: Request an Urlaubsverwaltungs-Anwendung und Validierung der Response

Um nun einen Test auszuführen, kann die Spezifikation über die Oberfläche oder mittels eines Befehls ausgeführt werden. Für die Testfälle bietet Gauge weiter die Möglichkeit, die Tests nach der Ausführung grafisch zu betrachten. Dazu erstellt Gauge eine HTML-Datei, welche in dem „reports“ Verzeichnis angelegt wird und sich von einem beliebigen Browser öffnen lässt. 

Fazit

Mit dem oben erprobten Ansatz lassen sich CDC-Tests mit Gauge erstellen, was neben Tools wie PACT oder Spring Cloud Contract eine weitere Möglichkeit bietet, die Tests zu implementieren. Hierbei kann eine Spezifikation mittels Gauge als ein Vertrag genutzt werden, der verbunden mit der Quellcode-Logik Requests an Anwendungen sendet. Kommt es hier zu Abweichungen durch Änderungen von Schnittstellen, wird dies als ein Fehler angezeigt, welcher schnell behoben werden kann. 

Seminarempfehlung

Florian Kowalke hat noch keine Informationen über sich angegeben
 

Kommentare

Derzeit gibt es keine Kommentare. Schreibe den ersten Kommentar!
Donnerstag, 25. April 2024

Sicherheitscode (Captcha)

×
Informiert bleiben!

Bei Updates im Blog, informieren wir per E-Mail.

Weitere Artikel in der Kategorie