iX 4/2019
S. 54
Titel
Verteilte Systeme III
Aufmacherbild

Microservice-Architekturen testen

Maßarbeit

Microservices bieten zahlreiche Vorteile wie Agilität, Flexibilität und technologische Freiheit. Die Komplexität des Testens nimmt allerdings deutlich zu. Ein Codebeispiel zeigt, wie man damit umgeht.

Bei Microservice-Architekturen verteilt sich die Fachlichkeit über mehrere Komponenten. Anforderungen, die zuvor in unterschiedlichen Modulen desselben Systems realisiert wurden, sind nun in Services aufgeteilt, die durch Schnittstellen, Kommunikationsprotokolle und Netzwerke entkoppelt sind. Tests, die sicherstellen, dass eine komplexe Fachlichkeit korrekt implementiert ist, müssen ganze Abschnitte der Architektur betrachten. Dazu kommt, dass jeder Service seine eigene Fachlichkeit mitbringt, die ebenfalls korrekt zu implementieren und aussagekräftig zu testen ist.

Es genügt nicht mehr, Mocks anderer Module zu verwenden, um das reibungslose Zusammenspiel zu gewährleisten. Stattdessen sind die Schnittstellen der angrenzenden Systeme zu simulieren und auf korrekte Ansteuerung zu prüfen. Testdatensätze setzen sich beispielsweise aus JSON- oder XML-Grundgerüsten mit variablen Werten zusammen. Daraus ergibt sich, dass Microservices als Pattern die Anzahl der Schnittstellenintegrationen über Kommunikationstechnologien innerhalb des Gesamtsystems im Vergleich zum Monolithen stark erhöhen. Diese veränderten Grundvoraussetzungen erfordern andere Herangehensweisen bei der technischen Umsetzung, den Werkzeugen und der Methodik im Zusammenhang mit Testing. Bekannte Herausforderungen bleiben allerdings bestehen.

Unit-Tests alleine reichen nicht mehr aus

Ein Test-driven Development Lifecycle sorgt für Clean Code (Abb. 1).

Je verteilter Systeme werden, desto klarer wird, dass Unit-Tests alleine nicht mehr ausreichen. Komponenten-, Integrations- und End-to-End-Tests waren schon immer empfehlenswert. Durch Microservices ist ihre Bedeutung deutlich gestiegen. Integrationstests, die fachliche Zusammenhänge isoliert für einen Service oder serviceübergreifend testen, werden in modernen Systemarchitekturen zur Grundlage der Sicherung von funktionaler Korrektheit. Die Herausforderungen dabei haben sich nicht verändert. Dazu gehören der Austausch von Anforderungen zwischen der Fachabteilung und den Entwicklungsteams, die Sicherstellung einer aussagekräftigen Testabdeckung sowie das Einhalten gängiger Clean-Code-Prinzipien. Methoden wie Test-driven Development (TDD) und Behavior-driven Development (BDD) decken diese Anforderungen bei richtiger Anwendung nahtlos ab.

Der TDD Lifecycle stellt sicher, dass eine aussagekräftige Testsuite entsteht, die sich an den Anforderungen der Fachabteilung orientiert. Zudem macht TDD die Einhaltung gängiger Clean-Code- und Softwarearchitekturregeln zu einer Pflichtaufgabe. Der Vorteil ist, dass die Qualität der Software dadurch kontinuierlich gepflegt und langfristig verbessert wird. Allerdings bringt TDD einen Nachteil mit sich: Tests sind ausschließlich in Quelltext formuliert. Missverständnisse bei der Umsetzung von Anforderungen in Softwaretests sind durchaus möglich und können von der Fachabteilung übersehen werden.

Mittels des Behavior-driven Development lässt sich diese Lücke schließen. Ausgangspunkt für diese Methodik ist eine ubiquitäre Sprache, wie man sie aus dem Domain-driven Design kennt. Dabei einigen sich Entwickler und Fachabteilung auf einen gemeinsamen Wortschatz mit festgelegter Bedeutung. Dieser lässt sich beispielsweise in einem Glossar pflegen und reduziert die Fehleranfälligkeit bei der textuellen Übermittlung enorm, da Begrifflichkeiten klar für beide Seiten definiert sind. Aufbauend darauf wird eine Beschreibungssprache festgelegt, in der sich technische Anforderungen allgemein verständlich formulieren lassen. Ziel ist es, dass die in der ubiquitären Sprache verfassten Anforderungen der Fachabteilung so als Softwaretest ausführbar sind. Das Beispielprojekt bedient sich dazu der Beschreibungssprache Gherkin sowie des BDD-Frameworks Cucumber (Download über ix.de/ix1904054).

BDD-Frameworks wie Cucumber gewährleisten die Aussagekraft der Testsuite. Zudem kann man die Entwicklung an den fachlichen Anforderungen ausrichten und ein Framework auf Basis der ubiquitären Sprache innerhalb einer Domäne erstellen. Dadurch kann die Fachabteilung allgemein verständliche Softwaretests schreiben. Bis jetzt kommt BDD exemplarisch mit Unit-Tests zum Einsatz. Um die Anforderungen an das System auch aus Sicht von Integrationstests verifizieren zu können, sind weitere Tools nötig. Dazu gehört das Integration-Testing-Framework Citrus, das hilft, die Systemgrenzen einzelner Services zu verlassen. Citrus ist ein Open-Source-Framework, bei dem die Verifikation der Kommunikation, der Nachrichteninhalte und das Einhalten von Prozessen und Protokollen im Vordergrund steht. Es zeichnet sich im Vergleich zu anderen Frameworks dadurch aus, dass es nicht nur auf REST, JMS oder SOAP spezialisiert ist. Dabei können die Kommunikationsprotokolle innerhalb eines Tests nahtlos ineinander übergehen.