Funktionale- und Integrationstests mit Selenium

vom 7. December 2008

Webseiten testen macht kein Spaß. Vor allem dann nicht, wenn man ein zweiseitiges Formular schon zum 25sten Mal ausgefüllt hat und immer noch ein Bug drin ist. Da ich gerne Dinge so weit es geht automatisiere, habe ich mich vor etwas längerer Zeit (diesen Blog-Post wollte ich schon vor ca. einem Jahr erstellen :) nach Test-Frameworks für Webseiten umgesehen.

Es gibt unzählige, allen voran SimpleTest und PHPUnit. Allerdings haben all diese Frameworks ein Problem: Die Testfälle sind dermaßen aufwendig zu erstellen, dass es bei größeren und komplexeren Seiten einfach zu zeitaufwändig und langweilig ist. Ein Beispiel hierfür aus dem Tutorial von SimpleTest:

function testWeAreTopOfGoogle() {
    $this->get('http://google.com/');
    $this->setField('q', 'simpletest');
    $this->click("I'm Feeling Lucky");
    $this->assertTitle('SimpleTest - Unit Testing for PHP');
}

Um diesen Vorgang zu beschleunigen, habe ich mich weiter auf die Suche gemacht und bin dann eines Tages auf Selenium gestoßen. Hiermit lassen sich die Tests viel einfacher erstellen.

Selenium besteht aus mehreren Komponenten. Zum Einen hat man zum Erstellen der Tests ein Firefox-Plugin, mit dem Tests einfach aufgezeichnet werden können:

Selenium-Tests im Browser aufzeichnen

Man drückt auf "Record" und beginnt mit seinem Testcase. Asserts lassen sich über das Kontextmenü hinzufügen. Will man beispielsweise sicherstellen, dass auf der aktuellen Seite eine Wortkombination erschienen ist, markiert man diese und wählt im Kontextmenü "assertTextPresent". Auch Formulareingaben, Seitensprünge usw. werden aufgezeichnet. Will man kompliziertere Testfälle aufsetzen, kann man die aufgenommenen Schritte nachträglich bearbeiten und erweitern. Reichen die Befehle von Selenium nicht aus, kann immer noch auf JavaScript ausgewichen werden.

Hat man seinen Testfall fertig, kann man diesen auf unterschiedliche Weise abspeichern. Zum Einen kann man den Testfall als simple HTML-Tabelle abspeichern, das ist das Standardformat von Selenium. Eine weitere Möglichkeit ist das Konvertieren in einer der vielen Programmiersprachen (Java, PHP, Ruby, Python, ...), so dass man am Ende ein ähnliches Ergebnis wie beim SimpleTest hat, nur mit viel weniger Tipparbeit. Dieses Script kann dann einfach ausgeführt werden.

Ein weiteres Modul von Selenium ist die Umgebung, die die aufgenommenen Testfälle starten kann. Hier gibt es wiederum mehrere Möglichkeiten. Die Serverkomponente ist für den "Hausgebrauch" relativ umbedeutend, und spielt erst eine entscheidende Rolle, wenn wirklich bei jedem Code-commit alle Testfälle ausgeführt werden sollen. Die einfachere Variante ist der Test-Runner, der komplett in JavaScript geschrieben ist, und somit direkt im Browser läuft. Das hat den Vorteil, dass man nicht an einen bestimmten Browser gebunden ist und somit seine Testfälle auf jedem beliebigen Browser testen kann, der einigermaßen neues JavaScript unterstützt.

Der Browser ist die Testumgebung

Ich habe mir, um den Testvorgang weitgehend zu automatisieren, eine Subdomain angelegt, die - wenn ich sie aufrufe - automatisch alle Tests durchlaufen werden. So kann ich in regelmäßigen Abständen und bei größeren Code-Änderungen immer prüfen, ob noch alles tut so wie es soll.

SetUp und TearDown?

Ein Problem gibt es dennoch mit Selenium: Wenn man beispielsweise ein Formular auf ungültige Eingaben testet, werden diese Testdaten evtl. in die Datenbank geschrieben und bleiben auch nach Beendigung des Testlaufs bestehen. Dies ist natürlich nicht gewollt. Eine SetUp- und eine TearDown-Methode muss her. Leider gibt es so etwas in Selenium nicht, da das Framework logischerweise die eigene Datenbank nicht kennt und auch keinen Zugriff darauf hat.

Um dieses Problem zu umgehen, habe ich mir ein kleines PHP-Skript geschrieben, dass diese Funktionalität "nachrüstet". Abgespeckt sieht es ungefähr so aus:

define('SECRET', '12345xyz');

if (isset($_GET['action'])) {
    $key = isset($_GET['secret']) ? $_GET['secret'] : '';
    if ($key == SECRET) dispatch($_GET['action'], $_GET);
    else die('Wrong secret, sorry.');
}

function dispatch($action, $params) {
    $preparator = new SeleniumPreparator();
    if (method_exists($preparator, $action)) {
        PicoraActiveRecord::connect(CONNECTION_STRING);
        output($preparator->$action($params));
    } else die("No method $action found.");
}
class SeleniumPreparator {
    public function setup() {
        // Make temp-Entry visible
        $entry = $this->getTestentry();
        $entry->display = 1;
        $entry->allow_comments = 1;
        $entry->save();
        return true;
    }
    
    public function teardown() {
        $entry = $this->getTestentry();
        $entry->display = 0;
        $entry->allow_comments = 0;
        
        // Clear all comments and annotations
        $comments = Comment::findAllByField('Comment', 'entry_id', $entry->id);
        foreach ($comments as $c) {
            $c->delete();
        }   
        $entry->annotations = '[]';
        $entry->save(); 
        return true;
    }
    
    public function activateAllComments() {
        $entryID = $this->getTestentry()->id;
        $comments = Comment::findAllByField('Comment', 'entry_id', $entryID);
        foreach ($comments as $c) {
            $c->approved = 1;
            $c->save();
        }
        return true;
    }
    
    private function getTestentry() {
        return Entry::findByField('Entry', 'url_name', 'selenium-testentry');
    }
}

Aufgerufen wird das Skript mit test_setup.php?secret=12345xyz&action=setup, um beispielsweise die Setup-Methode aufzurufen, die den Testeintrag sichtbar macht. Diesen Aufruf kann man natürlich auch mit Selenium machen. Mit dieser Technik lassen sich dann auch administrative Aufgaben wie das Aktivieren von Kommentaren durchführen, ohne manuell einzugreifen.

Ich hoffe, dass durch Selenium mehr Web-Developer ihre Webseite testen, denn das hat das Web dringend nötig.

delicious bookmark del.icio.us, Webseiten testen macht kein Spaß. Vor allem dann nicht, wenn man ein zweiseitiges Formular schon zum 25sten Mal ausgefüllt hat und immer noch ein Bug drin ist. Da ich gerne Dinge so weit es geht automatisiere, habe ich mich vor etwas längerer Zeit (die


Kommentare


Wolfgang am 8. December 2008
Muss ich mir mal genauer anschauen, danke für den Tipp!

Dir gehts mittlerweile hoffentlich wieder etwas besser...

Ruben am 12. December 2008
Wie genial ist das denn! Danke, danke ... danke! :D

H@nn€$ am 20. December 2008
Tada. Da kann ich gleich mal Werbung machen. Ich bin als Werksstudent bei einer Firma mit dem Namen exept tätig. Exept hat seit einiger Zeit eine web Edition seiner Testautomatisierungslösung expecco auf dem Markt. expecco webEdition basiert auf Selenium, erweitert die Funktionalität und Bedienbarkeit allerdings um Längen. Auf der Homepage kann man sich eine 30 Tage Testversion herunterladen. Soweit ich weiß, ist auch schon seit längerem eine kostenlose Version für Schüler und Studenten geplant. Auf der Seite gibt es auch ein kurzes Einführungsvideo, sowie ein Supportforum. Um die Werbung komplett zu machen weise ich natürlich auch noch auf das Gewinnspiel hin, bei dem jeder, der Feedback zu expecco webEdition abgibt, an einer Verlosung von ein paar iPods teilnimmt. Happy testing :) www.exept.de

Huggy am 30. December 2008
Sowas hab ich auch shcon länger gesucht - auch wenn es ein bisschen kompliziert ist, da blick ich noch durch .
DANKE!

Orna am 9. September 2010
Hallo,
ich suche ein Framework für Web Applikation zum Testen.
Kennst du noch welche außer Selenium?

Danke Orna

Aaron am 9. September 2010
@Orna: Was willst du denn Testen? In Rails ist beispielsweise ein komplettes Framework für funktionale Tests drin. Mit dem Ruby Gem "nokogiri" kannst du auch einiges anstellen.

Kommentar schreiben