Das Wissensportal für IT-Professionals. Entdecke die Tiefe und Breite unseres IT-Contents in exklusiven Themenchannels und Magazinmarken.

SIGS DATACOM GmbH

Lindlaustraße 2c, 53842 Troisdorf

Tel: +49 (0)2241/2341-100

kundenservice@sigs-datacom.de

Testen von Microservices mit Consumer-Driven Contracts

In Zeiten der Digitalisierung ist es für dynamische Unternehmen notwendig, technische Strukturen zu etablieren, die den immer schneller werdenden Marktanforderungen gerecht werden. Der digitale Darwinismus fordert Strukturen, die sowohl agil als auch schnell genug sind, diesen Anforderungen gerecht zu werden.
Author Image
Rudolf Grötz

Author


  • 01.05.2018
  • Lesezeit: 13 Minuten
  • 40 Views

Charles Darwin hat in seinen Werken einen wichtigen Punkt erarbeitet: Es sind nicht die Stärksten, die überleben, und auch nicht die Intelligentesten. Es sind vielmehr jene, die sich am schnellsten den neuen Gegebenheiten anpassen können.

Microservices als Antwort auf den digitalen Darwinismus

Unternehmen wie Netflix, Amazon und Google haben schnell erkannt, die notwendigen technischen Strukturen anzupassen und zu implementieren. Geschafft wurde das durch den Einsatz von Microservices als neues Architekturprinzip. Weg vom Monolithen, hin zu kleinen, schlanken, unabhängigen Services, die über standardisierte APIs kommunizieren. Statt einer einzelnen monolithischen Anwendung, die die gesamte Geschäftslogik enthält, erledigt ein flexibles Netzwerk aus Microservices alle komplexen Vorgänge.

Der Trend in der Systemarchitektur geht weg vom Monolithen, hin zu Microservices, um durch kleine überschaubare Programmeinheiten Continuous Delivery zu ermöglichen und Features schneller dem Markt zur Verfügung stellen zu können.

Microservices ist ein Architekturmuster, bei dem die Anwendung in Services unterteilt wird und nicht in Schichten (siehe Abbildung 1). Die Microservices sind dabei entkoppelt und laufen in einem eigenen Prozess, damit diese leichter austauschbar sind. Microservices sind auch technologisch voneinander unabhängig, da sie durchaus mit verschiedenen Programmiersprachen, Frameworks oder Datenbanken realisiert werden können.

Abb. 1: Architekturmuster

Microservices lassen sich unter anderem durch folgende Eigenschaften beschreiben:

  • Die Anwendung wird in kleine Services unterteilt, die entkoppelt sind, und verwendet diese als Komponenten. Diese Services sind IT-Repräsentationen von in sich abgeschlossenen fachlichen Funktionalitäten.
  • Microservices enthalten alle Schichten (Datenbank, Middleware und Frontend) einer Funktionalität. Anwendungen werden somit nicht in Schichten unterteilt, sondern in Funktionalitäten.
  • Microservices werden eigenständig und nicht als gesamte Anwendung deployed. Dadurch entsteht selbst bei der Implementierung keine Abhängigkeit zu anderen Services.
  • Die Kommunikation von Microservices erfolgt meist über HTTP-basierte Schnittstellen, wie beispielsweise RESTful-APIs.

Testautomation, ein Muss

Der Erfolgsfaktor, um die Qualität im Softwareentwicklungsprozess zu verbessern, besteht darin, schnelle Rückmeldung über die Auswirkungen von Änderungen zu erhalten. Microservices sind auch darauf ausgelegt, schnell auf Änderungswünsche der Kunden reagieren zu können, um die Time-To-Market niedrig zu halten.

Der Vorteil von Microservices ist die Fähigkeit, Services einzeln zu entwickeln, bereitzustellen und anzupassen, ohne das gesamte System zu stören. Diese Tatsachen stellen zusätzliche Anforderungen an das Testen. Hier nimmt Testautomation einen viel höheren Stellenwert ein als in anderen Systemen. Angesichts der Tatsache, dass die Entwicklung von Microservices verteilt und unabhängig ist, sind manuelle Teststrategien, die auf monolithische Architekturen angewendet werden, hier kaum anwendbar.

Microservices-Anwendungen müssen eine hohe Leistung und Funktionalität bieten, die erfordert, dass jede Schicht der Anwendung gründlich getestet wird. Hier ist wenig Platz für manuelle Tests. Testautomation ist hier ein Muss, manuelle Tests ein Kann. Es ist äußerst schwierig, die Auswirkungen einer Reihe von Änderungen auf so ein komplexes Softwaresystem durch manuelle Tests vorherzusagen. Hier gilt es, viele verschiedene Arten von Tests zu automatisieren.

SunRiseSet – Microservice-Architektur

Am Beispiel einer Microservice-Architektur für eine mobile App wird erklärt, welche Arten von Tests sinnvoll sind. Die App SunRiseSet hilft Anglern, ihren Anglerausflug zu planen. Es ist ein großer Vorteil für Angler zu wissen, wann die Sonne auf und wann die Sonne untergeht. Warum? Weil auf manchen Gewässern das Nachtfischen verboten ist. Nun stellt sich die Frage, von wann bis wann ist es eigentlich Nacht? Die Antwort steht im Fischereigesetz: eine Stunde nach Sonnenuntergang bis eine Stunde vor Sonnenaufgang. Daher muss man wissen, wann die Sonne auf und untergeht.

Dann gibt es ja auch noch die Wetterfrage. Was zieht der Angler an? Überrascht mich wieder das Wetter und ich muss den Anglertrip abbrechen?

All diese Fragen, beantwortet unsere mobile App SunRiseSet (siehe Abbildung 2). Sie liefert für einen eingegebenen Ort den Sonnenauf- und Untergangszeitpunkt, die Wetterdaten und die Landkarte.

Abb. 2: Die App SunRiseSet

Das System besteht auf der Backend-Seite aus sechs Microservices (siehe Abbildung 3). Der Location- und der Weather-Service greifen auf externe APIs zu. Die Services haben folgende Funktionalitäten:

  • API-Gateway – dient als Facade. Nur über dieses Gateway kann auf die dahinterliegenden Services zugegriffen werden.
  • AUTH – dient zu Authentifizierung des User und der Tokenverwaltung.
  • MariaDB – dient zum Persistieren der Userdaten.
  • Location – dient zur Ermittlung der Geolocation via externen Webservice.
  • Weather – ermittelt die Wetterdaten für die Geolocation via externen Webservice.
  • Sun – berechnet Sonnenauf- und Sonnenuntergang für die Geolocation.

Abb. 3: Übersicht der Testarten

Testaspekt

In unseren Tests betrachten wir die Login-Funktionalität des AUTH-Service. Um die Tests besser zu verstehen, ist es notwendig, die User-Daten (siehe Tabelle 1) zu kennen, die in der Datenbank vorhanden sind und den Tests zugrunde liegen.

Tabelle 1: Testdaten

Der Aufruf mit einem gültigen User liefert die JSON-Nachricht aus Abbildung 4 zurück.

Abb. 4: Loginversuch mit gültigem User

Testen von Microservices

Eine Microservice-Architektur besteht aus unabhängig einsetzbaren Diensten, die es erschweren, nur altbekannte Testansätze in der Pipeline anzuwenden.

Durch das Zerteilen eines Systems in kleine, eigenständige Services werden zusätzliche Schnittstellen sichtbar, die in einer monolithischen Architektur nicht vorhanden waren. Zusätzliche Schnittstellen, nicht nur in technischer, sondern auch in organisatorischer Hinsicht, sind hilfreich, sind doch nun meistens mehrere Teams an der Entwicklung beteiligt.

Die Zusammenarbeit mehrerer Services, sowohl aus Consumer- als auch Providersicht, und die reibungslose Integration der Services untereinander sind daher ein wichtiger Aspekt. Diesem Umstand wird durch zusätzliche Tests, besser bekannt unter dem Begriff „Consumer-Driven Contracts“, begegnet.

In der Welt der Softwaretests gibt es viele Bezeichnungen für die verschiedensten Arten von Tests. Eine strikte Trennung nach Zielen und Methoden ist sehr schwer. Das ISTQB-Glossar [ISTQB] alleine unterscheidet fünf verschiedene Arten von Acceptance-Tests. Diverse Testpyramiden verschiedenster Testspezialisten [Scot16, Ash14] tragen nicht dazu bei, hier Unklarheiten auszuräumen. Zum Test von Microservice-Umgebungen sollten meiner Meinung nach folgende Tests Anwendung finden.

Komponententests

Unit- oder Komponententests sind immer noch anwendbare Testansätze, denn innerhalb seiner Grenzen ist jedes Service noch sehr zusammenhängend. Diese Tests durchlaufen kleine Code-Abschnitte, also Klassen oder Methoden. Abhängige Komponenten werden dabei durch Mocks ersetzt, um den Service in einer isolierten Umgebung unabhängig testen zu können.

Integrationstests

Durch die zusätzlichen Schnittstellen werden Integrationstests noch wichtiger für das Gesamtgefüge. Wurden bisher die Daten innerhalb eines Monolithen ausgetauscht, kommt durch Microservices der Datenaustausch, in den meisten Fällen via HTTP, ins Spiel. Die Integrationstests sollen sicherstellen, dass dieses Zusammenspiel funktioniert.

Consumer-Driven Contracts

Mit Consumer-Driven Contracts (CDC) ist ein fast vergessener Testansatz wieder in den Mittelpunkt gerückt. Thoughtworks hat aufgrund des Microservice-Hypes Consumer-Driven Contracts wieder auf den Technology Radar [Thought] zurückgeholt:

… We’ve decided to bring consumer-driven contract testing back from the archive for this edition even though we had allowed it to fade in the past. The concept isn’t new, but with the mainstream acceptance of microservices, we need to remind people that consumer-driven contracts are an essential part of a mature microservice testing portfolio, enabling independent service deployments…

Consumer-Driven Contracts betrachten Microservices aus der Perspektive der aufrufenden Einheit. Der Contract beschreibt, wie die Antwort des Providers aufgebaut sein muss. Im Grunde genommen definiert der Consumer eines Service zusammen mit dessen Provider einen Kontrakt und prüft in einem Test, ob dieser eingehalten wird. Stellt der Provider nun eine neue Version des Service zur Verfügung, stellt er durch den Contract-Test aller Consumer sicher, dass alle Serviceaufrufe der Consumer noch funktionieren.

End-to-End-Tests

End-to-End-Tests kommen zum Einsatz, um die volle Qualität des Systems zu gewährleisten. Diese Tests prüfen, ob die Geschäftsfälle, wie sie ein Anwender durchführt, das richtige Ergebnis liefern. Es wird dabei sichergestellt, dass Systemabhängigkeiten und Datenintegrität zwischen verschiedenen Systemkomponenten und Systemen beibehalten werden.

Lasttests

Lasttests sind eine Unterkategorie von Performanztests und prüfen, ob die Leistung der Software unter ungünstigen und extremen Bedingungen zufriedenstellend ist. Diese Bedingungen können als Folge von starkem Netzwerkverkehr, Prozessorauslastung oder Überlastung einer bestimmten Ressource auftreten.

In diesem Artikel wird die Umsetzung von Consumer-Driven Contracts erklärt. Weitere Informationen sind in [Grö17] zu finden.

Consumer-Driven Contracts

Consumer-Driven Contracts betrachten Microservices aus der Perspektive der aufrufenden Einheit. Der Contract beschreibt, wie die Antwort des Providers aufgebaut sein muss. Im Grunde genommen definiert der Consumer eines Service zusammen mit dessen Provider einen Vertrag und prüft in einem Test, ob der Vertrag eingehalten wird. Stellt der Provider nun eine neue Version des Service zur Verfügung, stellt er durch den Contract-Test aller Consumer sicher, dass alle Serviceaufrufe der Consumer noch funktionieren.

Der Vorteil dieser neuen Art von Tests ist, dass es im Prinzip Unittests sind, die lokal und unabhängig ausgeführt werden können. Dazu sind sie auch sehr schnell und zuverlässig.

Was testen wir?
Wir testen das Zusammenspiel des API-Gateways als Consumer mit dem AUTH-Service, der als Provider fungiert (siehe Abbildung 5).

Abb. 5: Ablauf eines Consumer-Contract-Tests

Womit testen wir?
Für unseren Test verwenden wir PACT, ein Consumer-Driven-Contract-Testframework, das unter anderem für JavaScript, NodeJS, JVM, .NET, Ruby verfügbar ist.

Wie testen wir?
Im ersten Schritt definieren wir in der Datei AuthServiceClient.spec.js (siehe Listing 1), wie das API-Gateway den AUTH-Service aufruft und welcher JSON-Payload als Antwort erwartet wird. Wir definieren in der Datei:

  • einen PACT-Mock-Server, der als Service-Provider fungiert,
  • für jeden Request, den wir absetzen, definieren wir den JSON-Payload, der als Antwort zurückgeliefert werden soll,
  • Tests, die den PACT-Mock-Server aufrufen und prüfen, ob die Antworten den definierten Erwartungen entsprechen.

Listing 1: Auszug AuthServiceSlient.spec.js

Den Consumer-Contract starten wir durch:

mocha
../spec/AuthServiceClient.spec.js

Der PACT-Provider-Mock liefert die Daten wie von uns definiert zurück und generiert einen Consumer-Contract, aka PACT-Datei.

PACT-Datei
Unser Test hat nicht nur das Verhalten des Providers überprüft, sondern auch den Consumer-Contract in der Datei consumer-login_provider.json gespeichert (siehe Listing 2). Diese Datei im JSON-Format ist im Wesentlichen der Vertrag zwischen Consumer und Provider und enthält alle Interaktionen, die definiert wurden. Jede Interaktion legt fest, was der Provider für einen gegebenen Request zurückgeben soll.

Listing 2: Auszug aus dem Consumer Contract

Dem Test aus Providersicht steht jetzt nichts mehr im Weg. Der Provider erhält die PACT-Datei vom Consumer und überprüft mit seinem Provider-Test (siehe Listing 3), dass er nicht gegen die Interaktionen verstößt, die der Consumer definiert hat. Der Provider-Test wird gestartet durch:

node ../spec/AuthService.spec.js

Der Provider liefert die Daten wie in der PACT-Datei beschrieben zurück und wir haben eine erfolgreiche Testdurchführung:

Example app listening at http://:::8081 success

Listing 3: Provider Test/AuthService.spec.js

Zusammenfassend lässt sich feststellen, dass wir im Bereich von Microservices die klassischen Integrationstests fast vollständig durch Consumer-Contract-Tests ersetzen könnten.

Fazit

Microservices ist ein Architekturansatz, der die gesamte Geschäftslogik in einem Netzwerk aus kleinen Services zur Verfügung stellt, und nicht in einem einzelnen Monolithen. Die Microservices sind dabei lose gekoppelt und laufen jeweils in einem eigenen Prozess, damit sie leichter austauschbar sind. Durch die eigenständigen Services werden zusätzliche Schnittstellen sichtbar, die in einer monolithischen Architektur nicht vorhanden sind. Dieses Architekturmuster stellt besondere Anforderungen an die Testautomation und Continuous-Delivery-Initiativen.

Durch die zusätzlichen Schnittstellen bei Microservices werden Integrationstests noch wichtiger für das Gesamtgefüge. Es lässt sich feststellen, dass im Bereich von Microservices die klassischen Integrationstests durch Consumer-Contract-Tests sinnvoll ergänzt beziehungsweise unter Umständen sogar durch diese ersetzt werden können. Consumer-Contract-Tests sind leicht zu schreiben, schnell und robust.

In Microservice-Architekturen nimmt Testautomation einen viel höheren Stellenwert ein als in anderen Systemen. Angesichts der Tatsache, dass die Entwicklung von Microservices verteilt und unabhängig ist, können manuelle Teststrategien, die auf monolithische Architekturen angewendet werden, hier nicht angewendet werden. Testautomation ist hier der Schlüssel zum Erfolg.

Der Artikel gibt die Meinung des Autors, und nicht die seines Arbeitgebers, wieder.

Referenzen

[Ash14]
St. Ashman, Layers of Test Automation, QA Matters, 28.12.2014, siehe:
http://qa-matters.com/2014/12/28/layers-of-test-automation/

[Grö17]
R. Grötz, D. Kukacka, N. Nikolic, Testautomation einer Microservice-Architektur – Teil 1 und 2, in: JavaSPEKTRUM, 03 und 04/2017

[ISTQB]
ISTQB Glossary, siehe:
https://www.astqb.org/glossary

[Scot16]
A. Scott, Ask Me Anything, WatirMelon, 26.5.2016, siehe:
https://watirmelon.blog/tag/testing-pyramid/

[Thought]
Consumer-driven contract testing, Technology Radar, ThoughtWorks, siehe: https://www.thoughtworks.com/radar/techniques/consumer-driven-contract-testing

. . .

Author Image

Rudolf Grötz

Author
Zu Inhalten
seit 30 Jahren in der IT unterwegs und passionierter Softwaretester, ist als Senior Technical Test Engineer bei Raiffeisen Bank International in Wien im Bereich Softwaretests tätig und lebt den Leitspruch „Testautomation is not an act, Testautomation is a habit!“ Neben Fachartikeln in diversen Magazinen versorgt er die Community auch mit Konferenzauftritten und organisiert das Vienna Agile Testautomation Meetup.

Artikel teilen