Einführung von automatisierten Tests - Teil 4
08.05.2017
Was bisher geschah: In den ersten drei Teilen dieses Blogs haben wir diverse Frameworks zur Testautomatisierung evaluiert und uns im Endeffekt für eine eigene Lösung unter Verwendung von Scala und ScalaTest entschieden. Das Framework zu Benutzungsreife zu bringen hat einiges an Zeit gekostet. Herausgekommen ist ein unserer Meinung nach stabiles und vorzeigbares Produkt, das wir gerne einer größeren Community zur Verfügung stellen wollen (siehe [PageObjekt]).
In diesem Teil geht es nun darum, wie sich unser Framework in der Praxis bewährt. Welche Erfahrungen wir gemacht haben und wo es hakt, kann in diesem Teil nachgelesen werden.
Der erste Teil unserer Serie findet sich hier.
Der zweite Teil unserer Serie findet sich hier.
Der dritte Teil unserer Serie findet sich hier.
Am Ziel der Wünsche?
In diesem vorerst letzten Teil geht es nun darum, wie sich unser Framework in der Praxis bewährt. Welche Erfahrungen wir gemacht haben und wo es hakt, kann in diesem Teil nachgelesen werden.
Über Steine, die im Weg liegen
Den Vorteilen standen jedoch auch Nachteile gegenüber. Hier ist insbesondere zu nennen, dass das Page Object Pattern, das wir für die Umsetzung der Testanwendung verwenden wollten, bislang in ScalaTest nicht ausprogrammiert ist. Es ist nur eine einzige Klasse vorgesehen, die einer Seite (Page) die Eigenschaft URL zuteilt. Um den ausgereiften Zustand der Geb-Implementierung zu erreichen, war noch viel Entwicklungsarbeit zu leisten. Zunächst mussten wir daher identifizieren, was ScalaTest bietet und was wir in welchem Umfang selbst programmieren wollen oder müssen.
Die ersten Testfälle zu schreiben, war durch die Vorkenntnisse aus Geb und die Scala-Sprachkenntnisse keine Herausforderung. Allerdings hatten wir nicht mit den technischen Hürden gerechnet, die sich plötzlich vor uns auftaten. Bis wir einen Browser zuverlässig auf einem VNC-Server starten konnten, bedurfte es einiger Zeit. Diese Probleme sind sehr technischer Natur und haben wenig mit der Implementierung der Testanwendung zu tun. Daher werden sie hier nicht weiter beschrieben. Wichtig ist nur, dass solche Eventualitäten immer in eine Projektplanung einbezogen werden müssen.
Nachdem die technischen Hürden genommen waren, konnten wir uns wieder dem eigentlichen Testen zuwenden. Nachdem die automatisierten Tests manuell gestartet werden konnten, haben wir sie mit Bamboo in unseren CI-Prozess integriert. Für den Anfang hatten wir die Tests so eingestellt, dass sie automatisch alle 10 Minuten laufen. Hintergrund war, dass wir möglichst viele Testdaten bekommen wollten.
Zunächst war die Flut an Informationen sehr sinnvoll und hilfreich. Nachdem wir jedoch die größten Probleme beseitigt hatten, mussten wir feststellen, dass viel nicht immer viel hilft. So reduzierten wir die Läufe auf stündlichen Betrieb. Auch dies war auf Dauer zu viel. Aktuell laufen nachts 5 Builds. Dies ist mehr als ausreichend, so dass erneut Überlegungen bestehen, die Zahl auf 2 Builds weiter einzuschränken.
Wie im dritten Teil des Blogs beschrieben, haben wir Tests für die Registrierungsseite geschrieben. Dabei ist festzustellen, dass man hier als Tester genau überlegen muss, was getestet werden soll. Da der Test der Registrierungsseite für uns als Proof of Concept gelten sollte, haben wir verschiedene Tests für die Positiv- und Negativfälle implementiert. Die Machbarkeit konnte zu ca. 95% nachgewiesen werden. Die restlichen 5% betrafen Unwegsamkeiten, von den wir glauben, sie mit der Zeit lösen zu können.
Leidensfähigkeit, Kekse und Kollegen
Hier ist es jetzt notwendig, ein paar grundsätzliche Worte zur Automatisierung zu verlieren. Will man eine bestehende Anwendung automatisieren, die nicht sofort bei ihrem Entwurf dafür designt wurde, muss man ausgesprochen leidensfähig sein. Nicht nur das Auffinden von Elementen, die von der Automatisierung benutzt werden, ist die Hölle, auch die Sensibilisierung der Entwickler für das Thema, ist keine zu unterschätzende Aufgabe. Selbst wenn die Entwickler aufgeschlossen und innovativ sind, ist es ihnen nicht immer präsent, dass das automatisierte Testen Dinge braucht, die bislang außerhalb des Fokus waren.
Wir mussten auch einige Lektionen in Sachen asynchrone Requests mit ajax lernen. Man laviert hier ständig zwischen Simulation des User-Verhaltens und Umschiffen von Klippen, wie z.B. das Setzen und Abfragen von Cookies, um einen bestimmten Systemzustand feststellen zu können oder ähnliches.
Wie üblich in komplexen System ist die Analyse solcher Probleme ebenfalls beliebig komplex und benötigt sehr viel technisches Fachwissen. Da bewähren sich ein gutes Team und kooperative Kollegen.
Das nächste Thema sollten Einzahlungen sein. Die Schwierigkeit an dieser Stelle bestand im Aufruf von Seiten von Drittanbietern, den Zahlungsanbietern. Es handelt sich dabei z.B. um Popups, die entsprechend für Eingaben den Fokus erhalten müssen. Prinzipiell sind diese Fenster wie “normale” Pages zu behandeln.
Beispiel:
case class MDepositPage() extends MPage { … }
case class Page() extends XPage with WaitFor { … }
Dabei leitet MPage unter anderem von MBasePage ab, die wiederum von XPage ableitet. D.h. in den Seiten der Anwendung gibt es noch ein paar Zwischenschichten, auf die für externe Seiten verzichtet werden kann, weil dort nur Anwendungsspezifika definiert werden. Leider sind auch die Seiten der Drittanbieter immer wieder Änderungen unterworfen. Nachdem unsere Tests eine ganze Zeit zuverlässig liefen, schlugen plötzlich alle fehl. Grund hierfür war eine Änderung in der Sichtbarkeit der Terms & Conditions-Checkbox auf der Seite des Zahlungsanbieters. Der Test konnte die Box plötzlich nicht mehr sehen, obwohl sie deutlich auf dem Bildschirm vorhanden war. Da es nicht unser Ziel ist, die Seite des Zahlungsanbieters zu testen, haben wir in diesem Fall auf die Ausführung eines Javascript-Befehls zurückgegriffen.
executeScript("document.getElementById('acceptTerms').click()")
Bei der Weiterentwicklung unser eigenen Web-Anwendung stand des Weiteren eine große Design-Änderung an. Dafür wurde u.a. das gesamte css der Seite unter die Lupe genommen. Nach diesem Facelift funktionierte keiner unserer Tests mehr. Das neue css wandelte alle Überschriften Kapitälchen um. Egal wie die Seite vom Backend beliefert wurde, ergab sich daraus, das im Frontend diese Texte in UpperCase konvertiert wurden. Die implementierten String-Vergleiche der Tests schlugen damit fehl. Nach der Änderung auf equalsIgnoreCase funktionierte alles wieder. Dies war sicher nur eine kleine Änderung, man kann daran jedoch erkennen, wie weitreichend Änderungen sein können.
Auf in die mobile Welt
Nachdem nun eine Reihe mobiler Tests implementiert wurden, war der nächste Schritt, die Tests auf mobilen Geräten eines Cloud-Anbieters laufen zu lassen. Leider mussten wir dabei feststellen, dass unser Cloud-Anbieter diese Anbindung bei Android nur über Geräte-Emulatoren erlaubt. Ein manueller Test klappte eher leidlich. Der Anbieter zeigte jeden Test grün an, auch wenn er nicht erfolgreich war. Abgesehen davon liefen die Tests sehr langsam, so dass die Testausführung sich manchmal quasi selbst überholte.
Um diesem Umstand zu begegnen haben wir neben dem normalen Click-Befehl einen neuen ClickAfterAnimation-Befehl implementiert. Dieser prüft, ob ein Objekt noch in Bewegung ist, bevor es angeklickt wird. Erst wenn es seine Zielposition erreicht hat, wird es angeklickt.
Es gibt noch diverse kleine Annekdoten und Geschichten, die hier erzählt werden könnten, inhaltlich jedoch nicht viel Neues bringen. Daher soll es an dieser Stelle erstmal gut sein. Unser Weg geht weiter, wir lernen viel und entwickeln uns.
Fazit
Nach wie vor bin ich der Meinung, dass die Automatisierung ein wichtiger Beitrag zur Qualität des Produkts ist, insbesondere, wenn man mit CI arbeitet und häufig deployt.
Die vielfach in der Literatur zu findende Aussage über die Testbarkeit einer Anwendung insbesondere hinsichtlich der Automatisierung hat sich in diesem Projekt ebenfalls bestätigt. Sicher ist es möglich, auf diversen Wegen Webelemente zu finden, um diese dann automatisiert zu benutzen. Man kann sich aber das Leben auch vereinfachen, indem Id-Tags oder spezielle QA-Tags verwendet werden. Das erleichtert nicht nur die erste Implementierung sondern auch die Wartung der Tests. Die Fragen, die man sich zu Anfang immer stellen sollte sind:
- Was möchte ich erreichen?
- Warum möchte ich das erreichen?
- Wie möchte ich das erreichen?
- Wie nachhaltig soll meine Lösung sein?
Tests, die nicht stabil laufen, sind für die Automatisierung ungeeignet, weil man sich auf ihre Ergebnisse nicht verlassen kann. Bei fehlgeschlagenen Tests neigt man mit der Zeit dazu, diese zu ignorieren, weil die Tests ständig fehlschlagen. In diesem Fall ist es besser, den Test so lange auszusetzen bis er stabil läuft und zuverlässige Aussagen produziert.