Kontrollierte Risiken: Agile Integration erfordert automatisierte Tests

Blogserie | Agile Integration | #4

Sowohl die Entwicklung als auch die Integration von Services werden zunehmend agil und dezentralisiert. Systeme befinden sich dabei in stetigem Wandel und müssen trotzdem jederzeit einsatzbereit sein. Damit sich mit der höheren Geschwindigkeit und Flexibilität nicht genauso schnell Fehler und Inkompatibilitäten ergeben, ist ein automatisiertes Vorgehen beim Testen unerlässlich.

Autor: Benjamin Bürgisser

In der alten IT-Welt  mit ihren fixen Projekt-Phasen fand das Testing  meist strikt nach der Entwicklung und unmittelbar vor der Auslieferung statt. Oft kam dabei ein separates Team zum Einsatz, welches nicht in die Entwicklung involviert war. Einige manuelle End-to-End Tests später war dann klar: Diese Lösung funktioniert noch nicht. 

Mit zunehmender Geschwindigkeit rücken diese altgedienten Software-Lebenszyklen und festgelegte Release-Daten in den Hintergrund. Planung, Entwicklung, Test und Auslieferung eines Service geschehen nicht mehr in voneinander getrennten Phasen, sondern werden kontinuierlich und oft von einem einzigen DevOps-Team durchgeführt (siehe Abbildung 1). Und da sich einzelne Services und Softwarekomponenten selten in einem Vakuum befinden, wird die agile Integration immer wichtiger. Systeme und deren Komponenten entwickeln sich unabhängig voneinander weiter, befinden sich in unterschiedlichen Entwicklungsstadien und müssen doch stets miteinander interagieren können. Um mehr über die agile Integration und ihre Erfolgsfaktoren zu erfahren, empfehlen sich an dieser Stelle die Beiträge von Thomas Stuber: Formen der agilen Integration - eine Auslegeordnung und 5 Learnings zur agilen Integration. Ein besonders wichtiger Faktor für die agile Integration ist deren Dezentralisierung. Wie diese durch Integration als Microservice erreicht werden kann, ist dem folgenden Beitrag von Oliver Faust zu entnehmen: Integration as a Microservice.

Abbildung_1.png
Abbildung 1: Altes Vorgehen nach Wasserfallmodell mit fixen Phasen und separaten Teams gegenüber dem neuen agilen Vorgehen in DevOps-Teams.

Die Herausforderungen der Agilität 

Die Vorteile des agilen Vorgehens liegen auf der Hand: Softwaresysteme sind schneller einsatzbereit, mögliche Defizite werden früher erkannt, veränderte Anforderungen und neue Kundenwünsche werden sofort berücksichtigt und Fehler sind schneller beseitigt.

Doch genau diese Geschwindigkeit birgt auch Gefahren. Ohne geeignete Massnahmen befinden sich nicht nur die gewünschten Funktionen und Verbesserungen schneller in der produktiven Umgebung, sondern auch unerwünschte Nebeneffekte und Bugs. Die Beseitigung eines Fehlers verursacht einen neuen an einer anderen Stelle, die Integration eines neuen Features macht ein anderes unbrauchbar und die Anpassung der Schnittstelle für Service A verursacht einen Breaking Change für den Service B.

In der neuen agilen IT-Welt  nimmt das Testing deshalb eine zentrale Rolle ein. Nur durch Tests kann sichergestellt werden, dass Software auch bei kontinuierlicher Veränderung stets ausführbar bleibt und auch weiterhin korrekt mit anderen Systemen interagiert. Durch separate Testing-Teams und manuell ausgeführte End-to-End Tests lässt sich ein solches Vorhaben aber kaum mehr bewerkstelligen. Neue Lösungen sind gefragt und getreu dem agilen Ansatz sind auch diese schnell angepasst, stets auf dem aktuellen Stand und jederzeit ausführbar.

Testen in der agilen Welt: Die richtigen Tests automatisieren

Tests sollten also stets aktuell und relevant sein, Probleme früh aufdecken und vor allem bei jeder Änderung an der Code-Basis automatisch und schnell ausgeführt werden. Ein Code, der nicht lauffähig ist, Fehler enthält oder zu Breaking Changes bei den Abnehmern und Umsystemen führt, sollte gar nicht erst ausgeliefert und produktiv geschaltet werden können.

Es ergeben sich dadurch im Wesentlichen zwei Aufgaben:

  1. Die richtigen Tests zu schreiben
  2. Tests automatisiert nach jeder Code-Änderung auszuführen

Die Testpyramide zeigt welche Tests automatisiert werden sollten

Um die richtigen Tests zu schreiben kann die Testpyramide zu Rate gezogen werden. Diese ist in Abbildung 2 ersichtlich und empfiehlt eine hierarchische Vorgehensweise, um Probleme möglichst früh zu erkennen. Testverfahren auf den unteren Ebenen der Pyramide weisen die höchste Anzahl an Testfällen auf, werden früh in der Entwicklung erstellt und regelmässig ausgeführt. Tests auf den oberen Ebenen folgen erst später, sind aufwändiger, schwieriger zu automatisieren und üblicherweise in kleinerer Zahl vorhanden.
 

Abbildung_2.png
Abbildung 2: Die Testpyramide sagt aus, in welcher Reihenfolge wie viele Tests geschrieben und automatisiert werden sollten.

Unit Tests bilden das Fundament der Pyramide

Unit Tests sind simple, einfach zu automatisierende Tests, welche die einzelnen Bestandteile einer Software (Units) isoliert voneinander prüfen. Abhängigkeiten werden durch Mocks ersetzt, um jegliche Einflüsse und Folgefehler anderer Units von der getesteten Einheit fernzuhalten. Bei Unit Tests handelt es sich um White-Box-Tests, wir kennen also die innere Funktionsweise unseres Systems und dessen Bestandteile.

Diverse Tools können bei automatisierten Unit Tests unterstützen. Hier die zwei wichtigsten:

  • Test-Frameworks bilden die Grundlage, um Tests direkt innerhalb der Code-Base zu schreiben und auf immer gleiche Weise ausführen zu können. Tests werden wiederholbar und automatisierbar. Empfehlung: In der Java-Welt haben sich JUnit und TestNG bewährt.
  • Mocking-Frameworks erlauben das einfache Mocken von Units. Abhängigkeiten werden somit automatisiert und durch eine Dummy-Implementation ersetzt. Empfehlung: Eines der meistgenutzten Mocking-Frameworks der Java-Welt ist Mockito, welches hervorragend mit den obengenannten Test-Frameworks zusammenarbeitet.

Integrationstests ermöglichen die fehlerfreie agile Integration

Getestete und korrekt funktionierende Komponenten (Units) sind die Voraussetzung für Integrationstests, bei denen das korrekte Zusammenspiel voneinander abhängiger Services eines Gesamtsystems sichergestellt wird. An dieser Stelle wird der einzelne Service als Black-Box betrachtet. Es zählt also nicht mehr die korrekte innere Funktionsweise, die bereits durch die Unit Tests sichergestellt wurde. Stattdessen betrachten wir die Interaktion unseres Service mit anderen Services über die jeweiligen Schnittstellen. 

Auch hier bieten sich diverse Tools als Unterstützung an:

  • Integrationstest-Frameworks erlauben es mit einfach automatisierbaren Tests sowohl Clients als auch Umsysteme zu simulieren. Dadurch lassen sich bei Aufrufern und aufgerufenen Services Prüfungen vornehmen und die korrekte Verhaltensweise des zu testenden Service sicherstellen, ohne dass die realen Umsysteme verfügbar sein müssen. Dies nimmt den Integrationstests viel von ihrer eigentlichen Komplexität und macht sie schnell und automatisiert ausführbar. Empfehlung: Citrus hat sich für diese Aufgabe bei mehreren Kunden bewährt. Das Spring-basierte Framework ist Open Source und unterstützt von REST, SOAP, JMS und Kafka bis hin zu FTP und Mail-Servern alle Protokolle, die das Herz eines Java Entwicklers begehrt.
  • Contract-Test-Frameworks basieren auf einem Vertrag zwischen einem konsumierenden (Consumer) und einem produzierenden System (Producer). In diesem Vertrag wird Schema und Verhalten der Schnittstelle des Producers festgehalten. Darauf aufbauend können Mocks für den Consumer und Tests für den Producer generiert werden. Ein Consumer ist dadurch nicht mehr auf die Verfügbarkeit des realen Producers angewiesen. Gleichzeitig wird der Producer durch die generierten Tests darüber informiert, wenn eine Änderung den Vertrag bricht, sprich einen Breaking Change für den Consumer herbeiführen würde. Empfehlung: Wer mit verschiedenen Sprachen und Frameworks arbeitet, ist mit dem Pact-Framework hervorragend bedient. Es unterstützt von Java und JavaScript bis Perl und Go jede erdenkliche Technologie und bietet nützliche Zusatzfeatures wie den Pact Broker, der das Verwalten von Contracts erlaubt. Wer spezifisch mit dem Java Spring-Framework arbeitet, sollte sich aber auf jeden Fall Spring Cloud Contracts ansehen, da es perfekt in Spring integriert ist.

Pipelines führen Tests automatisch aus

Nachdem die Tests entsprechend der Testpyramide geschrieben sind, geht es nun darum diese automatisch auszuführen und damit zu verhindern, dass ein ungetesteter und potenziell fehlerhafter Code in den produktiven Build gelangt. Auch hier unterstützen verschiedene  Tools:

  • Build-Management-Tools wie Maven oder Gradle führen beim Bauen der Software die Tests automatisch aus und schliessen den Build nur dann erfolgreich ab, wenn auch alle erfolgreich durchgelaufen sind.
  • CI/CD-Tools und Automationsserver ermöglichen es, Tests und Builds an einem zentralen Ort automatisch auszuführen und den fertigen Build mittels festgelegter Schritte in einer Pipeline sofort  auszuliefern und produktiv zu schalten. Beispiele solcher Tools sind Jenkins, GitLab CI/CD, Atlassian Bamboo oder Cloud Lösungen wie Azure DevOps Pipelines (Abbildung 3). 
Abbildung_3.png
Abbildung 3: Mit CI/CD-Tools wie Azure DevOps Pipelines können repetitive Aufgaben wie Build, Test und Deployment automatisiert werden.

Fazit: Breaking Changes verhindern und Qualität schaffen

Tests und Testautomatisierung als zentralen Erfolgsfaktor der agilen Integration mehr in den Fokus zu rücken, lohnt sich langfristig. Denn was zu Beginn nach Mehraufwand aussieht, zahlt sich später sowohl bei Qualität und Betriebsaufwand als auch Agilität bei Entwicklung und Integration mehr als aus. 

Konkret ermöglichen wir durch automatisierte Tests:

  • Die frühe Erkennung von Problemen in der Entwicklung
  • Die schnelle und fehlerfreie Integration neuer Komponenten und Features (agile Integration)
  • Bessere Qualität günstig sicherzustellen, da aufwändige Test-Phasen und separate Testing-Teams weitgehend eliminiert werden
  • Software und Systeme in stets ausführbarem und produktivem Zustand zu halten …
  • … und damit eine schnellere Time-to-Market zu gewährleisten

Um diese Ziele zu erreichen, können wir folgende Massnahmen umsetzen:

  • Automatisierbare Tests gemäss der Testpyramide schreiben
  • Auf Contracts setzen, um Breaking Changes zu verhindern
  • Unit Test- und Integrationstest-Frameworks wie JUnit, Citrus und Pact einsetzen
  • Cloud basierte Automatisierungslösungen wie Azure DevOps anschaffen, um Tests skalierbar auszuführen

 

Blogserie:

#1 | Formen der agilen Integration - eine Auslegeordnung

#2 | 5 Learnings zur agilen Integration

#3 | Integration as a Microservice

#4 | Kontrollierte Risiken: Agile Integration erfordert automatisierte Tests

#5 | Agile Integration mit Red Hat leicht gemacht (coming soon)

Ihr ipt-Experte

Ich freue mich auf Ihre Kontaktaufnahme

DevOps Vorgehen kostenlos anfordern

Sie möchten Ihr Unternehmen mit DevOps voranbringen, wissen aber noch nicht genau, wie Sie es angehen sollen?