Testen trotz instabiler, externer Services


von

https://www.iteratec.de/fileadmin/Bilder/News/iteratec_logo.png https://www.iteratec.de/fileadmin/Bilder/News/iteratec_logo.png iteratec GmbH

Herausforderung

Viele Softwareentwicklungsprojekte beinhalten nicht nur eine einzelne zu entwickelnde Applikation. Stattdessen bestehen sie aus mehreren einzelnen Services, die als jeweils eigene Applikation entwickelt werden. Besonders der Microservice-Ansatz baut auf viele Services.

Das Testen der Services erfolgt idealerweise automatisiert in einer Build-Pipeline. Dabei orientiert man sich üblicherweise an der Testpyramide. Nach dieser testet man zuerst die Services separat in Unit- und Komponententests, bevor man alle Services im Zusammenspiel betrachtet.

 

 

In manchen Projekten kann es vorkommen, dass die entwickelten Services mit anderen Services außerhalb des eigenen Projektkontexts kommunizieren müssen, beispielsweise weil das Projekt in einer bestehenden Anwendungslandschaft integriert werden muss. Zumeist betreuen andere Teams, die mit dem eigenen Projekt nichts zu tun haben, diese externen Services. Somit sind sie dem eigenen Einfluss entzogen.

Die externen Services müssen ebenfalls in den automatisierten Tests berücksichtigt werden. Dies ist in der Testpyramide auf der zweithöchsten Ebene „Integrationstests mit Services außerhalb des Projekts“ dargestellt. Solche Integrationstests werden in einem eigenen Schritt in der Build-Pipeline abgebildet.

In diesem Artikel geht es darum, wie Integrationstests mit externen Services gehandhabt werden können, die nicht konstant verfügbar sind. Dies kann der Fall sein, wenn die Services in einer Testumgebung laufen, die in der Ressourcenverteilung nachrangig behandelt oder regelmäßig abgeschaltet werden. Ich beschreibe drei Ansätze, wie man mit dieser Herausforderung umgehen kann: Pipelines nicht brechen lassen, Services mocken und Tests nur unter der Bedingung ausführen lassen, dass die Services erreichbar sind.

Pipeline nicht brechen lassen

Eine Möglichkeit, Integrationstests mit externen Services durchzuführen, besteht darin, die Pipeline nicht brechen zu lassen, wenn Tests fehlschlagen, die solche Systeme beinhalten.

Vorteil
Es ist egal, ob das System verfügbar ist oder nicht.

Nachteil
Kein fehlschlagender Test bricht die Pipeline. Bei dieser Lösung wäre die Pipeline auch dann grün, wenn es fehlgeschlagene Tests gibt. Somit ist nicht auf den ersten Blick ersichtlich, dass Fehler vorliegen. Problematisch dabei ist, dass Tests, die nicht wegen eines nicht verfügbaren Services fehlschlagen, ebenfalls die Pipeline nicht brechen lassen. Die Entwickler*innen müssen daher die Testergebnisse bei jedem Build im Blick behalten. Somit sollte die Option, die Pipeline nicht brechen zu lassen, nicht in Erwägung gezogen werden.

 

Services mocken

Die zweite Option besteht darin, nicht den echten externen Service zu verwenden, sondern diesen zu mocken. In Unit-Tests werden externe Services regelmäßig gemockt. Auch bei Integrationstests sind Mocks hilfreich. Hierfür kann ein Service implementiert werden, der das Verhalten und die Schnittstelle des echten Service im Rahmen der Tests nachahmt.
Dabei bewegt man sich in der Testpyramide auf der Ebene „Komponententests“ oder „Integrationstests der Services innerhalb des Projekts“, die existierende externe Services ausblenden und eben durch Mocks ersetzen.

Vorteil
Dieser Mock-Service kann in der eigenen Testumgebung laufen, wodurch die Verfügbarkeit gewährleistet ist. Tests in der Pipeline schlagen somit nicht mehr durch Nichtverfügbarkeit fehl.

Nachteil
Ein Nachteil dieses Ansatzes ist, dass mögliche Änderungen an der Schnittstelle oder am Verhalten des echten Service nicht oder spät erkannt werden und die Tests nicht mehr dazu passen. Hier kommt es auf eine gute Kommunikation mit dem Team an, das für das echte System zuständig ist.

Weiterhin kann es passieren, dass die Netzwerkverbindung des eigenen Systems zum Mock oder Fake nicht der Realität entspricht, weil beispielsweise in der Realität ein Proxy dazwischengeschaltet ist. Die Kommunikation zwischen den Systemen müssen Entwickler*innen daher separat testen.

Wir haben den Kompromiss gefunden, den Großteil der Tests mit Mock-Services und nur die nötigsten Tests mit den externen Services durchzuführen. Dabei haben wir uns zusätzlich der nachfolgenden, dritten Herangehensweise bedient.

 

Bedingtes Ausführen der Tests

Ein Ansatz, den wir in einem aktuellen Projekt verwenden, ist das bedingte Ausführen von Tests je nach Verfügbarkeit des externen Service. Dabei wird zu Beginn des Tests überprüft, ob der Service verfügbar ist. Falls ja, dann wird der Test ausgeführt. Ansonsten wird er übersprungen. Für solche Überprüfungen liefert JUnit mit sogenannten Assumptions ein passendes Werkzeug. Assumptions prüfen definierte Vorbedingungen, unter denen der Test ausgeführt werden kann. Trifft eine solche Vorbedingung nicht zu, so wird der Test übersprungen. Im Fall der externen Systeme wird vorausgesetzt, dass das System verfügbar ist. Die Prüfung findet gleich am Anfang des Tests statt und sieht ungefähr so aus:

@Test
public void testMitExternemSystem() {
    assumeTrue(„Externes System ist nicht verfügbar!“, externesSystemIstVerfuegbar());
    // eigentlicher Testcode...
}

Dabei muss die zu implementierende Methode externesSystemIstVerfuegbar() natürlich dazu geeignet sein, die Verfügbarkeit des Systems zu ermitteln. Dies kann beispielsweise durch die Abfrage einer REST-Schnittstelle geschehen, die im Erfolgsfall den HTTP-Status 200 (OK) zurückliefert. Wichtig dabei ist, dass der Zustand des externen Service bei diesem Check nicht verändert wird. Ist der Service nicht verfügbar, so schlägt die Assumption fehl und der Test wird übersprungen.

Vorteil
Bei dieser Herangehensweise wird bei Durchführung der Tests in der Buildpipeline diese nicht gebrochen, wenn der externe Service nicht verfügbar ist. Umgekehrt wird die Pipeline weiterhin gebrochen, wenn der Service zwar verfügbar ist, der Test aber fehlschlägt. Somit kann man sicher sein, dass ein fehlgeschlagener Test tatsächlich auf einen Fehler in der Implementierung hinweist.

Nachteil
Diese Lösung funktioniert nur, wenn sich die Verfügbarkeit des externen Services nicht zu kurzfristig ändert. Sonst könnte die Assumption zwar feststellen, dass der Service erreichbar ist, bis zur tatsächlichen Verwendung könnte dieser Zustand jedoch wechseln. In so einem Fall haben wir wiederum auf einen Mock-Service zurückgegriffen, der einen solchen volatilen Service ersetzt.

Ist der externe Service selten verfügbar, so führt es dazu, dass Tests, die diesen Service voraussetzen, selten ausgeführt werden können. So kann es sein, dass Fehler in der eigenen Implementierung nicht auffallen, da die entsprechenden Tests nicht ausgeführt werden. Dies sollten Entwickler*innen durch eine Überwachung der Testergebnisse überprüfen. In einer solchen Situation kann in Betracht gezogen werden, die Tests unter Einbeziehung des verantwortlichen Teams manuell durchzuführen. In unserem Fall kam das jedoch nicht in Frage, da wir vollständig auf automatisierte Tests setzen.

 

Fazit

Die Wahl des richtigen Ansatzes hängt, wie so oft, von den Umständen ab. Die Pipeline einfach nicht brechen zu lassen, ist dabei in jedem Fall zu vermeiden, da das erfolgreiche Ausführen von Tests wesentlich zur Qualität der Software beiträgt und Entwickler*innen nicht nach jedem Build nach den Testergebnissen suchen sollten.

Ist der Service nur ab und zu nicht erreichbar, so kann die bedingte Ausführung der Tests die Chancen erhöhen, Fehler zu entdecken.

Ist der Service, von dem der Test abhängt, häufig nicht verfügbar oder steht nur auf Anfrage bereit, so ist man mit einer manuellen Ausführung besser bedient.

Die Verwendung von separaten Mock-Services ist in der Teststufe „Integrationstests der Services innerhalb des Projekts" empfehlenswert. Bei sehr volatilen Services kann ein Mock jedoch auch bei „Integrationstests mit externen Services“ verwendet werden.

Diesen Artikel bewerten
 
 
 
 
 
 
 
0 Bewertungen (0 %)
Bewerten
 
 
 
 
 
 
1
5
0