Wie automatisierte Tests in den Deployment-Prozess integriert werden können #2 – Einbinden eines Testframeworks in die GitLab-Pipeline

11.10.2021
Marvin Sommer, Simon Damrau

Im ersten Teil dieser Beitragsreihe haben wir uns die Herausforderungen und Möglichkeiten von Softwaretests in agilen Projekten angesehen. Eine dieser Möglichkeiten stellt das automatisierte Testen von Nutzeroberflächen zeitnah zur Fertigstellung von Entwicklungsinkrementen dar. Diese Inkremente werden in der Regel über eine sogenannte Deployment-Pipeline erstellt, in der ein:e Entwickler:in eine Änderung pusht und damit eine Kette an Build- und Testprozessen anstößt. Laufen diese Prozesse fehlerfrei durch, wird die Änderung auch für das restliche Team veröffentlicht. Auch automatisierte Oberflächentests lassen sich in eine solche Pipeline einbinden. Am Beispiel der Gitlab Continuous Integration (GitLab CI) werden wir zeigen, wie sich das realisieren lässt.

TESTEN MIT DER GITLAB CI

GitLab bietet die Möglichkeit, Änderungen am Projekt nachzuvollziehen und zu historisieren. Dazu wird das Projekt in einem git-Repository abgelegt, von dem sich jede:r Entwickler:in eine Kopie in die lokale Entwicklungsumgebung herunterladen kann. So können lokal Änderungen vorgenommen und später über einen sogenannten Push in das git Repository eingefügt werden, sodass sie auch allen anderen Entwickler:innen zur Verfügung stehen. Bei einem Push kann dazu automatisch eine Liste von Aufgaben angestoßen werden. Über diese Aufgaben, die in einer Pipeline nacheinander ablaufen, können z. B. Tests, Build-Prozesse und letztendlich das Deployment, also die Bereitstellung, ausgeführt werden. Die Grundlage einer Aufgabe bildet in der Gitlab CI stets ein Container Image. So ist die Funktionsweise von GitLab unabhängig von dem verwendeten Tool eines Jobs (bspw. Maven oder Docker), da aus der Sicht von Gitlab lediglich generische Container verwaltet und Skripts in diesen ausführt werden.
Bevor ein neues Inkrement der Software veröffentlicht wird, sollte es natürlich getestet worden sein. Sonar- und Unit-Tests sind bereits in vielen Deployment-Pipelines ein etablierter Bestandteil und prüfen die Funktionalität des Quellcodes selbst. Sie decken somit nur einzelne Module und Klassen ab und spiegeln nicht wider, was die Endnutzer:innen sehen und wie sie die Anwendung bedienen. An dieser Stelle stehen die GUI-Tests, die sich ebenso in eine Pipeline integrieren und somit bei jedem Push ausführen lassen. Durch diese Integration in den Deployment-Prozess kann jedes Software-Inkrement bereits automatisiert getestet werden, ohne dass die Tests manuell getriggert werden müssen. Damit an dieser Stelle immer die soeben gepushte Version der Anwendung getestet wird, muss sie dediziert gebaut, gestartet und der Testumgebung zur Verfügung gestellt werden. Dasselbe gilt für etwaige Datenbanken und weitere externe Dienste, die das Programm benötigt. Die Gitlab CI bietet dafür an, sogenannte Services für einen Job zu definieren. Ein Service ist in der Essenz lediglich ein weiterer Container mit einem bestimmten Image, den das Tool mit dem Build Container verbindet, sodass diese über das Netzwerk miteinander kommunizieren können. Das Testframework wird als Build-Container gestartet und bindet sowohl die soeben gebaute Anwendung als auch die benötigte Datenbank als Service ein. Es führt dann alle gewünschten Tests aus, stellt die entstehenden Reports als Download bereit und liefert als Rückgabewert den Status Erfolg oder Fehlschlag. Hierfür lassen sich auf Wunsch auch mehrere Gruppen von Tests parallel ausführen, um die Gesamtlaufzeit der Pipeline zu verringern. Dabei gibt jede Testgruppe ihren eigenen Status zurück und stellt Downloads separat bereit.
Es gibt Testframeworks, die es darüber hinaus erlauben, die Testergebnisse automatisch in einer Datenbank zu sammeln. Sie bieten damit eine einfache Möglichkeit, den zeitlichen Verlauf der Pipeline-Erfolge darzustellen. Dabei wird nach jedem ausgeführten Test ein Eintrag in eine Datenbank geschrieben, der Auskunft darüber gibt, wie lange der Test dauerte, wann er gestartet wurde und welche Testschritte erfolgreich waren und welche nicht. Diese Daten können dann zur Auswertung der Gesamtheit der Tests genutzt werden und bieten im Gegensatz zu den flüchtigen Pipelines einen historischen Überblick.

Qualitätssicherung von Anfang an

Für ein sinnvolles und effektives Testen in der Continuous Integration ist es wichtig, bereits fertiggestellte Tests von Anfang an in die Pipeline zu integrieren. Werden zuerst viele Tests gesammelt, bevor das Testframework für die Pipeline eingerichtet wurde, kann es passieren, dass die bestehenden Tests zunächst fehlschlagen. Das ist darin zu begründen, dass die Pipeline unter Umständen eine andere Umgebung darstellt als es die Rechner der Test-Entwickler:innen tun. Läuft das Testframework in einem Kubernetes-Pod, liegt i. d. R. ein Linux-System zugrunde. Während der Entwicklung kann aber z.B. auch auf Windows oder macOS gearbeitet werden. Besonders bei diesen beiden Betriebssystemen ist darauf zu achten, dass sie mit zwei verschiedenen Tastaturlayouts arbeiten. Das bedeutet unter anderem, dass einige häufig durchzuführende Aktionen auf unterschiedlichen Shortcuts liegen. Ein Beispiel dafür ist das Markieren eines ganzen Texts via STRG+A auf Windows bzw. COMMAND+A auf MacOS. Bei solchen Abweichungen empfiehlt es sich, für bestimmte Tastenanschläge Variablen zu nutzen, die je nach benutztem Betriebssystem unterschiedlich belegt werden. Dadurch werden die Tests in diesem Punkt unabhängig vom verwendeten Betriebssystem.
Ein weiterer wichtiger Aspekt bei der Ausführung in der Pipeline ist, dass dort gerne auf aufwendige virtuelle Maschinen (VM) mit einer grafischen Benutzeroberfläche verzichtet wird, wodurch alle Oberflächentests im Headless-Modus laufen müssen. Das bedeutet, dass der Browser, über den die zu testende Webanwendung aufgerufen wird, ohne eine grafische Nutzeroberfläche läuft. Während der Entwicklung kann diese hingegen verwendet werden. Besonders bei Funktionalitäten wie Up- und Download fällt das auf: Die Fenster für die Dateiauswahl beim Upload oder den Speicherort beim Download, werden vom Betriebssystem zur Verfügung gestellt. Diese gibt es im Headless-Modus daher nicht. Hier müssen Dateipfade direkt an das Upload-Feld gesendet und für Downloads ein Standard-Pfad angegeben werden. Bei letzterem ist wieder darauf zu achten, dass sich die Dateisysteme verschiedener Betriebssysteme unterscheiden können. Alternativ kann ggf. die API der Anwendung genutzt werden, falls diese entsprechende Endpunkte bereitstellt.
Eine Problematik, die nicht nur beim ersten Ausführen in der Pipeline, sondern auch zufällig auftreten kann, stellen Race Conditions dar. Hier kann es vorkommen, dass das Testframework schneller ist als die zu testende Anwendung. Das führt dazu, dass beispielsweise versucht wird, ein Feld zu füllen oder ein Element zu klicken, das noch nicht sichtbar ist, weil die Seite gerade noch lädt. Generell ist es von Vorteil, nach Möglichkeit keine harten Wartezeiten einzubauen, sondern auf weiches Warten zu setzen. Bei harten Wartezeiten wird im Test ein Wartebefehl aufgerufen, der strikt eine angegebene Zeit abwartet bevor der Test fortgeführt wird. Weiches Warten nutzt ein dynamisches Wartekriterium wie z.B. das Warten auf die Existenz eines bestimmten Elements. Dabei wird nur eine Obergrenze angegeben und ansonsten die zu wartende Zeit nicht statisch festgelegt. Wird diese Obergrenze überschritten, schlägt der Schritt fehl. Der Test wird daher nur dann weitergeführt, wenn das Wartekriterium erfüllt wurde. So können Race Conditions zuverlässig verhindert werden.

FAZIT: LANGFRISTIG GUTE SOFTWARE-QUALITÄT DURCH TESTAUTOMATISIERUNG

Durch die Integration der Testautomatisierung in die Deployment-Pipeline kann die entwickelte Software bei jeder neuen Änderung automatisch getestet werden. Das ist sowohl im Laufe der Entwicklung als auch in Produktion ein großer Bonus. Ein Testframework lässt sich hierfür als Basis-Image in eine Pipeline einbinden und kann dort mehrere Testgruppen gleichzeitig ausführen. Während der Entwicklung findet für jedes auf den Development-Branch gepushte Feature bereits Qualitätssicherung statt und die Tests wachsen gemeinsam mit der Anwendung. Diese Regelmäßigkeit nimmt besonders in agilen Projekten einen großen Stellenwert ein, wird in Bezug auf das Testen jedoch häufig unterschätzt. Durch das Ausführen der Oberflächentests in der Pipeline wird die Software stetig explizit geprüft. Das verhindert ein Ansammeln von Fehlern, die von Komponententests nicht abgedeckt werden können. Diese Früherkennung ermöglicht eine schnelle Reaktion und beugt somit dem verspäteten Auffallen von Fehlern nach der Entwicklung oder gar beim Kunden vor.
Nehmen die Änderungen, die am System vorgenommen werden, mit der Zeit ab, werden auch die Testausführungen seltener. Dann kann z.B. durch einen Nightly-Build, d.h. eine nächtliche automatische Ausführung der Pipeline und somit auch der Tests, eine regelmäßige Prüfung der Software realisiert und Fehler weiterhin zeitnah aufgedeckt werden.
 

Die Autoren:

Marvin Sommer arbeitet als Berater und Testdesigner bei der viadee Unternehmensberatung AG unter anderem an der Konzeption und Umsetzung von automatisierten Tests.

Simon Damrau arbeitet als Test Designer und Test Analyst bei der viadee Unternehmensberatung AG. In seiner Rolle als Berater arbeitet er an der Analyse, Konzeption und Um

0 Kommentare

Einen Kommentar abschicken

Bitte melden Sie sich an, um einen Kommentar zu hinterlassen.