Florian Pilz

Lesezeit: 9 Minuten

Techblog

Ein Architekturpattern für die Testautomatisierung

In einem Projekt unterliegt die Software ständigen Änderungen. Beispiele dafür sind Änderungen von IDs oder ganzer fachlicher Prozesse. Eine Testautomatisierung muss also so aufgebaut sein, dass sie schnell an Änderungen angepasst werden kann. Ein gängiger Ansatz dazu ist das Page Object-Pattern, das auch in vielen Online-Tutorials (zum Beispiel bei Udemy oder Testautomation University) gezeigt wird.…

Techblog

In einem Projekt unterliegt die Software ständigen Änderungen. Beispiele dafür sind Änderungen von IDs oder ganzer fachlicher Prozesse.

Eine Testautomatisierung muss also so aufgebaut sein, dass sie schnell an Änderungen angepasst werden kann.

Ein gängiger Ansatz dazu ist das Page Object-Pattern, das auch in vielen Online-Tutorials (zum Beispiel bei Udemy oder Testautomation University) gezeigt wird. Allerdings werden in diesem Pattern Objekte schnell lang und unübersichtlich. Mein Ansatz, um das zu verhindern: Ich splitte die Objekte auf. So sind sie kürzer, übersichtlicher und besser zu warten. Ebenso wie das Page-Object-Pattern ist auch dieses erweiterte Page-Object-Pattern unabhängig von der verwendeten Sprache.

Das Page-Object-Pattern

Mit dem Page-Object-Pattern werden Webseiten oder Teile von Webseiten als Code dargestellt – das Page-Object.

Nehmen wir als fiktives Einfach-Beispiel das Webseite-Projekt „My Shop 24-7“. Bei der Erstellung müssen unter anderem Kundendaten erfasst und bearbeitet werden. Dazu werden folgende Seiten erstellt:

Kundenübersicht

Auf der linken Website („Kundenübersicht“) werden alle Kundendaten aufgelistet, auf der rechten („Kundendaten bearbeiten“) können sie bearbeitet werden. Zudem befindet sich auf der jeweils linken Seite beider Webseiten das Menu – wie übrigens auch auf allen anderen Seiten.

Überträgt man diese beiden Seiten als Page-Objects, so entstehen insgesamt vier Objekte. Diese werden – je nach Sprache – Beispielsweise als Klassen dargestellt:

Kundenübersicht 2
  • Page-Class: von dieser Klasse erben alle weiteren Klassen. Sie beinhaltet allgemeine Funktionen, die an unterschiedlichster Stelle verwendet werden können. 
  • MyShop247Page-Class: diese Klasse stellt die Webseite dar und die Elemente, die an allen Stellen vorkommen. In diesem Fall die Navigation auf der linken Seite.
  • CreateCustomerPage-Class: in dieser Klasse sind die Elemente der Seite „Kundenübersicht“ enthalten. Da auf dieser Seite auch das Menu auf der linken Seite enthalten ist, erbt diese Klasse von der MyShop247Page. Dadurch kann aus dieser Klasse heraus auf Elemente des Menus zugegriffen werden.
  • EditCustomerPage-Class: in dieser Klasse sind die Elemente der Seite „Kundendaten bearbeiten“ enthalten. Da auf dieser Seite auch das Menu auf der linken Seite enthalten ist, erbt diese Klasse von der MyShop247Page.

In diesen Page-Objects werden die Locators für die einzelnen Elemente gespeichert. Das hat den Vorteil: sollte sich ein Locator ändern, so muss dieser nur an einer Stelle geändert werden. Zusätzlich sind Aktionen auf diesen Elementen definiert. So kann bspw. eine Methode erstellt werden, um auf einen Button zu klicken oder um den Wert eines Textfeldes zurückzugeben. Neben den Elementen und einzelnen Aktionen kommen zudem komplexere Methoden in den Page-Objects vor. Ein Beispiel kann ein Login sein: In dieser Methode werden Benutzername und Passwort eingegeben und der Login-Button geklickt. Im Test wird später diese Funktion aufgerufen. Ändert sich der Login-Prozess, so muss dieser nur an dieser Stelle angepasst werden – die Testfälle müssen nicht geändert werden.

Erweiterung des Page-Object-Patterns

Das Page-Object-Pattern hat sich in vielen Projekten bewährt. Ein Nachteil ist jedoch, dass die Klassen sehr schnell extrem groß werden können, wenn die Seiten komplex sind – je nach Seite können weit über 1000 Zeilen entstehen. Das macht den Code schnell unübersichtlich. Basierend auf Erfahrungen unterschiedlicher Projekte habe ich das Page-Object-Pattern für mich erweitert. Die Page-Objects werden in drei Teile zerlegt:

  • Page: In dieser Klasse sind nur die Locators als Eigenschaften definiert. Die Klasse gibt lediglich die einzelnen Elemente zurück. Weitere Methoden sind nicht definiert.
  • Actions: Diese Klasse erbt von einer allgemeinen Actions-Klasse, welche allgemeine Methoden definiert. In den Actions werden Aktionen auf den Elementen ausgeführt wie Klicks, Elemente selektieren oder den Wert eines Elements zurückgeben. Weitere Logik ist darin nicht implementiert – lediglich einfache Methoden wie bspw. das Überprüfen, ob ein Menu bereits aufgeklappt ist, wenn es geöffnet werden soll. 
  • Tasks: In den Tasks definiere ich die fachliche Logik. Dazu werden entsprechende Aktionen aus den Actions nacheinander ausgeführt. Die einzelnen Tasks können sowohl weitere Tasks als auch Actions aus anderen Seiten beinhalten. Diese Tasks werden in den Tests verwendet. Ein Vorteil haben hier Sprachen wie JavaScript oder Kotlin, die Funktionen als First-Class-Citizens betrachten. Dadurch können die Tasks als reine Funktionen (ohne Klassen) definiert werden. Sind die Methodennamen sprechend gewählt, so können die Tests übersichtlicher dargestellt werden.

Eine mögliche Struktur dieser Architektur sähe so aus:

Testdaten
  • Die Testdaten sind ganz klar von den Testfällen getrennt. Entweder befinden sie sich in derselben Datei oder sind in eine andere Datei ausgelagert. Sie werden jedoch nicht hart in den Testfällen kodiert.
  • In den Testfällen werden nur Tasks aufgerufen. Es findet kein Zugriff auf Actions oder Pages statt.

Die Testfälle können Tasks aus unterschiedlichen Bereichen ausführen.

  • Tasks rufen immer Actions und Tasks aus ihren Bereichen auf. Auch der Zugriff auf Tasks anderer Seiten ist möglich. So könnte ein Task aus EditCustomer auf Tasks aus MyShop247 zugreifen.
  • Actions verwenden immer nur die Elemente ihrer Seite. Ein Zugriff auf Actions oder Elemente anderer Seiten findet nicht statt.


Für das oben genannte Beispiel ergibt sich dadurch für ein Kotlin-Projekt folgende Dateistruktur:

Dateistruktur Kotlin
  • *Page: definiert die Locators der Seite „Kundendaten bearbeiten“.
Customer Page
  • *Actions: definieren Funktionen wie „trage den Vornamen ein“.
Customer Action
  • *Tasks: definieren Funktionen wie „Trage Daten zu Nutzer X ein“.
Customer Task
  • *Tests: kombinieren unterschiedliche Tasks.
Customer Test

Fazit

Die gezeigte Architektur wurde von mir inzwischen in mehreren Projekten verwendet. Auch in Schulungen benutze ich dieses Pattern. Hier hat sich gezeigt, dass das Pattern schnell verständlich und gut anzuwenden ist. 
Durch die Anwendung des Page-Object-Patterns wird der Testcode wartbarer und robuster gegenüber Änderungen im Testobjekt. Durch die zusätzliche Aufteilung der Page-Objects in mehrere Teile werden Funktionen und Klassen zudem übersichtlicher und verständlicher. Zusätzlich werden Dopplungen von Funktionalitäten vermieden. Dies kann zu einer fehlerfreieren und robusteren Testautomatisierung beitragen. 


Über den Autor

Florian Pilz

Software Testing