Über unsMediaKontaktImpressum
Tobias Schneck 25. Oktober 2016

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: http://localhost:6911/vnc_auto.html?password=vncpassword

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

Das gesamte Ökosystem der Container-Technologie ist immer noch stark im Wandel und es ist schwer abzuschätzen, welche neuen Möglichkeiten dadurch geschaffen werden. Klar ist, dass Orchestrierungslösungen wie Kubernetes [5] oder OpenShift [21] eine immer größere Verbreitung bekommen. Ob in der Public oder Private Cloud, auch das End-2-End-Testing muss hierfür bereit sein. Das hier vorgestellte Konzept ist bereits ein guter Anfang, allerdings bei weitem noch nicht das Ende der Fahnenstange. Besonders die folgenden Punkte stehen daher beim Open-Source-Projekt Sakuli auf der Agenda:

  • 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

Traditionelle End-2-End-Testing-Ansätze haben im Hinblick auf die immer schnelleren Release-Zyklen bis hin zum Continuous-Deployment immer größere Schwierigkeiten, die Qualitätsansprüche der Anwender zu erfüllen. Gerade die Flexibilität der Container-Technologie sowie deren Skalierbarkeit, die nur durch Hardware-Ressourcen beschränkt ist, stellt das End-2-End-Testing vor neue Aufgaben. Eine Vielzahl von Testsystemen zu betreiben oder genügend parallele Test-Clients zur Verfügung zu stellen, sind die neuen Herausforderungen, denen es zu Begegnen gilt.

Wenn ein Software-Release durch das Fehlen von Testressourcen nicht durchgeführt werden kann, wird das Testing auch schnell zum Bottleneck für das gesamte Softwareentwicklungsprojekt und erhöht die wichtige Time-to-Market. Der Einsatz der Container-Technologie beim End-2-End-Testing bietet einen Ausweg, da selbst UI-Tests in echten Desktop-Umgebungen effektiv automatisierbar werden. Weiter bietet die Nutzung von Container-Images als fixe Basis für den Aufbau einer Testumgebung den Vorteil, eine sogenannte "Immutable Infrastruktur" zu schaffen. Die gewonnene Verlässlichkeit, dass ein Testsystem, beziehungsweise Test-Client, unabhängig von Ort und Zeit der Ausführung fortwährend den gleichen Startzustand besitzt, beeinflusst nicht nur positiv die Stabilität der Tests, sondern auch die Fehlersuche, da jede Umgebung lokal auf dem Entwickler-Laptop nachgestellt werden kann. Damit die verwendete Testumgebung auch möglichst der Umgebung des End-Users entspricht, ist es wichtig, echte Browser in echten Desktop-Umgebungen zu nutzen. Selbst der Rich-Client oder das zu öffnende PDF der Bestellbestätigung darf kein No-Go sein.

Die Container-Technologie in Verbindung mit Sakuli End-2-End-Tests bietet dies an. Eine Vielzahl von unterschiedlichen Umgebungen können schnell und flexibel zur Verfügung gestellt werden, was einen wesentlichen Vorteil zum traditionellen Ansatz mit dedizierten Testumgebungen in virtuellen Maschinen schafft. Zusätzlich wird es durch das festgelegte Einheitsformat der Container möglich, sehr einfach seine eigenen, womöglich knappen Ressourcen, durch das schier unbegrenzte Ressourcen-Angebot der Cloud-Provider zu erweitern. Abschließend ist festzuhalten, dass die Container-Technologie und der Ansatz der Immutable Infrastruktur nicht nur die Verteilung und den Betrieb von Softwaresystemen verändert, sondern auch für das End-2-End-Testing eine große Chance bietet, endlich mit fragilen Testumgebungen abzuschließen. Klar ist aber auch, dass die Container-Technologie nicht alle Probleme, wie die Bereitstellung von vernünftigen Testdaten, allgemeingültig lösen kann. Hier kommt es auf die Fähigkeiten des Entwicklungsteams an, sich mithilfe der vorhandenen Technologien Lösungswege zu suchen.   

Autor

Tobias Schneck

Tobias Schneck ist Mitbegründer des Open-Source-Testing-Frameworks "Sakuli" und spezialisierte sich als Java-Entwickler auf den Bereich Testautomatisierung.
>> Weiterlesen
botMessage_toctoc_comments_9210