Facebook
Twitter
Google+
Kommentare
5
Willkommen bei "the web hates me". Mittlerweile hat unser Team ein tolles neues Monitoringtool für Agenturen gelauncht. Unter dem Namen koality.io haben wir einen Service geschaffen, der er Agenturen ermöglicht mit sehr geringen Kosten alle Ihre Webseiten zu überwachen.

Integration von Selenium in PHPUnit

Das Thema Unit-Testing und damit die Qualitätssicherung sind Evergreens. Weil wahrscheinlich jeder Entwickler bereit Erfahrungen mit Unit Frameworks gemacht hat liegt es nahe sich anzusehen, wie Oberflächentests erstellt und in Unit-Testsuiten integriert werden. Eine Einführung zum ersten Punkt wurde im Artikel Selenium – Grundkurs gegeben. Dieser Artikel beschäftigt sich damit, wie man Oberflächentests und Unit-Test zusammen bringt.

Welche Tools benötige ich?

Grundlage der Nutzung ist das Aufnehmen der Tests mittels Selenium IDE (Firefox Plugin). Da das PHPUnit-Framework nichts von Der IDE weiß, benötigt man einen Interpreter als Ersatz, der Selenese – so nennt man das HTML-Format in dem Selenium-Test per default gespeichert werden – verstehen kann: Den Selenium RC. Das ist ein Java-Server, der mittels HTTP angesprochen werden kann und die Selenese-Befehle im gewünschten Browser ausführt.

Als Übersetzer zwischen PHPUnit und dem Selenium RC fungiert ein PHP-Client. Ich habe Erfahrungen sowohl mit dem PEAR-Paket Testing_Selenium, als auch mit dem PHPUnit eigenen Wrapper PHPUnit_Extensions_SeleniumTestCase gesammelt. Aufgrund der besseren Dokumentation empfehle ich PHPUnit_Extensions_SeleniumTestCase und beziehe mich bei den Beispielen darauf.

Wir starten an dieser Stelle mit einer eigenen Klasse GUITest extends PHPUnit_Extensions_SeleniumTestCase.
Warum ein eigener Wrapper von Vorteil ist wird später erwähnt. Das Wichtigste an dieser Klasse ist das Setup, sodass PHPUnit weiß wo der RC zu finden ist und auf welcher Url der Test läuft.

/*
* \brief GUI test base functionality
*/
class GUITest extends PHPUnit_Extensions_SeleniumTestCase
{
/**
* \brief setup
*/
public function setUp()
{
// ip oder url
$this->setHost('192.168.1.1');
// der Port auf dem der RC lauscht
$this->setPort(5000);
// liste aller Browser im Anhang
$this->setBrowser('*firefox');
// url auf der der Test laeuft
$this->setBrowserUrl('www.google.de');
}
}

Wie laufen Selenium-Tests in PHPUnit?

Der erste – und einfachere – Weg ist das Abspielen der bereits aufgenommenen Test via GUITest->runSelenese( string $filename ) Die Methode runSelenese() ist in PHPUnit_Extensions_SeleniumTestCase definiert. Der Parameter $filename beschreibt den Pfad zum Selenese-Testfile. Das funktioniert größtenteils, jedoch fehlen PHPUnit_Extensions_SeleniumTestCase einige Methoden, die die Selenium IDE verwendet. Mehr zu dieses Problem später.

Der zweite Ansatz bezieht sich auf PHP-Quelltext. Selenese-Files lassen sich via IDE in PHP-Code umwandeln. Allerdings ist das Ergebnis meines Erachtens durch viele unnötige try-catch-Blöcke und Loops unsauber. Try-Catch hat in diesen Tests einfach nichts zu suchen.

Ein dritter sehr sauberer Weg ist das eigenhändige Schreiben des kompletten Tests. Wir öffnen die zu testende Webseite, suchen den X-Path des zu überprüfenden Elements und führen Operationen darauf auf.

Was im Allgemeinen am Ende bei drei Methoden raus kommt, ist ein durch PHPUnit_Extensions_SeleniumTestCase generierter HTTP Call an den Selenium RC, dessen Antwort in den verschiedenen Asserts von PHPUnit verarbeitet wird.

Grundlegend nutzt man Selenium für folgende Asserts:

  • assertTextPresent(): Ist der Text irgendwo auf der Seite vorhanden
  • assertElementPresent(): Ist ein bestimmtes DOM-Element vorhanden
  • die Kombination aus beiden: Beinhaltet ein bestimmtes Element einen bestimmten Text?

Es git eine Vielzahl weiterer Asserts, die jedoch die beiden grundlegenden Methoden auf bestimmte Elemente und Verfahren anwenden.

Beispiel

In google_selenium.html befindet sich ein einfaches Selenese-Beispiel.

  • Rufe Google auf
  • Gebe ‚Selenium‘ ein
  • Warte und Checke den Text

Wandelt man diesen Test in PHP sieht er ungefähr wie in der Methode testGoogleSelenium() aus:

runSelenese(BASE_PATH."/selenium/google_selenium.html");
}

/**
* \brief checks google for Selenium Website
*/
public function testGoogleSelenium()
{
$this->open("/");
$this->type("q", "Selenium");
$this->click("btnG");
$this->isTextPresent("Selenium web application testing system");
}
}
?>

Fertig.
Startet man den Test wie gewöhnlich per PHPUnit verbindet er sich zum Selenium RC, initialisiert die Session, Öffnet den gewünschten Browser, führt die Schritte aus und überprüft ob die folgenden Seite den Text „Selenium web application testing system“ beinhaltet.

4) Benefits

Eine grafische Aufbereitung der Test-Ergebnisse und Code-Coverages ist für jedes QA-Team lebhafter als die Analyse von Text. Selenium bringt mit dem recht jungen Bromine [url: http://seleniumhq.org/projects/bromine/] zwar eine Verwaltung von Testsuiten und RC-Servern mit, das Abspielen der Selenium-Test in PHPUnit bringt jedoch per se die Möglichkeit die Ergebnisse u.A. mittels PHPUnderControl oder Phing darzustellen.

Ein Mangel von Selenium als standalone Tool ist das fehlende TearDown. PHPUnit bringt an dieser Stelle Abhilfe und ermöglicht ein sauberes Recover auf den Staging-Machinen. Für PHP-Entwickler ggf. uninteressant, bringt Selenium für diverse Programmiersprachen Treiber und Wrapper mit. Der RC kann somit in Java, Ruby, Python, Perl … gesteuert werden.

Schwachstellen

Ein immer wieder ärgerlicher Punkt ist die Interpretation des X-Path’s. Fünf verschiedene Tools liefern in der Regeln fünf verschiede Pfade. An dieser Stelle muss man versuchen mit Ids und Klassen von Elementen zu arbeiten. In dieser Hinsicht ist das Selenium-Konzept im Allgemeinen sehr anfällig für bereits sehr kleine Layout-Änderungen, die Zur Folge haben, dass sich die Position von Elementen im DOM verändert. Auch ist die Anfälligkeit gegenüber Text-Änderungen auf einer Webseite zu nervig.

Die Selenium IDE benutzt Befehle, die der PHP-Client nicht kennt. Das sowieso nicht alle Funktionen in PHPUnit_Extensions_SeleniumTestCase deklariert werden, sondern fehlende mittels __call() an den Selenium RC delegiert werden führt uns direkt zum nächsten Abschnitt.

Best Practices

Da wir u.A. nicht wissen wie lange Selenium ein gutes Tool zum Testen von Oberflächen im Web bleibt, ist es sinnvoll PHPUnit_Extensions_SeleniumTestCase nicht direkt zu benutzen. Es hat sich gezeigt das es hin und wieder Probleme mit dem URL-Encoding zum Selenium RC und der relativen / absoluten Angabe des X-Path gibt. Besser ist es sich seinen eigenen Wrapper vor PHPUnit_Extensions_SeleniumTestCase zu basteln (im Beispiel ist das class GUITest). Auf diese Weise kann man später den Interpreter der Tests einfach austauschen und die teils fehlenden Methoden in PHPUnit_Extensions_SeleniumTestCase definieren. Das ist wesentlich sauberer. Ein Ausschnitt der fehlenden Methoden findet man im Anhang.

Da ein fehlgeschlagener Test keinen Nutzen bringt wenn man nicht nach der Ursache sucht, erweisen sich die beiden Helfer captureScreenshot() und storeHtmlSource() als sehr hilfreich. Die Methoden kann man benutzen um im Fehlerfall Screenshots oder Abbilder des DOM / der HTML Source zu speichern.

Fazit

Die Integration von Selenium in PHPUnit ist relativ einfach, sogleich es sicher auch andere Wege als den beschrieben gibt. Wartet man seine Suiten regelmäßig erhält man mit ziemlich geringen bis nicht vorhanden Kosten – die Tools sind frei, die Integration ist nicht aufwändig – einen hohen Nutzen der durch die Tatsache gestärkt wird, dass man keinen Entwickler benötigt um einen Test zu erstellen.

Anhang

Hier eine Liste mit Funktionen die die IDE benutzt, PHPUnit_Extensions_SeleniumTestCase jedoch nicht kennt. Die Liste ist sicher nicht vollständig.

verifyElementPresent( string $locator)
verifyTextPresent( string $text)
verifyTextNotPresent( string $text)
verifyTitle( string $title)
verifyText( string $argument1 = null, string $argument2 = null)
waitForText( string $argument1 = null, string $argument2 = null)
waitForEditable( string $argument1)
waitForElementPresent( string $locator)
setTimeout( integer $milliseconds)

Manche Funktionen werden zwar delegiert, haben in der Praxis jedoch nicht funktioniert und müssen überschrieben werden:

storeText($argument1 = null, $argument2 = null)
storeValue($argument1 = null, $argument2 = null)

Folgende Browser werden nativ durch Selenium RC unterstützt. Vorraussetzung ist natürlich eine Installation auf dem System auf dem der RC läuft.

*iexplore
*konqueror
*firefox
*mock
*pifirefox
*piiexplore
*chrome
*safari
*opera
*iehta
*custom

Über den Autor

Mathias Methner

Kommentare

5 Comments

  1. Sehr guter Artikel. Anscheinend setzen sich vernünftige Testframeworks auch bei Webseites immer mehr durch.

    Eine kleine Ergänzung zur XPath Problematik habe ich noch: CSS ID’s!
    Nicht nur für das Testen, aber gerade auch hierbei, kann die Verwendung von CSS ID’s ein Ausweg aus der XPath Problematik sein.
    Wann immer man per JavaScript auf eine Element zugreifen muss/will erzielt man mit ID’s die wesentlich performantere und stabilere Lösung.
    Am besten gleich beim strukturellen Entwurf der Site einen stabilen strukturellen CSS Wireframe inkl. ID’s definieren. Dann kann man das auch mit Selenium sehr schön addressieren und testgetrieben entwickeln.

    Reply
  2. Pingback: Sperrobjekt Weblog
  3. Sehr interessant, vielen Dank!
    Ich arbeite mich auch gerade in Selenium ein, und während ich jetzt deinen Artikel gelesen habe, ist mir eine Frage gekommen:
    Wozu brauch man überhaupt RC und PHPUnit, wenn man die Tests doch alle direkt im IDE schreiben/aufnehmen und ausführen kann?

    Reply

Leave a Comment.

Link erfolgreich vorgeschlagen.

Vielen Dank, dass du einen Link vorgeschlagen hast. Wir werden ihn sobald wie möglich prüfen. Schließen