Software-Test im Container: So können Sie Graphical User Interfaces mit Docker und Sakuli testen
Stabile und skalierbare Testumgebungen für End-2-End-Tests sind seit jeher schwer aufzusetzen und zu warten. Besonders in Kombination mit automatisierten UI-Tests stellen sie Tester und Entwickler immer wieder vor große Herausforderungen. Einen eleganten Ausweg bieten in Container verpackte Testumgebungen, die sowohl Web- als auch Rich-Clients in echten Desktop-Umgebungen testen können. Als "Immutable Infrastruktur" betrieben, wird es dadurch möglich, einen definierten Systemstand jederzeit reproduzierbar aufzurufen und Tests darin performant auszuführen.
Anforderungen an modernes End-2-End-Testing
Die Architektur eines modernen Softwaresystems orientiert sich zunehmend an den Anforderungen der Cloud. Skalierbarkeit, Flexibilität, Fehlertoleranz, Ausfallsicherheit bis hin zu Continuous Deployment sind meist die Schlagworte, die in diesem Zusammenhang genannt werden. Architektur-Trends wie Microservices [1] oder die Serverless Architektur [2] bieten hierfür praktikable Lösungsansätze. Deren technologische Umsetzung wird durch die sehr beliebte Container-Technologie Docker [3], rkt [4]) sowie deren Ökosystem mit Orchestrierungslösungen wie Kubernetes [5] oder Monitoringsysteme wie Prometheus [6] unterstützt. Durch diese Technologien werden die neuartigen Architekturkonzepte erst marktreif umsetzbar. Damit die Qualität der Software durch die neu gewonnene Flexibilität dementsprechend mit skaliert, ist es auch im Bereich des Testings notwendig, sich der neuen Architektur anzupassen und deren Technologie zu nutzen.
Die Testpyramide in Abbildung 1 definiert mehrere bekannte Bereiche des Testings. Welche davon sich besonders anpassen müssen, lässt sich anhand einer kurzen Analyse wie folgt ableiten: Auswirkungen auf das Unit-Testing sind nicht vorhanden, da ausschließlich kleine Code-Funktionalitäten wie Klassen oder Methoden getestet werden, die keine verteilten, sondern nur lokale, in sich abgeschlossene Aufgaben abbilden. In den Bereichen der Integrations- und End-2-End-Tests sieht dies anders aus, da dort die gesamtheitliche Funktionalität eines Systems von außen getestet wird. Wie das sogenannte "System Under Test" (SUT) selbst, wird auch das Testsystem durch die verteilte Architektur komplexer.
In dem Bereich der End-2-End-Tests wird im Allgemeinen das SUT über das User Interface (UI), die Systemoberfläche, gesteuert und die korrekte Veränderung des Systemzustands validiert. Beispielsweise kann ein Test die Login-Maske einer Web-Anwendung öffnen, Username und Passwort eingeben, den Login bestätigen und letztendlich überprüfen, ob der User angemeldet ist. Wichtig ist es in diesem Fall dedizierte Testdaten - wie die Login-Daten - vorzuhalten. Besitzt die Web-Anwendung beispielsweise die Eigenschaft, dass nur ein einziger gleichzeitiger Login möglich ist, muss sichergestellt werden, dass bei parallelen Tests jeweils ein Login exklusiv zur Verfügung steht. Gerade diese Parallelität stellt beim End-2-End-Testing oft eine große Herausforderung dar. Einerseits müssen Testdaten aufgesetzt und nach dem Testlauf aufgeräumt werden. Andererseits können sich die Anfangszustände des SUTs verändern. Die klassischen Systeme sind meist Stateful-Anwendungen, die aufgrund von unterschiedlichen Sessions, Benutzerverläufen oder Login-Berechtigungen unterschiedliche Oberflächen anzeigen. Diese Punkte sind beim Erstellen von stabilen End-2-End-Tests unabhängig vom eingesetzten Framework zu beachten.
Da der Aufwand durchaus nicht zu unterschätzen ist, sollten lediglich Regressionstests, die den Erhalt bereits entwickelter Funktionalität sicherstellen, automatisiert werden. Funktionale Abnahmetests, bei denen die UI-Funktionalität noch nicht final festgelegt ist, sind eher durch manuelle Tests in einen stabilen Zustand zu bringen, um sie im Anschluss daran zu automatisieren. Die Container-Technologie bietet hier generell keine allgemeingültige Lösung, sondern unterstützt lediglich das Deployment der SUT, die Parallelisierung und Skalierbarkeit der Tests. Die Schwierigkeit, dass je Test nur ein Login gleichzeitig genutzt werden kann, muss durch eigene Lösungen behoben werden. In diesem Fall könnte ein Pool an Login-Daten, die je nach Bedarf vom Testfall abgefragt werden, Abhilfe bieten. Erleichterungen schafft die Container-Technologie allerdings beim Aufbau der Testinfrastruktur, da sie folgende Vorteile mit sich bringt:
- Isolation von verschiedenen Testumgebungen in unterschiedlichen Versionen.
- Zentrales Repository für Versionierung und Verteilung verschiedener Systemversionen, unabhängig von der Technologie.
- Reproduzierbarer Aufbau von Umgebungen, zum Beispiel durch den Einsatz von Dockerfiles, Docker Compose bzw. Kubernetes Konfigurationsfiles.
- Optimiert für die Parallelisierung durch schnelle Startzeiten und optimaler Ressourcenauslastung durch den geteilten Linux-Kernel (Speicherplatz, CPU, RAM).
- Cloud-ready durch Einheitsformat für Orchestrierungsplattformen wie Kubernetes, OpenShift, Mesos oder Ranger.
Container-Technologie beim End-2-End-Testing: GUI und X-Server
Um realistische End-2-End-Tests zu ermöglichen und sich nicht auf Headless-Implementierungen eines Browsers zu verlassen, wird ein echtes Display mit Desktop-Oberfläche benötigt. Hierfür gibt es innerhalb der Container-Technologie zwei technische Möglichkeiten:
1) X-Forwarding
Ein gestarteter Container benutzt ein Display mit entsprechendem Window-Manager von außen, indem der X11 Unix Socket /tmp/.X11-unix in die Laufzeitumgebung des Containers gemountet wird. Durch den unten folgenden Befehl wird beispielsweise das im Docker-Container verpackte Grafikprogramm Inkscape [7] mit dem lokalen X11-Display des Hosts gestartet. Hierbei wird das gleiche Prinzip genutzt wie bei dem SSH X-Forwarding [8].
### start the docker container with x-forwarding docker run -it -e DISPLAY=$DISPLAY -v /tmp/.X11-unix:/tmp/.X11-unix:rw rasch/inkscape
2) Xvnc-Server
Eine weitere Möglichkeit, eine echte Oberfläche innerhalb des Containers zu nutzen, ist eine separate Instanz eines X-Servers im Container selbst zu erzeugen. Damit auch von außen ein Zugriff auf die Oberfläche des Containers möglich ist, bietet es sich an, eine Implementierung eines Xvnc-Servers [9] zu nutzen. Der in dem Beispiel verwendete Container consol/ubuntu-xfce-vnc benutzt den weit verbreiteten vnc4server [10]. Da ein Xvnc-Server lediglich eine leere Desktop-Oberfläche bereitstellt, wird ein sogenannter Window Manager [11] benötigt, damit eine vollwertig bedienbare Oberfläche entsteht. Im Docker-Image consol/ubuntu-xfce-vnc wurde das relativ leichtgewichtige Xfce4 [12] als Window Manager benutzt. Details zum Aufbau des Images befinden sich unter github.com/ConSol/docker-headless-vnc-container [13]. Sobald der Container mit docker run gestartet wurde, ist es möglich, sich via VNC oder einer HTML-Seite auf die Oberfläche zu verbinden und dort alle Aktionen wie auf einem gewöhnlichen Desktop durchzuführen.
### start the docker container with VNC interface # connect via VNC: Host: localhost, Port: 5911, Password: vncpassword # connect via URL: localhost/vnc_auto.html docker run -it -p 5911:5901 -p 6911:6901 consol/ubuntu-xfce-vnc
Um das Ziel zu erreichen, einen End-2-End-Test headless in einer Continuous-Integration-Pipeline auszuführen, bietet sich die Xvnc-Server-Lösung an, da diese völlig unabhängig vom Host-System genutzt werden kann. Als weitere Komponente wird ein Test-Framework benötigt. Im Bereich der End-2-End-Tests sind Web-Testing-Frameworks wie Selenium [14] weit verbreitet. Anhand des von einer HTML-Seite abgeleiteten Document-Object-Models (DOM), können alle enthaltenen Elemente auf Basis von Namen, IDs oder CSS-Klassen identifiziert werden. Über diese sogenannten Identifier wird die Seite innerhalb des Browsers ferngesteuert oder mit Werten befüllt. Der neue, veränderte Zustand wird darauffolgend mit Asserts validiert. Zum Beispiel wird geprüft, ob im Textfeld der Hinweistext des vorher ausgewählten User-Login-Felds richtig angezeigt wird.
Besteht allerdings die Anforderung, Rich-Clients oder ein generiertes PDF zu validieren, stoßen reine Web-Testing-Tools an ihre Grenzen. Hier bieten End-2-End-Frameworks wie Sakuli [15] einen Ausweg, da zusätzlich zu den DOM-basierten Elementen auch Inhalte außerhalb des Browserfensters zur Verfügung stehen. Konkret simuliert Sakuli zusätzlich zur Webseiten-Manipulation, User-Aktionen per Maus und Tastatur auf der nativen UI des Betriebssystems. Die Navigation darauf erfolgt mittels Bildmuster, die im Testfall definiert werden und während der Testausführung mit der aktuellen Anzeige verglichen werden. So kann zum Beispiel ein nativer Rich-Client, unabhängig von der Implementierungstechnologie, im Test geöffnet und ferngesteuert werden.
Das folgende Code-Beispiel zeigt einen Test-Case, der im Chrome-Browser über die "PDF speichern"-Funktion ein PDF speichert, es in einem neuen Browser-Tab öffnet und abschließend validiert, ob das PDF alle erforderlichen Elemente enthält. Dieser Test gewährleistet somit, dass im PDF alle gewünschten Report-Elemente enthalten sind und nicht aufgrund eines CSS- oder Darstellungs-Fehlers Elemente fehlen.
Code-Beispiel UI-Test "PDF Report":
//open bakery application _navigateTo("http://bakery-web-server:8080/bakery/"); _isVisible(_heading1("Cookie Bakery Application")); _highlight(_heading1("Cookie Bakery Application")); //open print preview env.type("p", Key.CTRL); screen.find("save_button").highlight().click().type(Key.ENTER); //open pdf in new tab and validate env.type("tl", Key.CTRL).paste(getPDFpath()).type(Key.ENTER); screen.waitForImage("pdf_place_order.png", 5).highlight(); [ "pdf_blueberry.png", "pdf_caramel.png", "pdf_chocolate.png" ].forEach(function (imgPattern) { screen.find(imgPattern).highlight(); });
Wird auf die Web-Komponente von Sakuli gänzlich verzichtet, können selbst native Rich-Clients wie eine Swing-Anwendung oder ein SAP-Rich-Client problemlos getestet werden. Ein weiteres Testszenario wäre, das Zusammenspiel zweier Anwendungen, wie die Web-Eingabe eines Anfrageformulars mit Übermittlung an das Backend eines CRM-Systems mit Rich-Client zu testen.
Docker-Images als Test-Clients nutzen
Zusammen mit der bereits beschriebenen Xvnc-Server-Umgebung, wird Sakuli zusätzlich zu dem nativen Installer (Windows, Linux, Mac) auch als Docker-Image zur Verfügung gestellt. Das Image enthält bereits die Browser Chrome und Firefox vorinstalliert. Für die Testerstellung wird generell sowohl Java als auch JavaScript unterstützt. Für die Nutzung in Docker-basierten CI-Umgebungen zeigt die Erfahrung, dass die JavaScript-basierten Tests bevorzugt genutzt werden, da dort kein Kompilierungsvorgang notwendig ist und für Nicht-Java-Entwickler der Einstieg leichter fällt. Das Open-Source-Framework bietet derzeit für die unterschiedlichen Umgebungen die folgenden Docker-Images an:
Verfügbare End-2-End-Testing Docker-Images
Docker-Image | Umgebung |
---|---|
consol/sakuli-ubuntu-xfce | Ubuntu 14.04, Xfce4, Tests in JavaScript (Rhino Engine) |
consol/sakuli-ubuntu-xfce-java | Ubuntu 14.04, Xfce4, Java 8, Maven, TestNG-Test |
consol/sakuli-centos-xfce | CentOS 7, Xfce4, Tests in JavaScript (Rhino Engine) |
consol/sakuli-centos-xfce-java | CentOS 7, Xfce4, Java 8, Maven, TestNG-Test |
Als Einstiegspunkt für die Erstellung eigener Tests bietet es sich an, eines der Beispiel-Projekte von github.com/ConSol/sakuli-examples [16] zu kopieren und auf die eigenen Bedürfnisse anzupassen. Das Tutorial Sakuli - First Steps [17] bietet hier gute Unterstützung bei der Testerstellung. Nachdem beispielsweise die JavaScript-basierten Tests erstellt sind, können diese mithilfe von Docker Compose [18] sehr einfach vom lokalen Dateisystem in den Sakuli-Container gemountet und mit dem Befehl docker-compose up zur Ausführung gebracht werden. Auch parallele Testausführungen sind damit einfach aufzusetzen, wie das docker-compose.yml zeigt:
# Run test in two separated environments: Ubuntu/Firefox and CentOS/Chrome sakuli_test_ubuntu_firefox: image: consol/sakuli-ubuntu-xfce volumes: - ./example_xfce:/opt/test ports: - 5911:5901 - 6911:6901 command: ["run /opt/test"] sakuli_test_centos_chrome: image: consol/sakuli-centos-xfce volumes: - ./example_xfce:/opt/test ports: - 5912:5901 - 6912:6901 command: ["run /opt/test -browser chrome"]
Applikationen, die dem Microservices-Architektur-Pattern folgen, können besonders von kontinuierlichen End-2-End-Tests profitieren, da die Funktionalität aus Anwendersicht nicht von einer, sondern von einer ganzen Reihe an Komponenten abhängt. Die Anwendung in Abbildung 5 nutzt zum Beispiel separate Services für das Bestellen, Produzieren und Reporten von Bäckerei-Artikeln. Ein End-2-End-Test muss in diesem Szenario prüfen, dass der Service webapp eine Bestellung annimmt, die parallelen worker diese verarbeiten, der report Server die abschließende Mitteilung empfängt und der PDF-Report alle produzierten Artikel beinhaltet.
Durch die Flexibilität für Web-basierte Clients die DOM-basierten Identifier zu nutzen und nahtlos zusätzliche UI-basierte Aktionen, wie PDFs zu öffnen, wird es mithilfe eines Test-Frameworks möglich nahezu jeden Use-Case abzubilden. Um den erstellten Test nun kontinuierlich zur Ausführung zu bringen, reicht allerdings das reine Nutzen von Docker Compose [18] nicht aus. Wird zum Beispiel eine Docker-Compose-Umgebung mehrfach nacheinander mit docker-compose up gestartet, werden entgegen des vermuteten Verhaltens die Container nicht frisch gestartet, sondern nur "wiederbelebt". Eine absolute Nachvollziehbarkeit und Reproduzierbarkeit kann nur erreicht werden, wenn die Container bei jeder Ausführung von Scratch starten. Aufgrund dessen ist es notwendig, Docker-Compose in entsprechende Helfer-Skripte zu wrappen, die dieses Verhalten sicherstellen. Diese können dann ebenfalls dazu genutzt werden, die Tests aus einem CI-System wie Jenkins [19] zu starten. Das komplette Beispiel mit lauffähigem CI-Build ist auf github.com/toschneck/sakuli-example-bakery-testing [20] zu finden.
Ausblick
- Vorbereitung der Docker-Images für die Verwendung mit Kubernetes und OpenShift.
- Evaluierung und Umsetzung eines Windows-Containers [22] für End-2-End-Tests.
- Vereinfachung der Erstellung und Pflege der Sakuli-Tests (Web-UI für die Test-Verwaltung, Video-Aufzeichnung im Fehlerfall, uvm.).
- Selenium [14] als zweite Alternative für Web-Testing.
Fazit
- Microservices
- Serverless Architektur
- Docker
- rkt
- Kubernetes
- Prometheus
- Inkscape
- SSH X-Forwarding
- Xvnc-Server
- vnc4server
- Window Manager
- Xfce4
- github.com/ConSol/docker-headless-vnc-container
- Selenium
- Sakuli
- github.com/ConSol/sakuli-examples
- Sakuli - First Steps
- Docker Compose
- Jenkins
- github.com/toschneck/sakuli-example-bakery-testing
- OpenShift
- Windows-Container