28. Oktober 2020 von Wolfgang Wünsche
Lasttests im agilen Umfeld – ein Bericht aus der Praxis
Die Ausgangssituation
Die Architektur der Anwendung besteht aus Microservices, die über REST-APIs miteinander kommunizieren. Im Backend werden Datenbanken, weitere Systeme über REST- und SOAP-APIs, aber auch proprietäre Schnittstellen angebunden. Die eingesetzte Technik ist Java mit Spring Boot. Die UI ist JavaScript-basiert und in eine Portalanwendung eingebunden.
Für die Durchführung des Lasttests ist MicroFocus LoadRunner vorgegeben. Das ehemals von HP entwickelte Tool hat bereits eine lange Historie, ist aber auch eines mit dem größten Funktionsumfang am Markt. Es bietet eine lokale Entwicklungsumgebung zum Erstellen und Testen der Testskripte und eine gehostete Laufzeitumgebung mit Lastgeneratoren zur Simulation der Zugriffe von Testusern. In der Entwicklungsumgebung ist sowohl die Aufzeichnung von UI-Tests möglich als auch die Generierung von Skripten zum API-Test ¬- zumindest theoretisch. Die Aufzeichnung der Klicks mit dem Browser durch die Portalanwendung hat sich leider in der Praxis als nicht praktikabel erwiesen. Der eingesetzte Proxy hat nicht alle HTTP-Requests aufgezeichnet, insbesondere die Tokens zur Authentifizierung wurden aufgrund der vielen Redirects zwischen verschiedenen Domains nicht zuverlässig erfasst. Die Generierung von Templates für Testskripte aus einer Schnittstellenbeschreibung funktioniert nur mit SOAP-Requests. Also mussten die Testskripte für die REST-API selbst implementiert werden.
Herausforderungen
Für die Erstellung der REST-API-Testskripte war nicht die Komplexität des Tools oder der Schnittstellen die entscheidende Hürde, sondern der Mangel an Dokumentation und Know-how für den Einsatz im Projektkontext. Organisationsbedingt stand beides nur sehr begrenzt zur Verfügung. Da das Team mit einer Pilotanwendung am neuen Prozess teilnahm, war die Anfangszeit von viel „Try and Error“ geprägt.
Die C-ähnliche Syntax, fehlende Objektorientierung und eigenwillige Datenstrukturen waren eine Anfangshürde, die sich im Laufe der Entwicklung mit einem wachsenden Baukasten an funktionierenden Script-Bausteinen kompensieren ließ. Echte Herausforderungen waren die Vorlaufzeit zur Planung und die Reservierung von Ressourcen für den Test auf der gehosteten Laufzeitumgebung und die Abstimmung mit anderen genutzten Backendsystemen. Wenn dann noch Wartungsarbeiten oder Ausfälle einzelner Lastgeneratoren dazu kamen oder schlicht Fehler durch zu großzügige Blockierung von Lastgeneratoren, dann wurde die Laufzeitumgebung von LoadRunner schnell zum Flaschenhals.
Welche Alternativen gibt es?
Der Einsatz von LoadRunner hatte sich bei diesem Kunden in der Vergangenheit bereits bewährt, erfordert aber einen relativ schwergewichtigen Prozess. Dies lief dem Gedanken der agilen Entwicklung mit kurzen Feedbackzyklen entgegen.
Als wahres Multitalent hat sich das Test-Tool SoapUI erwiesen. In der GUI können einzelne HTTP-Requests parametrisiert und getestet werden. Testcases und Testsuites erlauben die Gruppierung dieser Request und können mit weiteren Bausteinen wie Bedingungen, Schleifen und Groovy-Skripten zu Workflows ausgebaut werden. Assertions für jeden HTTP-Request erlauben die Überprüfung der erfolgreichen Ausführung. Dabei kommt auch der bereits von JUnit bekannte „grüne Balken“ zum Einsatz. Diese Testcases können dann auch als Lasttests ausgeführt werden - entweder in der GUI oder über ein Batch-Skript. Für die Eingangsdaten von Tests haben sich Excel-Dateien bewährt, auf die mit Groovy und Java-Bibliotheken einfach zugegriffen werden kann. Mit anderen Tools, wie zum Beispiel Postman, war dieser Grad der Automatisierung und Auswertung nicht erreichbar.
Der größte Nutzen liegt aber nicht in der Messung von Performanz und Durchsatz, sondern im Aufdecken von Problemen mit Nebenläufigkeit. Das wird an zwei Praxisbeispielen deutlich:
Zum einen konnte ein Ressourcenleck bei der Verwendung von org.apache.http.impl.client.CloseableHttpClient aufgedeckt werden. Es gab immer wieder den Effekt eines erschöpften HTTP-Connection-Pools beim Aufruf von Rest-APIs im Backend. Die Erhöhung der maximalen Connections im Pool kann das Problem für den Testbetrieb lösen, zögert aber den Fehler unter Umständen nur heraus. Mit Hilfe eines Lasttests mit nur zehn Test-Nutzern, der speziell auf Aufrufe zugeschnitten war, bei denen das verdächtige Backend beteiligt war, konnte das Problem nachgestellt werden. Inzwischen gehört so ein Test mit gleichzeitiger Überwachung der Connection-Pools über den Spring Boot Actuator-Endpunkt zur Entwicklung dazu.
Ein anderer Fall, der mit einem Lasttest aufgedeckt wurde, war ein unvollständiges Transaktionsmanagement. Beim Testen traten immer wieder Fehler vom Typ org.springframework.dao.OptimisticLockingFailureException auf. Der Testfall konnte dann auf zwei Testuser reduziert werden, die quasi gleichzeitig dieselbe Funktion aufriefen. Mithilfe der geloggten SQL-Statements ließ sich das Problem auf eine fehlende Annotation zur Transaktionssteuerung zurückführen. Bei einem anschließenden Test mit mehr Test-Nutzern trat der Fehler nicht mehr auf.
Für die Testdaten werden typischerweise Nutzer und Daten verwendet, die ohnehin während der Entwicklung zum Einsatz kommen. Werden für einen Lasttest mehr als ca. zehn Test-Nutzer benötigt, dann können diese über ein Testcenter mit den gewünschten Eigenschaften bestellt werden. Spezialfälle müssen aber in der Regel manuell über SQL-Skripte ergänzt werden. Dabei dienen vorhandene Daten als Vorlage.
Ausblick: Kontinuierlicher Lasttest
Im Sinne kurzer Feedbackzyklen sollte auch der Lasttest kontinuierlich durchgeführt werden. Kontinuierlich heißt aber nicht zwangsläufig als Teil des Build-Prozesses, um dessen Ausführungszeit nicht zu verlängern oder fehlschlagen zu lassen. Wenn nicht nur die Testskripte, sondern auch die zugehörigen Tools unter der Kontrolle des Teams liegen, dann spricht nichts dagegen, Lasttests auch in kürzeren Abständen, als nur zu geplanten Release-Wechseln, durchzuführen.
Ziel ist dabei nicht, die Last bis zur Kapazitätsgrenze der zu testenden Anwendung zu erhöhen. Oft sind dabei ohnehin die Prozesse zum Login oder manche Backendsysteme der limitierende Faktor für den Durchsatz. Gefragt ist eine Grundlast zusammen mit einem intelligenten Monitoring und einer Überwachung der Logs. Intelligentes Monitoring heißt, das Allokieren und die Freigabe von Ressourcen-Pools für HTTP-, Datenbank- und Queue-Connections zu überwachen, außerdem Speicher und CPU. Mit mehreren Testreihen mit verschiedenen Parametern für den Lastgenerator (Anzahl der Test-Nutzer und Dauer für den Aufbau der Last) lassen sich die Werte auch für größere Lasten hochrechnen. Oft reicht eine Einschätzung, ob das Wachstum proportional oder überproportional ist.
Wenn die Anwendung ohnehin in der Cloud läuft und eine Provisionierung von zusätzlichen Containern einfach möglich ist, dann können diese auch zur Ausführung von Lasttests genutzt werden. Damit kann man sich seinen eigenen Lastgenerator bauen, der nicht von der Leistungsfähigkeit und Netzwerkanbindung des eigenen Entwicklungsrechners abhängt. Bei Nichtnutzung wird der Container gelöscht und verursacht keine zusätzlichen Kosten.
Fazit
Mein persönlicher Favorit zum automatisierten Schnittstellentest ist eindeutig SoapUI und das wegen seiner Flexibilität. Die GUI bietet gute Möglichkeiten, einzelne API-Requests auszuführen sowie zur iterativen Erstellung von Testskripten und der Batch-Modus ermöglicht Lasttests auf einem Server. Das Haupteinsatzgebiet bleibt jedoch der Start auf dem eigenen Entwicklungsrechner. Damit lässt sich der klassische Lasttest mit LoadRunner nicht vollständig ersetzen, der zum Testen von kompletten Releases mit definierten Lasten oder der Kapazitätsgrenze eingesetzt wird. Aber statt nur zwei bis drei Mal im Jahr einen solchen Lasttest durchzuführen, haben sich die eigenen Testskripte für flexible Lasttests während der Entwicklung bewährt. Das ist ein deutlicher Fortschritt im Sinne eines agilen Entwicklungsprozesses.
Ihr möchtet gern mehr über spannende Themen aus der adesso-Welt erfahren? Dann werft doch auch einen Blick in unsere bisher erschienenen Blog-Beiträge.