Geb Starthilfe - Grails Functional Testing

Mittels Unit- und Integration-Tests kann die Qualität einer Grails Web-Applikation zum Großteil sichergestellt werden.

Um eine Funktionalität aus Nutzersicht zu überprüfen ist eine andere Herangehensweise erforderlich: Man startet die Applikation und überprüft per Browser ob die verschiedenen Anwendungsfälle wie gewünscht durchgeführt werden können.

Um langfristig zu gewährleisten, dass die Funktionalität erhalten bleibt, müsste man die Schritte nach jeder weiteren Code-Änderung wiederholen.

Dies ist umständlich bis unmöglich und kann in Grails mittels Geb automatisiert werden.

Anwendungsfälle von funktionalen Tests

Es gibt unendlich viele Möglichkeiten durch eine Web-Applikation zu navigieren. Dementsprechend sinnlos ist der Versuch “möglichst Alles” zu testen.

Folgende Szenarien sind aus meiner Sicht für “functional testing” interessant:

Sicherstellung von Kern-Funktionalität

Die Durchführbarkeit, zumindest jener Anwendungsfälle ohne welche die Web-Applikation nutzlos werden würde, kann gut über funktionale Tests sichergestellt werden.

Planung von Usability-Tests

Bei der Planung von Tests der Benutzerfreundlichkeit kann man den idealen Ablauf zur Lösung der Aufgabe definieren und damit sicherstellen, dass zumindest dieser Weg zum Zeitpunkt des Tests mit dem Teilnehmer funktionieren wird.

Regressionsvermeidung

Regressionen – für mich einer der Hauptgründe überhaupt Tests zu schreiben.

Für jeden gefundenen Programmfehler wird ein Test geschrieben, welcher diesen Fehler reproduziert. Dann implementiert man die Lösung so, dass der Test erfolgreich verläuft – natürlich ohne dabei den Test zu ändern.

Somit wird eine Applikation langfristig immer “besser”, da sich ein bereits gefundener Bug unmöglich erneut in das Programm einschleichen kann.

Starthilfe zur Geb-Entwicklung

Wie der Titel der offiziellen Dokumentation, “The Book of Geb”, vermuten lässt, ist Geb ist umfangreich dokumentiert.

Zumindest das Einführungskapitel solltest du an dieser Stelle gelesen haben.

Eine Sammlung weiterer wichtiger Ressourcen ist am Ende dieses Artikels zusammen gefasst.

Im Folgenden möchte ich auf einige Hürden und Tücken hinweisen um dir einen einfachen Einstieg in Geb zu ermöglichen.

Geb-Hürde 1: Abhängigkeitskonflikt verhindert sämtliche Grails Kommandos

Error executing script Clean: loader constraint violation: when resolving overridden method "org.apache.xerces.jaxp.SAXParserImpl.getParser()Lorg/xml/sax/Parser;" the class loader (instance of org/codehaus/groovy/grails/cli/support/GrailsRootLoader) of the current class, org/apache/xerces/jaxp/SAXParserImpl, and its superclass loader (instance of), have different Class objects for the type org/xml/sax/Parser used in the signature

Umgang mit der Hürde

Diesen Fehler konnte ich durch Ausschluss der Abhängigkeit zu xercesImpl bei selenium-htmlunit-driver beheben:

/** Ausschnitt aus der Datei BuildConfig.groovy **/
// ...
grails.project.dependency.resolution = {
// ...
  dependencies {
    test "org.codehaus.geb:geb-spock:0.6.0"

    def driverVersion = '2.1.0' // slow startup if set to 'latest.release'
    test("org.seleniumhq.selenium:selenium-htmlunit-driver:${driverVersion}") {
      exclude 'xml-apis'
      exclude 'xercesImpl' // IMPORTANT!
      //exclude 'xmlParserAPIs'
    }
    test "org.seleniumhq.selenium:selenium-firefox-driver:${driverVersion}"

    // other drivers ...
  }
}

Das Problem ist im Grail – user Forum erläutert.

Geb-Hürde 2: Abstraktionsebenen erschweren den Einstieg

Beim Einsatz von Geb in Grails müssen viele Ebenen zusammen arbeiten:

  • Grails Geb Plugin
  • Eigentliches Geb
  • Grails Testing Framework / Spock
  • Selenium WebDriver
  • Browser (HtmlUnit, Firefox, Chromium, IE)

Schwierigkeiten sind vor allem beim Grails Plugin (aufgrund der mangelhaften Dokumentation) und den Browsern oder “Browser-Drivers” wahrscheinlich.

Umgang mit der Hürde

Um das Grails Plugin möglichst schnell verstehen zu lernen ist Luke Daley’s Beispielprojekt auf GitHub hilfreich.

Übernimm am besten die Beispieldateien in dein Projekt und passe sie dann entsprechend an.

Geb-Hürde 2: Unzureichende Spezifizierung erschwert die Identifikation

Um innerhalb eines Tests überprüfen zu können, auf welcher Seite sich Geb gerade befindet, benötigt es gewisse Anhaltspunkte. Die Summe der Anhaltspunkte muss die jeweilige Seiten korrekt und vor allem eindeutig identifizieren.

Im oben genannten Beispielprojekt kannst du anhand der static at-Deklarationen erkennen, dass die einzelnen Pages hauptsächlich über ihren Titel identifiziert werden.

Oft reicht dies aus.

Spätestens bei einer mehrsprachigen Applikation schlägt es in dieser Form allerdings fehl.

Das entsprechende Stacktrace ist wenig hilfreich. Unter anderem fehlt ein Hinweis auf welcher Seite man sich denn – anstelle der erwarteten – befindet.

Condition not satisfied: at LoginPage | false

junit.framework.AssertionFailedError: Condition not satisfied:

at LoginPage
|
false

Versäumst du die eindeutige Definition, öffnet das also viele Fehlerquellen.

Umgang mit der Hürde

Es ist also sinnvoll, eine Seite über zusätzliche Elemente zu identifizieren.

static at = {
  // heading, ...
  title =~ /(?i)melde/
  $( "input.loginbtn", id:'loginbtn').value() =~ /(?i)anmelden/
}

Geb-Hürde 3: jQuery 1.6 wird in HtmlUnit fehlerhaft ausgeführt

Bei funktionialen Tests wird ein am System verfügbarer Browser gesteuert.

Man sieht also beispielsweise das Firefox-Fenster in welchem die definierten Anweisungen durchlaufen werden.

Andererseits fungiert auch HtmlUnit als Browser, bietet allerdings die Möglichkeit die Äblaufe im Hintergrund vorzunehmen.

Aufgrunddessen ist HtmlUnit für Build-Automation die erste Wahl.

Verwendet die zu testende Applikation jQuery, kommt es bei den aktuellen Versionen (1.6.x) zu einem Fehler:

The data necessary to complete this operation is not yet available.

In einem Blogbeitrag von Steve Liles ist die Ursache ausführlich beschrieben.

Umgang mit der Hürde

Um das Problem in unserem Fall zu umgehen muss die Deklaration von driver in der Datei GebConfig.groovy so geändert werden, dass ein anderer Browser verwendet wird. Zum Beispiel:

def driver = new HtmlUnitDriver(BrowserVersion.FIREFOX_3_6) // fails if BrowserVersion is left to the default value

Geb-Hürde 4: Fehler der Browser und Browser-Driver wirken sich im Test aus

Durch die Möglichkeit, verschiedene Browser automatisch steuern zu lassen, wirken sich die Eigenheiten und Bugs der jeweiligen Browser auf unsere Tests aus.

Zum Beispiel gibt es bei der Verwendung von Firefox scheinbar ein Problem beim Absenden von Formularen mittels ENTER 1, welches bei der Nutzung des FirefoxDriver auftritt.

Mit HtmlUnit hingegen funktioniert das Senden wie erwartet.

Umgang mit der Hürde

Im Zweifelsfall ist es daher sinnvoll, die Test’s mit unterschiedlichen Browsern laufen zu lassen. Die entsprechenden Befehle sind als Kommentare in der GebConfig.groovy Beispieldatei vermerkt.

Geb-Hürde 5: Mehrsprachigkeit erschwert das Finden von Elementen

Standardmäßig stellt Grails die Sprache einer Applikation entsprechend der Sprachkonfiguration im Browser des Besuchers ein.

Wenn also Firefox auf Deutsch und HtmlUnit auf Englisch eingestellt ist, fehlen möglicherweise Seiteninhalte, welche zur Identifikation oder Navigation einer Seite genutzt werden.

Die bereits früher im Artikel beschriebene Definition würde auf jene Seiten zutreffen, welche “melde” (in Groß-, Klein- oder gemischter Schreibweise) enthalten:

title =~ /(?i)melde/

Allerdings ist der Inhalt des Title-Elements auf Englisch wahrscheinlich anders.

Umgang mit der Hürde

Um auch die Zeichenkette “Login” gelten zu lassen, muss die Definition entsprechend angepasst werden:

title =~ /(?i)melde|(?i)login/

Alternativ könnte man dafür sorgen, dass alle Browser-Driver die selbe Sprache anfordern. Ich habe darauf verzichtet eine Lösung dafür zu suchen, wäre aber an einer interessiert.

Geb-Hürde 6: Die Verwendung von Variablen führt zu Fehlern

In einer Spezifikation hatte ich versucht die Werte über eine Variable zu setzen:

def subject = "Subject for testing the contact form via Geb"
// ... some logic
contactForm.subject = subject

Offensichtlich versucht Geb in diesem Fall ein Element namens subject auf der Website zu finden, anstatt die deklarierte Variable zu verwenden:

@No such property: subject for class: geb.navigator.EmptyNavigator

groovy.lang.MissingPropertyException: No such property: subject for class: geb.navigator.EmptyNavigator
at geb.navigator.EmptyNavigator.propertyMissing(EmptyNavigator.groovy:151)
at geb.navigator.Navigator.getProperty(Navigator.groovy)@

Umgang mit der Hürde

Ob und wie es möglich ist, Variablen zu nutzen ist mir derzeit unbekannt.

Wenn du auf Variablen verzichten kannst, weise einfach Strings zu.

Ressourcen

Folgende Ressourcen sind im Zusammenhang mit Geb hilfreich.

Dokumentation & Beispiele

Verwendete Technologien

Fehlerdetails