Einführung von automatisierten Tests - Teil 3

Was bisher geschah: Im ersten und zweiten Teil dieses Blogs haben wir uns zunächst mit der Auswahl eines Frameworks für die Automatisierung einer Anwendung auf unterschiedlichen Plattformen, unterschiedlichen Domänen und zehn verschiedenen Sprachen beschäftigt. Die erste Wahl fiel auf Geb als Framework mit der darunter liegenden Programmiersprache Groovy. Mit Projektfortschritt haben wir diese Entscheidung überdacht und zur Diskussion gestellt.

Der erste Teil unserer Serie findet sich hier.

Der zweite Teil unserer Serie findet sich hier.

Aufbruch in eine neue Welt

Da sowohl das Framework als auch die Programmiersprache uns mit der Zeit deutliche Schwierigkeiten machten, war es notwendig, die Entscheidung zu überdenken. Wir entschieden, ScalaTest zu evaluieren, da hier bereits firmenintern Vorkenntnisse in anderen Projekten vorlagen und Scala als Sprache innerhalb der Firma etabliert ist.

ScalaTest - eine erste Bilanz

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.

ScalaTest sollte uns möglichst dasselbe bieten wie Geb. Es soll möglichst einfach und unkompliziert auf die Webelemente zugreifen. Auch die Modellierung der Webseite in PageObjects war ein Muss. Dabei wollten wir jedoch die einzelnen PageObjects parametrisierbar haben, um ihnen von außen Werte mitgeben zu können, mit denen weiter gearbeitet werden kann.

Bereits vorhanden war eine breite Palette an Ausgabemöglichkeiten für die Testergebnisse sowie diverse Möglichkeiten, die Testfälle zu strukturieren.

Wir entschieden folgende Punkte umzusetzen:

  • Nutzung des Taggings von Testfällen - dies ist ein Vorgehen, dass in ScalaTest bereits vorgesehen ist, um Testfälle zu klassifizieren und nur solche aus einzelnen Klassifizierungen laufen zu lassen.
  • Ausgabe bzw. Spezifierung der Test mittels der scalaeigenen FunSpec.
  • Erweiterung der Testreportausgaben um Levels (INFO, DEBUG, WARN, ERROR) sowie weitere Informationen wie z.B. Cookies, Zugriffe und Aktionen auf Webelementen etc.
  • Umsetzung und Erweiterung des PageObject Patterns - dieses Vorhaben umfasste sehr viele Vorgänge und Überlegungen, die im restlichen Beitrag genauer beschrieben werden.

PageObjects in ScalaTest

Das Design zur Umsetzung des PageObject Patterns war sehr umfangreich, um möglichst viel abzudecken und dabei flexibel zu bleiben.

Auszug aus dem Klassendiagramm des PageObject Patterns

Eine Page kann später so programmiert werden, dass sie PageObject implementiert. Es ist außerdem möglich, nur Teile einer Seite als PageModule zu implementieren. Diese Module können ineinander oder in Pages integriert werden. So ist eine flexible Seitengestaltung möglich. Jedes PageModule und jedes PageObject implementieren eine gleiche Basis, den PageBase Trait. In diesem ist z.B. festgelegt, dass für den Zugriff auf die Webelement eine QueryDsl benutzt wird. In der QueryDsl sind unterschiedliche Möglichkeiten umgesetzt, wie auf Webelement zugegriffen werden kann, z.B. mittels Id, Css-Klasse oder XPath.

Um den Bedarf verschiedener Domains abzudecken, wurde die DomainPage als Trait implementiert, der einfach in jede Seite gemergt werden kann, so dass diese auf eine bestimmte Domain zeigt.

Jede Page implementiert den AtChecker, der prüft, ob man auf der erwarteten Seite ist. Im Gegensatz zu Geb haben wir den AtChecker mit Vererbung implementiert. So kann ein genereller AtChecker definiert werden, der jedoch bei Bedarf in einzelnen Pages überschrieben werden kann.

Um einen korrekten Seitenwechsel zu garantieren und die Möglichkeit zu verbieten, auf Objekte zuzugreifen, die auf nicht aktiven Seiten sind, haben wir das Konzept der “Aktiven Seite” umgesetzt. Eine Verwaltungsklasse hält darin die aktive Seite vor und prüft vor dem Zugriff auf ein Webelement, ob dies überhaupt grundsätzlich möglich ist.

Um die Möglichkeit zu haben, auf verschiedenen Browsern zu testen, kann jeder Browsertyp von einer DriverFactory erzeugt werden. Die ChromeDriverFactory erzeugt z.B. einen Chrome-Browser. Standardmäßig erzeugen DriverFactorys immer Browserfenster auf dem aktuellen Desktop. Damit sind allerdings einige Tests nicht möglich. Als Alternative gibt es eine VncDriverFactory, im Speziellen z.B. die VncChromeDriverFactory, die den Chrome Browser innerhalb eines VNC Servers startet. Dies ist speziell für Testserver oder parallele Testausführungen sehr nützlich, wird von uns aber auch zum Entwickeln benutzt, damit der Browser nicht den Fokus verliert, wenn in der IDE der Debugger benutzt wird. Bei Fokusverlust werden z.B. Webelemente nicht mehr gefunden, daher musste hier eine Alternative geschaffen werden.

Für den Einsatz bei Tests, die auf verschiedenen Browsern laufen sollen, gibt es Zusammenfassungen, wie z.B. die DefaultDriverFactoryList für Browser, die auf dem Desktop laufen sollen. Analog gibt es wieder eine DefaultVncDriverFactoryList für den Betrieb innerhalb des VNC Servers.

Listing DefaultDriverFactoryList:

/**
* A list of DriverFactories to create all supported local browsers
*
* DriverFactoryList will filter DriverFactories returning false for compatible or selected
*/
class DefaultDriverFactoryList extends DriverFactoryList(HtmlUnitDriverFactory(), FirefoxDriverFactory(), 
      SafariDriverFactory(), ChromeDriverFactory(), InternetExplorerDriverFactory())

Testen, Testen, Testen

Unter Verwendung des neuen PageObject Patterns haben wir angefangen, unsere Testanwendung umzusetzen. Dafür wurde zunächst die Registrierungsseite als Page angelegt.

Hier sei deutlich darauf hingewiesen, dass man nicht einfach eine automatisierte Testumgebung für eine Webseite/-anwendung schreiben kann. Die Anwendung muss auch für die automatisierten Tests geeignet sein. Dies ist sie nur dann, wenn der automatisierte Testfall auch mit ihr kommunizieren kann. Konkret heißt dies, dass der Testfall Webelemente identifizieren und zugreifen können muss. Dafür ist es unumgänglich, dass die Elemente der Webseite eindeutige Identifizierungsmöglichkeiten besitzen.

Mittlerweile glauben wir, dass unsere Geb-Anwendung z.T. so langsam war, weil die Selektoren zur Identifizierung der Webelemente sehr schlecht waren. Meist wurden sehr viele Elemente zu einem Kriterium gefunden und aus dieser Liste weiter eingeschränkt. Nach dem Einbau verschiedener Log-Ausgaben zur weiteren Analyse, konnten wir feststellen, dass Geb zu einem vorgegebenen Selektor bei dessen Aufruf immer alle Elemente aus dem Browser in die JVM kopiert. Verwendet wird im Endeffekt aber nur das erste gefundene Element, egal wie groß die Liste ist. Je schlechter der Selektor ist, desto mehr Zeit wird dafür benötigt.

Das PageObject Pattern hingegen liefert immer nur genau ein Element für einen Selektor. Werden mehr Elemente gefunden, fliegt eine Exception. Weiß man vorher, dass mehrere Elemente passen, kann man dies mit einer speziellen Funktion angeben und das Ergebnis nach den eigenen Bedürfnissen auswerten. Neben dem Geschwindigkeitsvorteil bietet dies die Sicherheit, immer mit dem Element zu arbeiten, welches vom Entwickler vorgesehen war.

Dennoch sollte man eindeutige Selektoren wählen, um nicht versehentlich das falsche Element zu identifizieren. Wir begegneten dem mit der Einführung der data-qa Tags, die wir an alle Elemente vergaben, die für die Tests nötig sind.

Beispiel:

Besonders gruselig sind CSS-Selektoren in dieser Form:

saveEmailButton {
  $("form#editUserForm>div:nth-child(20)>div:nth-child(12)>
  div:nth-child(5)>div:nth-child(5)>div:nth-child(1)>input")
}

Schon besser wäre es in dieser Form:

saveEmailButton {
  $("div#mailUserForm", class:"button")
}

Noch besser mit einem eigenen id-Tag:

saveEmailButton {
  $("#saveEmailButton")
}

Am besten ist man jedoch aufgestellt, wenn man spezielle, eindeutige Selektoren für die QA einbaut:

saveEmailButton {
  $("data-qa=saveEmailButton")
}

Wie sich unser Weg ins automatisierte Testen weiter entwickelt, wird im vierten Teil dieses Berichts veröffentlicht. Hier wird es nun konkret. Wie schnell konnten wir unsere Ideen umsetzen, welche Erfahrungen haben wir mit den Tests gemacht, welchen Hindernissen sind wir begegnet. Ein ausführlicher 2-teiliger Bericht zu dieser Thematik ist außerdem im Javamagazin 11.2016 und 1.2017 erschienen.


Literatur

Bücher

[B1] Andreas Spillner, Tilo Linz: Basiswissen Softwaretest 4., überarbeitete und aktualisierte Auflage
[B2] : Thomas Bucsics , Manfred Baumgartner, Richard Seidl, Stefan Gwihs: Basiswissen Testautomatisierung: Konzepte, Methoden und Techniken, 2. aktualisierte und überarbeitete Auflage

Artikel

[A1] Prof. Dr. Wüst, Prof. Dr. Kneisel, Automatisches Testen von Software - Ein Überblick über Softwaretests, ihre Anwendung und Implementierung im Rahmen der ATLM
[A2] Bernd Beersma: Right Tool - The Pyramid Approach to Test Tool Selection aus Automated Software Testing Magazine, S. 22 - 25, Aug. 2013
[A3] Stefan Gwihs: Test Automation Practices: Keywords, Page Objects und Clients in Java aus JavaSpektrum 6/2015, S. 28 - 30
[A4] Javamagazin 11.16
[A5] Javamagazin 1.17

Webseiten

[W1] PageObjects Pattern, Beschreibung von Martin Fowler, http://martinfowler.com/bliki/PageObject.html
[W2] Tutorial zum PageObject Pattern, https://www.kainos.pl/blog/building-test-automation-framework-by-example-2-page-object-pattern
[W3] Simon Stewart, My Selenium Tests Aren't Stable!, 2.6.2009: http://googletesting.blogspot.de/2009/06/my-selenium-tests-arent-stable.html
[W4] ScalaTest: http://www.scalatest.org/
[W5] ScalaTest User Guide: http://www.scalatest.org/user_guide
[W6] ScalaTest API: http://www.artima.com/docs-scalatest-3.0.0-RC1
[W7] Implementierung des Page Object Patterns unter Scala: http://www.pageobject.org
[W8] Beispiel einer Page Object Pattern Implementierung für Wikipedia.org: https://github.com/agido/pageobject/tree/master/examples/src/test/scala/org/pageobject/examples/wikipedia