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