Difference Testing mit recheck-web: Von der Installation bis zur Stabilisierung

recheck-web ist ein Difference-Testing Tool auf Basis von Selenium. Es unterstützt semantisches Golden-Master-Testing ebenso wie Visual Regression Testing, Cross-Browser- oder Cross-Device-Testing. Dieses kurze Tutorial ist keine Produktdemo, es führt lediglich in die Prinzipien und Vorgehensweisen ein.

“Assertion Testing” ist der klassische, aus JUnit stammende Ansatz, bei dem manuell erstellte Assertion-Statements bei der Testausführung als Testorakel (zur Unterscheidung zwischen pass/fail) dienen. “Difference Testing” ist im Gegensatz dazu ein Golden-Master-basierter Testansatz, der die Unterschiede zwischen verschiedenen Ausführungen verdeutlicht, beispielsweise zwischen unterschiedlichen Versionen.

Assertion Testing ist ein Blacklist-Ansatz, es werden also nur Unterschiede erkannt, die zuvor per Assertion explizit definiert wurden. Difference Testing ist hingegen ein Whitelist-Ansatz. Hier werden zunächst alle Unterschiede (auch unerwartete) erkannt, irrelevante lassen sich dann ignorieren.

Eine Schritt-für-Schritt-Anleitung

  • zum Installieren und Einrichten von recheck-web mit dem Build-Tool Maven und recheck.cli
  • zum Erstellen eines ersten ausführbaren Difference-Testing-basierten Java-Testfalls
  • sowie zum Stabilisieren der Testausführung, indem irrelevante Änderungen ignoriert werden.

Darüber hinaus zeigt es einige typische Anwendungsszenarien in der Entwicklung und Testwartung.

Am Anfang steht die Installation

Voraussetzung für den Einsatz von recheck-web ist, dass Java und Maven auf dem System installiert sind. Entwickler können das überprüfen, indem sie ein Terminal beziehungsweise CMD öffnen und die folgenden Befehle ausführen:

java -version
mvn –version
Die Ausgabe sollte keinen Fehler enthalten und anzeigen, dass eine Java-Version von 8 oder höher installiert ist. Nun lässt sich ein neuer Ordner (z. B. recheck-web-tutorial) und darin eine einfache pom.xml-Datei mit folgendem Inhalt erstellen:
?<project>
<modelVersion>4.0.0</modelVersion>
<groupId>com.mycompany</groupId>
<artifactId>recheck-web-tutorial</artifactId>
<version>0.1-SNAPSHOT</version>

<properties><maven.compiler.source>1.8</maven.compiler.source>
<maven.compiler.target>1.8</maven.compiler.target>
</properties>

<dependencies>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.12</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>de.retest</groupId>
<artifactId>recheck-web</artifactId>
<version>1.0.1</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.seleniumhq.selenium</groupId>
<artifactId>selenium-java</artifactId>
<version>3.141.59</version>
<scope>test</scope>
</dependency>
</dependencies>
</project>

Nach dem Zugriff auf die bevorzugte IDE (z.B. mittels mvn eclipse:eclipse) lässt sich eine erste Testklasse erstellen. Vor dem Ausführen eines Selenium-Tests müssen Entwickler zunächst einen Driver beziehungsweise Browser nach Geschmack und Betriebssystem herunterladen und einrichten, etwa Chrome.

Zur Installation des Chrome-Drivers entpackt man das Archiv auf die Festplatte. Entwickler müssen hier beachten, dass sie zusätzlich eine passende Version von Chrome auf ihrem System benötigen. Danach sollte alles vorbereitet sein.

Der erste recheck-web-Testfall
Ein einfacher Test mit Selenium und recheck-web könnte in Java ungefähr so aussehen:

?package com.mycompany;

import org.junit.*;
import org.openqa.selenium.*;
import de.retest.recheck.*;

public class MyFirstTest {

private WebDriver driver;
private Recheck re;

@Before
public void setup() {
? re = new RecheckImpl();
? System.setProperty(“webdriver.chrome.driver”, “C:\\pathto\\chromedriver.exe”);
driver = new ChromeDriver();
}

@Test
public void google() throws Exception {
re.startTest();

driver.get(“http://google.com”);
re.check(driver, “open”);

re.capTest();
}

@After
public void tearDown() {
driver.quit();
re.cap();
}
}

Die mit @Before annotierte Methode erstellt sowohl die zu verwendende Recheck-Instanz als auch den ChromeDriver. Die mit @Test annotierte Methode teilt zuerst recheck mit, dass der Test startet (durch Aufruf von startTest), und lädt dann die Google-Startseite in Chrome. Danach wird die aktuelle Version der Seite überprüft, indem sie mit dem Aufruf von check mit dem Golden Master (wird später erklärt) verglichen wird. Dabei vergibt das Werkzeug eine semantische und eindeutige Kennung.

In einem typischen umfangreicheren Test rufen Entwickler check mehrmals auf, jedes Mal mit einer anderen eindeutigen Kennung. Da Unterschiede nicht so ungewöhnlich sind, ist es nicht gewünscht, dass der Test bei einem kleinen Unterschied sofort fehlschlägt. Deshalb werden bei den Aufrufen der Prüfmethode alle Abweichungen gesammelt. Um den Test im Fall von Differenzen fehlschlagen zu lassen, rufen Entwickler am Ende des Tests die Methode capTest auf. Sollten sie den Aufruf vergessen, findet sich eine entsprechende Meldung in der Logausgabe. Nach Abschluss des Tests beendet die @After-Methode Chrome durch den Aufruf von driver.quit() und veranlasst via cap das Erstellen einer zusammenfassenden Berichtsdatei inklusive aller Änderungen.

Lokale Ausführung eines ersten Testfalls
Nun sollten Entwickler den Test mit mvn test ausführen können. Wenn alles korrekt eingerichtet wurde, sieht man beim ersten Ausführen des Tests die folgende Fehlermeldung:

?java.lang.AssertionError: ‘com.mycompany.MyFirstTest’:
No recheck file found. First time test was run? Created recheck file now, don’t forget to commit…
at de.retest.recheck.RecheckImpl.capTest(RecheckImpl.java:137)
? at com.mycompany.MyFirstTest.google(MyFirstTest.java:30)
at … some more stack trace …
Die erneute Überprüfung erfolgt, indem der aktuelle Zustand der Software (d. h. in diesem Fall der Website) mit einer Referenz namens Golden Master aus einem früheren Zustand der Software verglichen wird. Lässt sich kein solcher Master finden, wirft recheck einen Fehler. Das ist das erwartete und gewünschte Verhalten. Denn sollten Entwickler vergessen, ihren Golden Master in ihrem Versionskontrollsystem zu committen, oder ein Pfad ändert sich und der Golden Master ist nicht auffindbar, dann ist ein Fehlschlagen des Tests erwünscht.

Der Test muss also beim ersten Ausführen fehlschlagen. Aber recheck erzeugt genau dann den Golden Master und legt ihn unter src\test\resources\retest\recheck\com.mycompany.MyFirstTest\google.open.recheck ab. Im Ordner befindet sich nun eine XML-Datei, die alle Attribute jedes HTML-DOM-Elements nach dem Rendern der Website enthält, sofern es sich nicht um die Standardwerte handelt, zusammen mit einem Screenshot der Website. Wenn Entwickler nun ihren Test erneut ausführen, werden alle Elemente und Attribute des Golden Master mit den aktuellen HTML-DOM-Elementen verglichen. Jede nicht ignorierte Differenz eines Elements führt zum Fehlschlagen des Tests. Führen Entwickler das erneut aus (wiederum mit mvn test), wird eine Ausgabe (ohne ignorierte Elemente) ähnlich der folgenden erzeugt:

?java.lang.AssertionError:
A detailed report will be created at ‘target\test-classes\retest\recheck\com.mycompany.MyFirstTest.report’. You can review the details by using our GUI from https://retest.de/review/.

The following differences have been found in ‘com.mycompany.MyFirstTest'(with 1 check(s)):
Test ‘google’ has 37 differences in 1 states:
open resulted in:
A [About Google] at ‘HTML[1]/BODY[1]/DIV[1]/DIV[3]/DIV[1]/DIV[1]/A[1]’:
? ping: expected=”some gibberish”
? … many more differences …

Die erneute Prüfung zeigt wirklich alle Unterschiede aller Attribute an. Das ist vergleichbar mit der Verwendung von Git ohne Konfiguration in einem bestehenden Projekt: Es zeigt alle Unterschiede, einschließlich Protokolldateien, Binärdateien und vielen anderen Dateien, die normalerweise nicht von Interesse sind. Glücklicherweise erlaubt es Git, diese Unterschiede via .gitignore einfach zu ignorieren.

recheck funktioniert ähnlich: Entwickler können alle volatilen und nicht relevanten Unterschiede einfach ignorieren. Das Werkzeug hat einen Ordner mit dem Namen .retest im Projektverzeichnis erstellt. Dort findet man ein Beispiel für die Datei recheck.ignore. Um diese flüchtigen Elemente zu ignorieren und den gegebenen Test grün werden zu lassen, müssen Anwender nur diese Klartextdatei bearbeiten. Das Einfügen des folgenden Inhalts in die Datei sollte den Test erfolgreich durchlaufen lassen:

?attribute=ping
attribute=jsdata
attribute=data-.*
attribute=class
attribute=outline
attribute=transform
Da Google seine Website ständig ändert, müssen Entwickler möglicherweise einige weitere Attribute hinzufügen. Aber das ist nicht schwierig, und selbst das Ignorieren von Attributen durch Platzhalter (mit dem Mechanismus des Java-Pattern) ist möglich. Die erzeugte Datei recheck.ignore enthält Beispiele, wie man ganze Teilbäume des DOM oder bestimmte Attribute bestimmter Elemente vernachlässigen kann. Weitere Informationen finden sich in der Dokumentation. Der Mechanismus erlaubt es zum Beispiel, die Schriftart eines Texts zu ignorieren, aber nicht den Text selbst. Der semantische Ignore-Mechanismus ist eines der Kernmerkmale von recheck.

Je nachdem, was Entwickler in der Ignore-Datei angeben, lassen sich verschiedene Testszenarien realisieren. Der allgemeine Mechanismus von recheck ermöglicht es, Funktionstests, Cross-Browser- und Cross-Device-Tests sowie visuelle Regressionstests zu erstellen und durchzuführen. Für die letzteren Testzwecke sollten Entwickler vorsichtig sein mit dem, was sie ignorieren. Für reine Funktionstests können sie meist problem- und risikolos viele CSS-Attribute vernachlässigen.

Pflegen des Golden Master
Wenn Entwickler einen automatisierten Test erstellen, möchten sie, dass er grün ist, wenn sich nichts ändert. Der eigentliche Vorteil eines automatisierten Tests besteht jedoch darin, die Auswirkungen von Änderungen aufzuzeigen, insbesondere unbeabsichtigte Nebenwirkungen. Während der normalen Softwareentwicklung entstehen jedoch regelmäßig viele Änderungen. Mit einem Versionskontrollsystem kann man sie einfach akzeptieren, das heißt commiten. Mit recheck benötigt man die gleichen Funktionen, um den Golden Master zu pflegen.

Dafür müssen Entwickler zum Beispiel die recheck CLI installieren. Dafür entpackt sie einfach das Archiv zum Beispiel in C:\Program Files\recheck.cli-1.0.0. Dann müssen sie noch die Datei recheck.cmd oder recheck (je nach Betriebssystem) zu ihrem Pfad hinzufügen. Wie das zu bewerkstelligen ist, hängt stark vom Betriebssystem und dessen Version ab. Für Windows 10 funktioniert es beispielsweise so: Zuerst die “Einstellungen” öffnen. Dann im Suchfeld “Umgebung” eingeben und “Bearbeiten der Umgebungsvariablen” wählen. Danach klickt man auf die Registerkarte Erweitert | Umgebungsvariablen | Pfad | Bearbeiten | Neu. Schließlich fügt man den Pfad zum Ordner recheck/bin hinzu.

Ob alles funktioniert hat, können Entwickler überprüfen, indem sie recheck –version in eine neu gestartete CMD eingeben. Die Ausgabe sollte die aktuelle Version von recheck anzeigen, das heißt recheck CLI version 1.0.0.

Um Änderungen für die Prüfung und Pflege zu generieren, öffnen Entwickler einen Browser und gehen zu scratchpad.io. Wer die Seite öffnet, wird an eine eindeutige URL weitergeleitet (z. B. http://scratchpad.io/recheck-45678). Jetzt bearbeiten sie den Test aus dem vorherigen Abschnitt und ersetzen den Methodennamen google durch scratchpad und die URL durch die gerade neu erstellte, eindeutige URL von Scratchpad. Der Methodenrumpf sollte dann so ähnlich aussehen:

? @Test
public void scratchpad() throws Exception {
re.startTest();
driver.get(“http://scratchpad.io/recheck-45678”);
re.check(driver, “open”);

re.capTest();
}

Jetzt können Entwickler den Test mit mvn test erneut ausführen. Wie erwartet, schlägt er beim ersten Mal fehl, da recheck keinen Goldenen Master für den Test scratchpad finden kann. Aber unter src/test/resources/… wurde ein Golden Master erstellt. Wenn Entwickler den Test nun zum zweiten Mal ausführen, schlägt er immer noch fehl, da die Website eine volatile URL enthält. Später wird gezeigt, wie man das auf eine ausgefeiltere Art und Weise behandeln kann. Aber vorerst sollen Entwickler ihre neu installierte recheck CLI verwenden. Dazu navigieren sie in der CMD in das Projektverzeichnis. Dort geben sie dann recheck ein, um alle verfügbaren Befehle anzuzeigen. Die Ausgabe sollte etwa so aussehen:
?C:\Users\retest\Desktop\recheck-web-tutorial>recheck
Usage: recheck [–help] [–version] [COMMAND]
Command-line interface for recheck.
–help Display this help message.
–version Display version info.
Commands:
version Display version info.
diff Display given differences.
commit Accept given differences.
ignore Ignore given differences.
completion Generate and display auto completion script.
help Displays help information about the specified command
Jetzt sollen alle irrelevanten Änderungen für den Test automatisch ignoriert werden. Um das zu tun, geben Entwickler einfach recheck ignore –all target\test-classes\retest\recheck\com.mycompany.MyFirstTest.report ein. Das fügt automatisch die folgende Zeile zur recheck.ignore-Datei hinzu:
?matcher: xpath=HTML[1]/BODY[1]/DIV[1]/P[3]/IFRAME[1], attribute: src
Das lässt recheck nur ein Attribut eines Elements ignorieren: ein Twitter-API-bezogenes IFrame. Nun sollte die Wiederholung des Tests (mvn test) einen erfolgreichen Build und einen bestandenen Test anzeigen. Jetzt können Entwickler mit dem regulären Browser zu der URL wechseln, die sie auch im Test öffnen (z. B. http://scratchpad.io/recheck-45678) und die angezeigten Inhalte bearbeiten. Zum Beispiel ersetzen sie hier <h1>Welcome to <span>scratchpad.io</span></h1><br> auf der linken Seite der Website mit <h1>Welcome to <span>recheck</span></h1><br>. Wenn sie danach den Test erneut durchführen, sollte das zu folgendem Ergebnis führen:
?The following differences have been found in ‘com.mycompany.MyFirstTest'(with 1 check(s)):
Test ‘scratchpad’ has 7 differences in 1 states:
open resulted in:
textnode [scratchpad.io] at ‘HTML[1]/BODY[1]/DIV[3]/DIV[2]/DIV[1]/DIV[3]/DIV[23]/textnode[2]’:
text: expected=”scratchpad.io”, actual=”recheck”
DIV at ‘HTML[1]/BODY[1]/DIV[3]/DIV[2]/DIV[1]/DIV[5]/DIV[1]’:
left: expected=”210.125px”, actual=”173.75px”
right: expected=”252.813px”, actual=”289.188px”
style: expected=”left: 210.125px; top: 286px; width: 6.0625px; height: 13px;”, actual=”left: 173.75px; top: 286px; width: 6.0625px; height: 13px;”
TEXTAREA at ‘HTML[1]/BODY[1]/DIV[3]/TEXTAREA[1]’:
left: expected=”255.25px”, actual=”218.875px”
right: expected=”148.297px”, actual=”184.672px”
style: expected=”top: 285px; height: 13px; width: 6.0625px; left: 255.25px;”, actual=”top: 285px; height: 13px; width: 6.0625px; left: 218.875px;”
at de.retest.recheck.RecheckImpl.capTest(RecheckImpl.java:137)
Jetzt sieht man, dass Scratchpad ein style-Attribut generiert und anpasst. Da alle relevanten Style-Informationen gerendert und damit durch einzelne CSS-Attribute repräsentiert werden, können Entwickler den ignorierten Attributen style hinzufügen. Die Wiederholung des Tests zeigt nun die erwarteten Unterschiede im text und resultierend daraus auch in den Attributen left und right.

Angenommen, dies ist eine beabsichtigte Änderung und man will den Golden Master aktualisieren. Dazu können Entwickler nun ein CMD im Projektordner öffnen und den folgenden Befehl ausführen:

?recheck commit –all \target\test-classes\retest\recheck\com.mycompany.MyFirstTest.report
Das Ergebnis des Aufrufs sollte ungefähr so aussehen:
?
Updated SUT state file C:\Users\retest\Desktop\recheck-web-tutorial\src\test\resources\retest\recheck\com.mycompany.MyFirstTest\scratchpad.open.recheck
Wenn es mehr als einen Golden Master gäbe, würden nun alle aktualisiert. Verwalten Entwickler ihre Golden-Master-Dateien in einem Versionskontrollsystem, werden diese nun als geändert angezeigt und müssten zum Beispiel in Git committet werden. Jetzt lässt sich der Test erneut ausführen (mvn test) und sehen, ob das Update funktioniert hat: Der Test sollte prüfen, ob die Seite “welcome to recheck” enthält und entsprechend erfolgreich sein.

Um weitere Funktionen der recheck CLI zu zeigen, passt man den Inhalt der Scratchpad-Seite noch einmal an. Dazu öffnen Entwickler den Browser und ändern die Begrüßungsnachricht auf recheck-web. Das Ausführen des Tests sollte den Unterschied erneut aufzeigen und eine Berichtsdatei erstellen. Entwickler können nun die recheck CLI nutzen, um den Inhalt der Datei anzuzeigen, indem sie folgenden Befehl ausführen:

recheck diff target\test-classes\retest\recheck\com.mycompany.MyFirstTest.report
Das sollte zu einer Ausgabe ähnlich der folgenden führen:
Checking test report in path ‘C:\Users\retest\Desktop\recheck-web-tutorial\target\test-classes\retest\recheck\com.mycompany.MyFirstTest.report’.
Reading JS ignore rules file from C:\Users\retest\Desktop\recheck-web-tutorial\.retest\recheck.ignore.js.
Specified JS ignore file has no ‘shouldIgnoreAttributeDifference’ function.
Specified JS ignore file has no ‘shouldIgnoreElement’ function.

Test ‘scratchpad’ has 5 differences in 1 states:
open resulted in:
textnode [recheck] at ‘HTML[1]/BODY[1]/DIV[3]/DIV[2]/DIV[1]/DIV[3]/DIV[23]/textnode[2]’:
text: expected=”recheck”, actual=”scratchpad.io”
DIV at ‘HTML[1]/BODY[1]/DIV[3]/DIV[2]/DIV[1]/DIV[5]/DIV[1]’:
left: expected=”173.75px”, actual=”210.125px”
right: expected=”289.188px”, actual=”252.813px”
TEXTAREA at ‘HTML[1]/BODY[1]/DIV[3]/TEXTAREA[1]’:
left: expected=”218.875px”, actual=”255.25px”
right: expected=”184.672px”, actual=”148.297px”

Fazit

Das Tutorial hat verdeutlicht, wie man recheck-web und CLI einrichtet und einen auf Difference Testing basierenden automatisierten Selenium-Test erstellt und pflegt. Der Testansatz hat viele Vorteile gegenüber assertionsbasierten Tests. Er ist einfacher einzurichten – keine Identifizierung einzelner Elemente zum Beispiel über XPath oder ID mehr. Er ist einfacher zu warten – statt neue Werte manuell zu kopieren und einzufügen, um den Erwartungswert zu aktualisieren, genehmigen Entwickler die Änderungen mit einem einzigen Befehl. Aber der größte Vorteil ist, dass die Tests viel vollständiger sind als ihre Assertions-basierten Gegenstücke: Sie können gar keine Assertion für eine unerwartete Änderung anlegen. Mit Difference Testing dagegen bleibt keine Veränderung unbemerkt.

Ein semantischer Vergleich ist auch im Vergleich zu den vielen pixelbasierten Tools von Vorteil, die einen Golden-Master-Testansatz implementieren. Heutige Websites sind sehr dynamisch. Mit dem transition-Attribut von CSS möchten Entwickler mehr als nur das reine Aussehen visuell überprüfen, sie möchten auch das dynamische Ladeverhalten untersuchen. Das Überprüfen der CSS-Attribute weist sie direkt auf Änderungen hierbei hin, die sie dann manuell überprüfen können. Statt ganze Bildbereiche grob zu ignorieren, kann man spezifisch angeben, was ignoriert werden soll. Man kann zum Beispiel Schriftart und -größe ignorieren, aber nicht den Text. Man kann sogar ein regelbasiertes Ignorieren von Änderungen implementieren (bspw. Änderungen mit einer Differenz von weniger als 5 Pixel).

Aber dieses regelbasierte Ignorieren wird Teil eines folgenden und tiefer gehenden Tutorials über die erweiterten Funktionen von recheck sein. Es zeigt dann, wie man Tests fast “unzerbrechlich” machen kann, indem man im Golden Master nachschaut, wie der alte Zustand vor einer “breaking change” aussah. Und es wird gezeigt, wie man bestehende herkömmliche Tests migriert und recheck mit anderen Tools wie CI/CD oder Drittanbieter-Testframeworks wie Cucumber integriert.

Der hier gezeigte Code lässt sich auch hier herunterladen.