Einführung von automatisierten Tests - Teil 1
16.06.2016
Das Kerngeschäft der agido GmbH ist die Entwicklung von Software für das Angebot und die Verwaltung von Sportwetten. Zielplattformen sind sowohl Desktop-Browser als auch Mobilgeräte. Mit zunehmendem Funktionsumfang werden dabei Tests immer aufwändiger. Insbesondere Regressionstests sind sehr zeitintensiv. Seit der Umstellung auf eine agile Entwicklung und continuous delivery mit bis zu fünf Releases pro Woche ist ein umfänglicher Regressionstest je Release nicht mehr möglich. Um diesem Manko entgegen zu wirken, haben wir uns entschieden, automatisiertes Testen einzuführen.
Zunächst sollten dabei die Hauptfunktionalitäten geprüft werden. Die Anforderungen an die automatisierten Tests unserer Anwendung wurden dabei im Rahmen der Analyse immer umfangreicher: Mehrsprachigkeit (10 verschiedenen Sprachen), unterschiedlichen Domänen und diverse Endgeräte ehöhen den Umfang und die Komplexität der Tests. Die Tests sollten parallel zur Entwicklung und den manuellen Tests des Systems durchgeführt werden. Daher waren das Toolset und der Technologiestack der zu testenden Anwendung ein wesentlicher Entscheidungsgrund:
- Linux Betriebssystem
- Jira/Confluence als Ticket- und Dokumentations-System
- Maven für builds
- Bamboo für Continuous Integration (CI)
- Entwicklungssprache Java, für die Weboberfläche weiterhin JavaScript
- Einsatz verschiedener Frameworks/Libraries wie Spring, AngularJS, PhoneGap etc. sowie einiger selbst entwickelter Lösungen
- Einsatz externer Systeme bzw. Bedienung derer Schnittstellen, z.B. Quotenprovider, Zahlungsprovider
- Eclipse als IDE für die Implementierung
- Applikationsplattformen: Internet-Browser (Desktop), Mobilgeräte (Handy, Tablet), Desktopanwendung für die Administration
Auf Grund der Applikationsplattformen wollten wir uns zunächst auf die Mobilgeräte und Webbrowser beschränken. Für diese Zielplattformen galt es, ein Framework zu finden, das das automatisierte Testen der Anwendung in geeigneter Form unterstützt. Definierte Anforderungen waren:
- Steuerung der Desktop Weboberfläche
- Steuerung der mobilen Oberfläche
- Im Hinblick auf Erweiterungen ist die Unterstützung von Wischgesten und nativer Apps für mobile Geräte wünschenswert
- Unterstützung verschiedener mobiler Geräte
- Administration der Testfälle
- Möglichkeit, Testfälle aus Einzelteilen flexibel zusammen zu stellen
- Möglichkeit, gezielte Fragestellungen zu testen und nur einzelne Tests laufen zu lassen
- Möglichkeit der Anbindung an Jira und/oder Bamboo, so dass Testergebnisse direkt mit Testläufen oder Tickets in Zusammenhang gebracht werden können
- Möglichkeit von automatisierten Testläufen über Bamboo (automatische und manuelle Ausführung)
- Aussagekräftiges Reporting für fehlgeschlagene Tests, ggf. Export der Fehlerreporte
- Schnelle Lernkurve
- Als Implementierungssprache nahe an Java
- Wenig Komplexität in der Implementierung der Testfälle
Wir evaluierten im Anschluss an die Anforderungsdefinition die im Internet angebotenen Testframeworks. Da die Informationen zu den Frameworks nicht normiert sind, ist es schwierig, vergleichbare Aussagen zu bekommen. Sicher ist nur: Selenium Webdriver als Schnittstelle zwischen Anwendung und Testfall ist state of the art ([W4]). Es gibt diverse Anbieter, die auf Selenium aufgesetzt haben und mittels eigener Weiterentwicklungen eine erweiterte Funktionalität sowie die Anbindung an verschiedenste Ticket- und CI-Systeme anbieten. Je nach Anbieter ist diese auch für viele Programmiersprachen verfügbar. Für eine endgültige Auswahl eines Frameworks gab es einige
Hinderungsgründe:
- Ein größeres finanzielles Investment in ein kommerzielles Framework ohne Gewissheit, dass dieses unsere Anforderungen erfüllt, kam nicht in Frage. Damit gerieten alle kommerziellen Anbieter leicht ins Hintertreffen.
- Viele Anbieter kommerzieller Frameworks haben keine Demo-Anwendung, mit der ausprobiert werden kann, ob das Framework den nötigen Umfang bietet.
- Unsere eigene spärliche Erfahrung ließ uns häufig zögern, Entscheidungen zu fällen.
Aus diesem Grund evaluierten wir zunächst „plain Selenium“ im Hinblick auf unsere Anforderungen. Nur wenn dieser Basisschritt erfolgreich ist, können weitere Frameworks in Augenschein genommen werden.
Für die Entwicklung der Tests sollte das Entwurfsmuster der Page Objects eingesetzt werden, das zur Zeit ebenfalls state of the art ist. Jedes PageObject repräsentiert (modelliert) dabei eine (Web-)Seite der Anwendung und bietet auf dieser ausschließlich die Felder, Informationen und Interaktionsmöglichkeiten, die auch dem Benutzer zur Verfügung stehen. Damit kann man innerhalb der Tests Änderungen auf der Anwendungsoberfläche gut begegnen, da diese nur eine Änderung in dem entsprechenden PageObject nach sich ziehen. Die eigentlichen Testklassen müssen von diesen PageObjects unabhängig sein. Sie bedienen sich nur der dort angebotenen Methoden, um mit dem System under test zu kommunizieren.
Eine gute Überprüfung für den eigenen Programmcode liefert Simon Stewart mit der Aussage:
If you have WebDriver APIs in your test methods, You're Doing It Wrong.
[W2]
Aufgrund der heterogenen Anwendungsplattformen arbeiten wir mit Browserstack als cloud-basiertem Browseranbieter für die Tests. Browserstack bietet die Möglichkeit, über einen Remote-Service auf Endgeräte zuzugreifen, Selenium(-basierte) Testskripte auszuführen und Testergebnisse zu liefern. Auch dies war ein Grund für die Entscheidung, zunächst mit Selenium eine Evaluierung zu machen. Erste Tests mit Selenium zeigten sich als guter Einstieg.
Selenium IDE
Das Entwickeln mit der Selenium IDE erscheint zunächst als einfaches Mittel, um schnell einzusteigen. Mittels eines Firefox Add-Ons ist es möglich, mit Capture und Replay schnell einen Testfall zu erstellen. Dieser Testfall wird als xml-Datei abgelegt und kann – ebenfalls über das Tool – in Programmcode für diverse Zielsprachen überführt werden. Es gibt zudem die Möglichkeit, einzelne Tests oder Testsuiten aus einzelnen Testfällen zusammen zu stellen. Der generierte Java-Code der Testfälle bietet jedoch leider nicht unbedingt das, was man erwartet bzw. benötigt.
Das Generieren ganzer Testsuiten stellte sich als unhandlich heraus und lieferte ein für uns noch weniger brauchbares Ergebnis. Es handelt sich dabei maximal um Skeletons, die einer intensive Nachbehandlung bedürfen. Zudem mussten die generierten Codestücke immer in das eigentliche Programm kopiert werden, was die Codierungsarbeit nur minimal vereinfachte.
Man kann jedoch in den mit Capture erstellten Tests gut sehen, wie die Selektoren für die Webelemente aussehen. Das Tool bietet die Möglichkeit, zwischen XPath, CSS und ID zu wechseln, selektiert zunächst aber automatisch das, was am besten und eindeutigsten passt. Was sich auf dem Papier zunächst gut anhört, erscheint live und in Farbe weniger geeignet. Die Selektion des geeignetsten Selektors mutet schon mal etwas willkürlich an und entspricht nicht immer dem, was man als Entwickler intuitiv wählen würde. Besonders gut verständlich ist das Ergebnis auch nicht.
Listing Selenium IDE für Testcase [1] :
<?xml version="1.0" encoding="UTF-8"?> <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd"> <html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en"> <head profile="http://selenium-ide.openqa.org/profiles/test-case"> <meta http-equiv="Content-Type" content="text/html; charset=UTF-8" /> <link rel="selenium.base" href="https://test4.myapplication.com/" /> <title>ChangeLangToEN</title> </head> <body> <table cellpadding="1" cellspacing="1" border="1"> <thead> <tr><td rowspan="1" colspan="3">ChangeLangToEN</td></tr> </thead><tbody> <tr> <td>click</td> <td>//div[@id='languageChooser']/a[2]/span[3]</td> <td></td> </tr> <tr> <td>clickAndWait</td> <td>xpath=(//img[@alt='flag'])[4]</td> <td></td> </tr> </tbody></table> </body> </html>
Listing Selenium IDE für Testsuite:
<?xml version="1.0" encoding="UTF-8"?> <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd"> <html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en"> <head> <meta content="text/html; charset=UTF-8" http-equiv="content-type" /> <title>Test Suite</title> </head> <body> <table id="suiteTable" cellpadding="1" cellspacing="1" border="1" class="selenium"> <tbody> <tr><td><b>Test Suite</b></td></tr> <tr><td><a href="../../TestCases/Login/LoginUser001">LoginUser001</a></td></tr> <tr> <td> <a href="../../TestCases/LangChanging/ChangeLangToEN">ChangeLangToEN</a> </td> </tr> <tr> <td> <a href="../../TestCases/Payment/DepositFailure">DepositFailure</a> </td> </tr> <tr> <td> <a href="../../TestCases/Payment/VerifyCashierLayer-Contact-EN">VerifyCashierLayer-Contact-EN</a> </td> </tr> <tr> <td> <a href="../../TestCases/LangChanging/ChangeLangToDE">ChangeLangToDE</a> </td> </tr> <tr> <td> <a href="../../TestCases/Payment/DepositFailure">DepositFailure</a> </td> </tr> <tr> <td> <a href="../../TestCases/Payment/VerifyCashierLayer-Contact-DE">VerifyCashierLayer-Contact-DE</a> </td> </tr> ... </tbody></table> </body> </html>
Den letzte Ausschlag gegen die Verwendung der Selenium IDE gab, dass diese nicht weiter entwickelt wird. Mangels besserer Alternativen gingen wir dazu über, die Selenium-Teile in Java selbst ausprogrammieren.
Java-Entwicklung mit Selenium
Die Entwicklung von Testfällen unter Java mit der Seleniumbibliothek machte keine großen Schwierigkeiten. Der Einstieg ging recht schnell, konnte sich auf den Skeletons, die von der IDE generiert wurden, abstützen und war im Wesentlichen intuitiv. Die Selenium Homepage bietet hier einen guten Überblick über die Möglichkeiten. Ebenfalls sind in dieser Quelle diverse Beispiele zu finden.
Der Test von Selenium wurde als erfolgreich angesehen. Für das weitere Vorgehen und die Auswahl eines Werkzeugkastens aus Test-Framework und -Tools benötigten wir zusätzlich externe Unterstützung.
Geb und Groovy
Um der Flut an Tools und Möglichkeiten Herr zu werden, ist es für Einsteiger ratsam, sich beraten zu lassen. So bekamen wir ein Referenzprojekt, das unter Verwendung eines empfohlenen Satzes an Tools, Methoden und Strukturen, eine erste Übersicht bot. Als Framework zur Implementierung der Webseitenzugriffe wurde hier Geb eingesetzt. Dieses Framework bietet ein bereits vorimplementiertes PageObject Pattern, einen jQuery-artigen Zugriff auf Webelemente und eine gute Integration für weitere Frameworks.
Geb is a browser automation solution. It brings together the power of WebDriver, the elegance of jQuery content selection, the robustness of Page Object modelling and the expressiveness of the Groovy language. It can be used for scripting, scraping and general automation — or equally as a functional/web/acceptance testing solution via integration with testing frameworks such as Spock, JUnit & TestNG.
[W1]
Auf Grund der vielen positiven Internetbeiträgen, standen wir Geb aufgeschlossen gegenüber und wollten es evaluieren.
Dafür haben wir uns unter anderem in Groovy eingearbeitet, die Demo-Testanwendung nachvollzogen und im Anschluss erste eigene einfache Tests implementiert. Für die ersten Gehversuche haben wir uns auf einfache Tests beschränkt, die nur die Desktop Webseite in einer Sprache abdecken.
Sehr angenehm begegnete uns die Geb-seitige Umsetzung des PageObject Patterns. Eine Webseite (= PageObject) definiert sich in diesem Fall durch die Eigenschaft, eine eigene URL zu besitzen, einen content mit dem Inhalt der Seite zu haben und einen at-Checker zu implementieren, der automatisch beim Betreten einer Seite prüft, ob man auf der richtigen Seite ist. Der content kann einzelne Webelemente oder Module enthalten. Module fassen Teile der Webseite zusammen, wie z.B. eine Login-Box bestehend aus Username, Passwort und Button. Module können rekursiv weitere Module enthalten.
Nach dem ersten Eindruck von Geb und weiteren grundlegenden Überlegungen zur Infrastruktur, Verfahren, Dokumentation und dem zu verwendenden Testing-Framework wurde in diesem Zusammenhang folgendes festgelegt:
- Eigenes Git-Repository für die Testfälle
- Entwurf und Beschreibung der Testpläne zunächst über Confluence
- Verwendung des Geb-Framework zum Testen der mobilen und der Weboberfläche, Entwurf komplexerer Testfälle und weitere Beurteilung der Eignung von Geb
- Groovy als Implementierungssprache
- IntelliJ als IDE
- TestNG zur Steuerung der Tests
- Integration in Bamboo (CI)
Während der Evaluierung stellte sich heraus, dass unsere Anwendung in ihrer derzeitigen Form für automatisiertes Testen nicht geeignet ist.
Um Software testen zu können, muss diese zunächst einmal testbar sein. Auch wenn es logisch klingt, die zu testende Software wird nicht primär für dieses Ziel entwickelt, sodass spezielle Anpassungen zur Testunterstützung der Testbarkeit eher die Regel sind. Wenn Sie Testautomatisierung in einem seit langem laufenden Softwareprojekt einführen wollen, kann die zu testende Software Ihnen das Leben sehr schwermachen, da Oberflächenelemente nicht eindeutig erfasst werden können.
[W5]
Der Wahrheitsgehalt dieser Aussage wurde mehr als schnell offenbar. Es eröffneten sich folgende, sicher übliche Szenarien einer Anwendung, die seit viele Jahren entwickelt wird:
- Mehrdeutigkeit eines Selektors
- Keine durchgängige Verwendung von ID-Tags im html Code
- Z.T. mehrfache Verwendung derselben ID-Tags
- Unschöne Selektoren, die sich sehr an die DOM-Struktur anlehnen (z.B. div#jq-event-id-158028310-live>div:nth-child(3)>button:nth-child(3))
- Selektoren, die überhaupt nicht lesbar sind
- Selektoren, die variable Daten enthalten
Wir passten die Anwendung an, um diese testfähig zu machen. Dies wurde mit einer eigenen Tag-Eigenschaft data-qa umgesetzt, die wir den für die Tests notwendigen Webelementen mitgaben. Dieses Tag soll im jeweiligen Seitenkontext eindeutig sein. Entsprechend konnten nun im Code die Webelemente in den PageObjects angepasst werden.
Beispiel LoginBox:
<form id="loginForm" action="/login" method="post"> <div id="loginDiv"> <div class="box_login_sub"> <input data-qa="loginFormUsername" type="text" name="login" id="login" placeholder="Username"> <input data-qa="loginFormPassword" type="password" name="password" id="password"> </div> <div class="wrap"> <div data-qa="loginFormHelp" id="help_login" onclick="show_layer(this.id);">Help</div> </div> <div class="wrap"> <input data-qa="loginFormLogin" id="loginButton" type="submit" value="Log in" onclick="stop()"> </div> </div> </form>
Der nächste Schritt ist das Abbilden des vorgesehenen Verhaltens eines Benutzers. Aus dem Referenzprojekt übernahmen wir die Idee der „Behaviours“. Behaviours sind die Interaktionsmöglichkeiten, die ein Benutzer innerhalb eines Webseitenbereiches hat. Dem Entwickler der Testfälle wird mit den Behaviours ein Set an Möglichkeiten der Interaktion an die Hand gegeben. Für eine Benutzeraktion, die es in den Behaviours nicht gibt, gibt es genau zwei Möglichkeiten:
- Der Benutzer kann sie nicht ausführen.
ODER
- Sie wurde noch nicht implementiert.
Damit wird vermieden, dass man einen Test schreibt, der auf Kenntnis des Anwendungscodes basiert bzw. sich (unerwünschter Weise) Funktionen bedient, die im Testfall nichts zu suchen haben. Webseiten sollen in der Anwendung über Klicks erreicht werden und nicht weil der Entwickler „zufällig“ weiß, wie die entsprechende URL heißt oder noch schlimmer, welcher JavaScript-Befehl zum Erfolg führt. Man rufe sich hier erneut Simon Stewart in Erinnerung:
If you have WebDriver APIs in your test methods, You're Doing It Wrong.
[W2]
Konkret werden die Behaviours als Traits implementiert und müssen entsprechend von den Testfall-Klassen mittels implements eingebunden werden.
Listing Basic Behaviour:
trait WBehaviours_Basic { def _openHomepage() { ActionUtil.logInfo("BEHAV: Öffne Homepage") to HomePage } ... }
Erläuterung: Das to HomePage holt sich aus dem HomePage Objekt die URL und navigiert dorthin. Ist die Seite aufgerufen, wird der in HomePage definierte at-Checker ausgeführt, um zu prüfen, ob man am richtigen Ziel angelangt ist. Die Seite steht dann zur Verfügung und ist das aktuelle, aktive PageObject. Alle content-Elemente können nun zugegriffen werden.
Mit zunehmender Komplexität unserer Anforderung an die Testing-Anwendung zeigten sich bald einige Grenzen von Geb bzw. Groovy. Sehr umständlich zeigte sich beispielsweise die Integration der Mehrsprachigkeit, die in eine Klassenexplosion ausartete. Auch die Refaktorisierung und Kompilierung machte große Probleme. Wie sich unsere Schwierigkeiten im Einzelnen darstellten, wie Lösungen aussehen können und welche Konsequenzen dies hatte, wird in Teil 2 beschrieben.
Literatur
Bücher
[B1] Andreas Spillner, Tilo Linz: Basiswissen Softwaretest 4., überarbeitete und aktualisierte AuflageArtikel
[A1] Peter Haberl, Prof. Dr. Andreas Spillner, Prof. Dr. Karin Vosseberg, Prof. Dr. Mario Winter: Umfrage 2011: Softwaretest in der Praxis, 1. Aufl. 2012, überarb., dpunkt.verlag 2012, Artikelnummer: 077.95734 [A2] Frank Simon, Manuel Fischer, Prof. Dr. Karin Vosseberg, Prof. Dr. Andreas Spillner, Kai Lepler, Prof. Dr. Mario Winter: Management-Summary der GTB Softwaretestumfrage 2015/2016, April 2016 [A3] Prof. Dr. Wüst, Prof. Dr. Kneisel, Automatisches Testen von Software - Ein Überblick über Softwaretests, ihre Anwendung und Implementierung im Rahmen der ATLM [A4] Bernd Beersma: Right Tool - The Pyramid Approach to Test Tool Selection aus Automated Software Testing Magazine, S. 22 - 25, Aug. 2013Webseiten
[W1] Geb: www.gebish.org (Dokumentation und API) [W2] PageObjects Pattern, Beschreibung von Martin Fowler, http://martinfowler.com/bliki/PageObject.html [W3] Tutorial zum PageObject Pattern, https://www.kainos.pl/blog/building-test-automation-framework-by-example-2-page-object-pattern [W4] SeleniumHQ, Selenium WebDriver, offizielle Selenium Webseite, http://www.seleniumhq.org/projects/webdriver [W5] Selenium best practices, http://www.qytera.de/blog/selenium-webdriver-best-practices[1] Da die Darstellung des Testfalls im Browser läuft, handelt es sich hier um einen html-File, der diesen Teil umsetzt. Alle Angaben, die man später für eine Transformation in eine Programmiersprache benötigt, sind schon enthalten (Reihenfolge der Aktionen, Identifikation der Webelemente etc.)