×
Dynamisches Testen mit JUnit 5

Dynamisches Testen mit Junit 5



Moderne Webseiten sind heutzutage deutlich dynamischer als ihre Vorgänger vor 20 Jahren. News, Blogs oder Produktupdates führen meist zu vielen Aktualisierungen pro Tag. Bisherige Testansätze kommen in Anbetracht der Content-Flut jedoch schnell an die Grenzen des Machbaren. Wir wollen Ihnen zeigen, wie sie mit JUnit 5 und den neuen dynamischen Tests wieder Herr über ihre Webseite werden.

Warum dynamisches Testen?

Um die Problematik zu verdeutlichen, wollen wir uns eins der dynamischsten Beispiele aus dem Bereich E-Commerce anschauen: der Webshop. Bei den Giganten der Branche wie beispielsweise Amazon oder Ebay kommen sekündlich neue Produkte in den Shop, alte Produkte werden entfernt und bestehende Produktseiten werden aktualisiert. Jede einzelne Produktseite ist dabei wichtig und muss gewissen Anforderungen entsprechen. Ein fehlendes Bild oder ein falscher Preis kann hierbei recht schnell zu entgangenem Umsatz führen. In einer idealen Welt würde man also jede Produktseite am besten automatisiert abtesten.

In der Realität steht jedoch eine große Anzahl von Produktseiten einer kleinen Anzahl an Entwicklern gegenüber. Auch wenn sich große Teile des Testcodes sicherlich wiederverwenden lassen, der Aufwand für das Erstellen und Warten von Tests, die jeweils nur eine spezifische Produktseite testen, ist immens. Deswegen werden oft nur einzelne Produkte als Stichprobe mit einem solchen Test abgeprüft. So lassen sich zwar seitenübergreifende Fehler abfangen, spezifische Fehler für einzelne Produktseiten wie beispielsweise Rechtschreibfehler oder fehlende Bilder werden jedoch nicht erkannt.

Produktseite von amazon
Wer alle Produktseiten vollständig testen möchte, hat viel zu tun.

Eine mögliche Lösung für dieses Problem ist dem Test mehr Intelligenz zu verleihen und eine Art Mini-Crawler zu schreiben. Der Test sucht sich zu Beginn die zu testenden Produktseiten selbstständig zusammen, beispielsweise indem er eine Liste aller Produkte abruft oder eine bestimmte Produktkategorie aufruft und dort alle verfügbaren Produkte sammelt. Die auf diese Art gesammelten Produktseiten können nun eine nach der anderen aufgerufen und abgetestet werden. Sind alle Produktseiten in Ordnung, ist der Test erfolgreich und alles ist gut. Doch was soll passieren, wenn eine oder mehrere Produktseiten fehlerhaft sind? Bricht der Test bei der ersten fehlerhaften Seite ab, fehlen unter Umständen viele Seiten und der Test muss wiederholt werden. Sammelt der Tests alle Fehler zusammen, muss der Entwickler dafür sorgen, dass am Ende des Tests alle Fehler gut lesbar berichtet werden. Hinzu kommt, dass bei Hunderten bis Tausenden Produkten die Laufzeit eines solchen Tests sehr hoch ist. Man würde das ganze also idealerweise noch parallelisieren wollen, was zusätzlichen Aufwand und Fehlerquellen bedeutet.

Im Idealfall wollen wir die Parallelisierbarkeit und das Error Reporting von einzelnen Tests mit dem dynamischen Suchen der Testfälle miteinander kombinieren. An dieser Stelle kommt das dynamische Testen von JUnit 5 ins Spiel. Dieser Testtyp besteht aus zwei Phasen. In der ersten sogenannten TestFactory Phase werden die späteren Tests zusammengebaut. Ein Test besteht dabei aus einem Namen und einer Executable. Diese Executable beinhaltet den eigentlichen Testcode und funktioniert dabei wie jeder andere JUnit Test. In der zweiten Phase, der Test Phase, werden die Tests aus der ersten Phase ausgeführt, auf Wunsch sogar parallel. Schlägt ein Test fehl, wird dieser einzeln als fehlgeschlagen reportet, alle anderen Tests laufen trotzdem weiter.

Wie das ganze genau funktioniert, wollen wir uns anhand eines Beispiels anschauen.

Unser kleiner Beispielshop

Damit Sie das dynamische Testen in einer eigenen kleinen Umgebung selbst ausprobieren können, haben wir Ihnen ein kleines Paket geschnürt. Als Voraussetzungen benötigen Sie auf ihrem Rechner docker, Java, Maven sowie Chrome mit passendem ChromeDriver. Im Paket enthalten ist der Beispielcode aus diesem Artikel sowie eine vorinitialisierte Instanz von OpenCart, einem Open Source E-Commerce System. Dieses kann einfach lokal per docker-compose gestartet werden.

1
  docker-compose up

Das Shopsystem ist bereits mit einigen Beispielprodukten ausgestattet, von denen auch manche Fehler enthalten, die durch unsere Tests entdeckt werden sollen. Wenn Sie eigenständig Änderungen am Shopsystem vornehmen wollen, können sie sich unter “http://localhost/admin” mit dem Usernamen “admin” und dem Passwort “changeme” in den Adminbereich einloggen.

Der Beispielcode in Java liegt im Ordner “dynamic-test-example”.

The OpenCarts Shop
Die Seite, die uns als Basis unserer Tests dient. Jedes Produkt soll getestet werden, egal wie viele es sind.

Wir bauen eine Testfabrik

Das Herzstück der dynamischen Tests ist die @Testfactory-Methode. In dieser Methode werden die einzelnen Tests zusammengebaut, als Liste gesammelt und an das JUnit Framework zurückgegeben. Wir beginnen an dieser Stelle mit der Erstellung eines lokalen ChromeDrivers und navigieren zu unseren lokalen OpenCarts-Instanz. Wir nutzen die Methoden der von uns gebauten PageObjects, um zur Übersichtsseite der Notebooks zu navigieren.

1
2
3
4
5
6
7
8
9
@TestFactory
public Collection<DynamicTest> TestNotebookCatgeory () {
  //Starte einen ChromeDriver
  ChromeDriver driver = new ChromeDriver();
  //Navigiere zur Laptopübersichtseite
  driver.get("http://localhost:80");
  HomePage home = new HomePage(driver);
  NotebookOverview notebookOverview = home.navigateToLaptops();
  ...

Als Nächstes müssen wir alle Produkte auf der Übersichtsseite finden und den Namen sowie den Link zur eigentlichen Produktseite extrahieren. Mit diesen Informationen können wir nun dynamisch den Testcode der einzelnen Produktseiten zusammenbauen. Hierfür bauen wir einfach für jedes Produkt eine Executable, die den gewünschten Testcode beinhaltet. In unserem Fall bauen wir einen neuen ChromeDriver, navigieren zu den Produktseiten und überprüfen, ob das große Hauptbild existiert und ob es eine Beschreibung gibt. Ihr Testcode kann an dieser Stelle genauso komplex ausfallen wie jeder andere Selenium-Test.

Haben wir den Produktnamen extrahiert und die Executable gebaut, erstellen wir einfach einen dynamischen Testfall durch den Aufruf der statischen Methode “dynamicTest(…)” mit den entsprechenden Parametern. Als Endergebnis der TestFactory-Methode erhalten wir eine Liste von dynamischen Tests, bei denen jeder einzelne eine andere Produktseite testen.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
  ...
  //Sammel alle Produkte auf der Seite ein
  List<WebElement> products = notebookOverview.getProducts();

    //Funktion zum bauen der einzelnen Tests
    Function<WebElement, DynamicTest> buildDynamicTests = element -> {
    // Informationen vom Element einsammeln
    WebElement caption = element.findElement(By.xpath(".//*[contains(@class, \"caption\")]")).findElement(By.xpath(".//a"));
      String productName = caption.getText();
      String linkToProduct = caption.getAttribute("href");

      //Testfunktion für den Test bauen
      Executable testFunction = () -> {
        ChromeDriver testdriver = new ChromeDriver();
        try {
          //Navigiere zum Produkt
          testdriver.get("http://localhost:80");
          testdriver.get(linkToProduct);
          // Überprüfe ob Bild und Beschreibung existieren
          ProductPage productPage = new ProductPage(testdriver);
          assertTrue(productPage.checkMainImageExists(), "Main Image is missing on Product Page!");
          assertTrue(productPage.checkDescriptionExists(), "Description is missing on Product Page!");
        }
        finally {
          //Beende Driver nach dem Test
          testdriver.quit();
        }
      };
    // Aufruf von dynamicTest() zum Bauen des Tests 
    return dynamicTest("Product Test: " + productName, testFunction)
    };

    // Für jedes gefundene Produkt, baue einen Dynamischen Test
    List<DynamicTest> testList = products.stream().map(buildDynamicTests).collect(Collectors.toList());
  driver.quit();

  // Gib die Liste von dyamischen Tests zurück
  return testList;
  }

Sie können nun die Tests über ihre Entwicklungsumgebung oder dem folgenden Kommando ausführen lassen:

1
  mvn test -Dwebdriver.chrome.driver=</Path/To/chromedriver-executable> --fail-at-end

Der Test navigiert nun zunächst zur Übersichtsseite der Kategorie “Notebooks”. Nachdem die Tests zusammengebaut wurden, schließt sich der Chrome und die Ausführung der einzelnen Tests beginnt. Jeder Test öffnet eine neue Instanz von Chrome, navigiert auf die entsprechende Produktseite und überprüft Bild und Beschreibung. Stellt ein Test einen Fehler, fest wird nur dieser als fehlgeschlagen markiert. Alle anderen Tests werden trotzdem regulär ausgeführt. Sollten neue Notebooks hinzukommen oder Notebooks aus dem Webshop gelöscht werden, passen sich die Tests automatisch entsprechend an.

Und schalten den Turbo ein

JUnit 5 unterstützt auch die parallele Ausführung von Tests als experimentelles Feature. In Kombination mit dynamischen Tests ergbit das vor allen Dingen dann Sinn, wenn viele Tests auf einmal generiert werden. Die eigentliche Orchestrierung übernimmt dabei JUnit5. Die Testklasse muss hierfür mit der Annotation @Execution(ExecutionMode.CONCURRENT) versehen werden. Anschließend müssen folgende Einträge in der junit-platform.properties gesetzt werden. Die Tests werden nun automatisch parallel ausgeführt.

1
2
junit.jupiter.execution.parallel.enabled = true
junit.jupiter.execution.parallel.mode.default = concurrent

Weitere Informationen zur parallelen Ausführung von Tests finden Sie in der offizielle Dokumentation.

Fazit

Mit den dynamischen Tests von JUnit 5 wird eine elegante und wartungsarme Lösung für das Testen von dynamischem Content angeboten, beispielsweise Produkte in einem Webshop oder Blogeinträge auf einer Website. Diese Art von Test skaliert automatisch mit neuem Content und ermöglicht so eine viel breitere Testabdeckung als von Hand geschriebene Tests. Gleichzeitig bleibt das Reporting übersichtlich und kann so zu einer schnellen Fehlerbehebung beitragen.

Besonders in Kombination mit der parallelen Ausführung kann so eine Vielzahl von Tests in kurzer Zeit ausgeführt werden. Die Wartung der entsprechenden Infrastruktur kann jedoch schnell aufwendig und teuer werden. Hierbei kann Ihnen unser Produkt webmate helfen, indem es eine skalierbare und wartungsfreie Testinfrastruktur bereitstellt. Vereinbaren Sie jetzt Ihren Demotermin!

Wählen Sie Ihren Wunschtermin!

Zum Kalender