© Arak Rattanawijittakorn/Shutterstock.com
Teil 3: Integrationstests und Ende-zu-Ende-Tests in der Praxis

Von Anfang bis Ende


Im vorigen Artikel der Serie haben wir uns mit Tests auf der Komponentenebene befasst. Diese befinden sich in der Testpyramide weit unten, laufen schnell durch und weisen einen hohen Isolationsgrad auf. Zudem stellen sie die breite Masse dar, und man sollte auf sie am allerwenigsten verzichten. Leider findet man mit ihrer Hilfe nicht alle potenziellen Fehler in einer Anwendung. Das liegt daran, dass eine typische Anwendung einen komplexen Zustand aufweist, der häufig in unterschiedlichen Komponenten materialisiert ist.

Zu diesem verteilten Zustand kommt nun auch noch das Zusammenspiel mehrerer Komponenten, die ihrerseits das eigene Verhalten auf Basis des Zustands anpassen können oder Einfluss auf den Zustand nehmen.

Ein relativ einfaches Beispiel für diesen Zusammenhang ist ein Datumsfeld in der Anwendung. In das Feld kann ein Datum eingetragen werden. Andererseits kann ein Datum dort auch angezeigt werden, wenn es (vor-)ausgewählt wurde. Kommt als alternatives Eingabeverfahren noch ein Kalender-Datepicker dazu, der seinerseits sowohl Eingabe als auch Anzeige umsetzt, kann es zu Wechselwirkungen kommen, die zu einer fehlerhaften Datumsauswahl führen.

Es ist also leicht zu sehen, dass das Testen von Komponenten in Isolation nur drei Viertel der Miete ausmacht – wir benötigen noch Integrationstests bzw. Ende-zu-Ende-Tests. Bei Integrationstests werden umfangreichere Teile der Anwendung und ihr Zusammenspiel, jedoch nicht die gesamte Anwendung mit Umsystemen getestet. Wir befinden uns damit eine Stufe höher auf der Testpyramide. Bei klassischen Webanwendungen sind solche Integrationstests – komplett ohne UI – sehr einfach möglich. So müssen nicht unbedingt alle durch die Anwendung aufgerufenen Umsysteme (Microservices, Messaging) mit einbezogen werden, um sinnvolle Integrationstests zu erstellen. Typischerweise wird jedoch die Service-Schicht innerhalb der Anwendung mit getestet. Der Aufbau einer klassischen Webanwendung ist in Abbildung 1 links dargestellt.

sitterberg_test_driven_3_1.tif_fmt1.jpgAbb. 1: Grundsätzlicher Aufbau klassischer Webanwendungen (links), in der Regel mit SPA als Frontend (rechts)

Bei Browseranwendungen mit einer SPA-Architektur unterscheiden sich Integrationstests von Ende-zu-Ende-Tests lediglich in Nuancen (Mock Backend), sodass hier eine Differenzierung schwierig ist: Ein Test von (SPA-seitigen) UI Services alleine kann durch Unittests erfolgen, ein Integrationstest benötigt jedoch sowohl UI-Komponenten als auch UI Services, die wiederum in der Regel ein Backend voraussetzen. Beispiele für die Verwendung von UI Services in Browseranwendungen sind die bereits erwähnte Zustandshaltung, Berechnungen oder Logikanteile des Frontends. Konkret könnte eine solche UI-Logik die Herleitung der zu verwendenden Hintergrundfarbe für ein Issue sein: Handelt es sich um einen wichtigen Kunden, und das anzuzeigende Ticket hat eine mittlere oder hohe Priorität, so ist die Farbe rot bzw. eine adäquate CSS-Klasse urgent zu setzen.

Es hat sich bei solchen Integrationstests bewährt, die Tests anhand der tatsächlich vorkommenden Fachlichkeit zu formulieren. Wir werden bei den Beispielen für Test-Frameworks Cucumber kennenlernen, das sich besonders für die Formulierung solcher fachlichen Vorgaben eignet.

Bewegen wir uns auf der Testpyramide noch eine Stufe höher, spricht man von Ende-zu-Ende-Tests bei klassischen Webanwendungen bzw. UI Tests bei Browseranwendungen. Zwischen diesen Stufen ist die Grenze fließend, und man kann sich vortrefflich in stundenlangen Diskussionen darüber verfangen, ob es sich nun um einen Integrations- oder Ende-zu-Ende-Test handelt. Pragmatischer geht dabei Google vor: Im Buch „How Google tests Software“ wird diskutiert, dass Google Tests eher in die Klassen „small“, „medium“, „large“ und „x-large“ für Systemintegrationstests unterteilt. Damit bekommt man sofort ein Gefühl dafür, wie komplex das jeweilige Test-Set-up ist, wie schnell/langsam die Tests laufen und welche Menge an Tests man erwarten darf.

Bei Ende-zu-Ende-Tests darf man in jedem Fall davon ausgehen, dass entsprechende direkte Umsysteme mitgetestet werden. Handelt es sich um eine reine Browseranwendung, wird somit das zugehörige Backend benötigt – zumindest auf Schnittstellenniveau.

Handelt es sich um eine klassische Webanwendung, wird man Ende-zu-Ende-Tests eher so auffassen, dass neben der eigenen Anwendung auch beispielsweise Datenbank und Messaginginfrastruktur oder einzelne zuliefernde Microservices bei Tests miteinbezogen werden, ohne dass es sich hier um ein Mock oder eine Fake-Implementierung handelt.

Da diese Tests aufgrund der Komplexität direkt einen höheren Erstellungs- und auch Wartungsaufwand mit sich bringen, ist ihr Nutzen primär in Projekten mit iterativer Vorgehensweise, wie z. B. bei agilen Projekten, und häufigen Releases zu finden. Eine entsprechende Testabdeckung auf Ende-zu-Ende-Basis ist zwingende Voraussetzung, um eine hohe Qualität bei häufigen Releases und Änderungen liefern zu können: Bei häufiger Durchführung ist ein manuelles Testen aller Features prohibitiv teuer und die damit einhergehende Monotonie führt für die Tester zwangsläufig zu Fehlern im Test.

Damit soll jedoch nicht gesagt sein, dass manuelle Tests keine Daseinsberechtigung hätten, ganz im Gegenteil! Manuelle Tests sollten jedoch sinnvoll eingesetzt werden. Also gerade da, wo eine Automatisierung schwierig oder unmöglich ist. Das kann z. B. die Bewertung des „Gefühls“ der Anwendung (UX/Usability) sein, oder ob das Design korrekt umgesetzt ist und sich Animationenen und Effekte sauber integrieren. Auch exploratives Vorgehen, um proaktiv Fehler zu finden, lässt sich am besten durch manuelle Tests umsetzen. Die Findings können dann Ausgangsbasis für automatisierte Tests sein.

Übrigens: Wie angedeutet, ist der Wartungsaufwand für Tests auf den höheren Stufen der Testpyramide aufgrund der Komplexität und der Menge von involvierten Teilen der Anwendung relativ hoch. Häufig haben Integrations- und Ende-zu-Ende-Tests daher den Ruf, wenig Nutzen zu bringen („müssen sowieso bei jeder Änderung angepasst werden“) oder sehr aufwendig zu implementieren zu sein („braucht länger als die Anwendung selbst“). Ein typischer Grund dafür ist eine Vorgehensweise, die zu einer zu starken Kopplung der Tests an die Anwendung führt. Oft ist das das Resultat von Test-Frameworks, die mit einem Recorder arbeiten, in dem Click Streams aufgezeichnet und nachher wiedergegeben werden. Ändern sich nun Teile der Anwendung so, dass die Aufzeichnung nicht mehr direkt verwendet werden kann, verliert ein Test seinen Nutzen. Daher empfiehlt es sich dringend, auch Tests wie die eigene Anwendung zu entwickeln. Das bedeutet, entsprechende Abstraktionen mitzudenken und insgesamt wartbaren, modularen Testcode zu schreiben – und zwar kontinuierlich und nicht gemäß dem Motto „getestet wird am Ende“.

Im Folgenden betrachten wir aktuelle Test-Frameworks, mit denen sich Ende-zu-Ende-Tests umsetzen lassen, und gehen auf die Herausforderungen ein, die Ende-zu-Ende-Tests mit sich bringen. Bevor wir auf der technischen Ebene einsteigen, betrachten wir das grundsätzliche Vorgehen bei Browsertests und werfen dann einen Blick auf das bewährte Page Object Pattern für UI-Tests.

Browser-Testing

Im Gegensatz zu Unittests, die direkt auf Codeebene einzelne Komponenten und Services einbinden und testen, wird bei Integrationstests im Webkontext auf Anwendungsebene getestet. Dabei wird die Anwendung durch die Test-Frameworks so bedient, wie auch ein normaler User die Anwendung bedienen würde: Die Testsoftware navigiert auf eine vorgegebene (Unter-)Seite, wartet, bis diese geladen ist und kann dort auf Elemente klicken oder zum Beispiel Eingaben in Textfelder vornehmen. Um zu prüfen, ob die Anwendung auf diese Interaktionen richtig reagiert, kann im Browser-DOM wiederum nach Elementen gesucht werden, um deren Inhalt zu überprüfen. Die Selektoren sind dabei in der Regel CSS-Selektoren oder XPath-Ausdrücke. Damit die Anwendung sinnvoll getestet werden kann, benötigt sie (Test-)Daten.

Fake-Backend und Umsysteme

Im Gegensatz zu klassischen Webanwendungen, die oft ohne Umsysteme oder mit Mocks arbeiten können, ist bei Browseranwendungen mit SPA-Architektur in der Regel mindestens ein Backend erforderlich, das Daten bzgl. der Implementierung von Geschäftsprozessen bereitstellt. Das gilt ebenso für Tests. Tests wiederum setzen eine stabile Datenbasis voraus, damit sinnvolle Aussagen ermöglicht werden.

Sollen Tests – beispiel...

Neugierig geworden? Wir haben diese Angebote für dich:

Angebote für Gewinner-Teams

Wir bieten Lizenz-Lösungen für Teams jeder Größe: Finden Sie heraus, welche Lösung am besten zu Ihnen passt.

Das Library-Modell:
IP-Zugang

Das Company-Modell:
Domain-Zugang