Gute Dinge kommen in kleinen Containern.

Parallele Akzeptanztests: Selenium-Grid mit Docker erstellen
Keine Kommentare

Da ein Selenium-Grid aus mehreren Nodes besteht, ist Docker dafür der ideale Partner. Vorbei sind die Zeiten, in denen man Selenium-Tests gestartet hat, Mittagessen gegangen ist, um nach einer Stunde festzustellen, dass es doch einen Fehler gab. In diesem Artikel erstellen wir ein skalierbares Selenium-Grid in wenigen Minuten, mit nur einer Handvoll Konfigurationszeilen.

Mithilfe von Selenium wird die Interaktion mit der Webanwendung automatisiert; Akzeptanz- oder Funktionstests werden dabei in einem Browser ausgeführt. Gerade beim Ausfüllen von Formularen wird auf diesem Weg viel Tipparbeit und Zeit eingespart. Die Integration von Selenium-Tests in PHPUnit ist sehr einfach und kann somit in die vorhandene Teststruktur integriert werden.

Während Unit-Tests jedoch nur einen bestimmten Teil der Applikation testen – in den meisten Fällen ist das eine einzelne Klasse – dienen Funktionstests hingegen der Verifikation. Wurde ein korrekt funktionierendes Produkt entwickelt und erfüllt die Software die Businessanforderungen? Bei dieser Art von Tests gibt es Testfälle, die alle möglichen Szenarien abdecken; auch solche, die nicht wirklich in der realen Welt existieren. Ziel ist es, eine möglichst hohe Testabdeckung zu erhalten.

Akzeptanztests: die Software aus Kundensicht testen

Akzeptanztests hingegen stellen sicher, dass die Software die Spezifikation des Kunden oder des Product Owners erfüllt und die erwartete Funktionalität liefert. Akzeptanztests sind aus Kundensicht geschrieben und behandeln typische Szenarien, in der die Software benutzt wird. Es ist wichtig, sich auch Gedanken über die Funktionalität der Software aus Endkundensicht zu machen.

Das Selenium-Grid dient dazu, Akzeptanz- oder Funktionstests parallel auszuführen und die Ausführungszeit damit deutlich zu reduzieren. Der große Vorteil von Selenium besteht darin, Tests in einem echten Browser inklusive JavaScript und Betriebssystem auszuführen. Dadurch wird echtes Verhalten, einschließlich Browserbugs, getestet.

Mink als Abstraktion

Die Mink Library ist aus der Behat-Community bekannt, um damit Behaviour-driven Development (BDD) umzusetzen. Da es sich um eine eigenständige PHP-Library handelt, kann es sehr leicht in PHPUnit benutzt werden. Mink bietet allerdings eine höhere Flexibilität gegenüber der direkten Abstraktion von Selenium in PHPUnit.

Durch das einheitliche API von Mink für verschiedene Browser-Libraries können die Tests auch mit Headless-Browsern wie Goutte durchgeführt werden, ohne den Test anpassen zu müssen. Tests mit Headless-Browsern sind deutlich schneller als Tests mit Selenium. Die Auswahl des Treibers ist abhängig von der zu testenden Funktionalität; der Goutte-Driver unterstützt z. B. kein JavaScript, und unter Selenium werden Request und Response Header nicht ausgewertet.

Insgesamt stehen sechs Treiber zur Verfügung: Neben Goutte sind das Selenium 2, BrowserKit, Zombie, Selenium und Sahi. In diesem Beispiel wird Mink mit dem Selenium-2-Treiber verwendet. Wer gerne ein Full-Stack-Testing-Framework nutzen möchte, sollte sich Codeception anschauen. Es nutzt PHPUnit und bietet ebenfalls eine Abstraktionsschicht.

Docker und Docker Compose

Mit Docker und Docker Compose ist es wirklich sehr einfach, innerhalb weniger Minuten ein Selenium-Grid aufzusetzen. Während die Docker-Engine dafür sorgt, dass die Container unabhängig von Entwicklungs- oder Liveumgebung auf jedem System laufen, kümmert sich Docker Compose um Multicontainerapplikationen. In einer einzigen Datei werden dafür alle Container und Abhängigkeiten definiert. Docker Compose sorgt nicht nur dafür, dass die Container in der richtigen Reihenfolge starten und die Kommunikation untereinander möglich ist, sondern ist auch für die Skalierung der Container zuständig. Auf Docker Hub stehen neben dem Selenium Hub auch Selenium Nodes für Firefox und Chrome zur Verfügung. Man kann natürlich auch Docker-Images mit der genau zu testenden Version eines Browsers und Betriebssystems erstellen. Windows und Mac-Computer können sich ebenfalls am Selenium Hub registrieren, um mit dem Internet Explorer bzw. Safari-Browser zu testen.

Selenium-Grid aufsetzen

In der Datei docker-compose.yml wird ein Selenium-Grid bestehend aus dem Selenium Hub, einem Firefox und einem Google Chrome Node aufgesetzt. Bei den Selenium Nodes handelt es sich jeweils um ein Linux mit VNC-Server. Dadurch können die Tests über einen VNC-Viewer beobachtet werden. Die Varianten ohne debug-Suffix enthalten keinen VNC-Server, doch dazu später mehr. Listing 1 zeigt die Docker-Compose-Konfiguration für das eben beschriebene Set-up.

hub:
  image: selenium/hub:2.53.0
  container_name: hub
  ports:
    - "4444:4444"

firefox:
  image: selenium/node-firefox-debug:2.53.0
  ports:
    - "5900"
  links:
    - hub:hub

chrome:
  image: selenium/node-chrome-debug:2.53.0
  ports:
    - "5900"
  links:
    - hub:hub

Für diejenigen, die noch nicht so vertraut mit Docker Compose sind, erfolgt nun eine kurze Erläuterung der Docker-Compose-Konfiguration in Version 1. Es ist sehr leicht, diese auf die Version 2 der Docker-Compose-Konfiguration umzustellen. Dadurch erhält man weitere Vorteile, darauf einzugehen würde die Länge des Artikels jedoch deutlich übersteigen. Hier empfiehlt es sich, einen Blick in die Docker-Compose-Dokumentation zu werfen.

Das Docker Image

Auf der ersten Ebene steht immer der Name des Containers. Wie der Name schon vermuten lässt, gibt die image-Definition an, welches Docker-Image für den Container genutzt wird. Das Image kann dabei lokal auf dem Rechner oder auf einem Remote-Server liegen. Wenn es lokal nicht gefunden wird, wird automatisch versucht, es von Docker Hub herunterzuladen. Anhand eines Doppelpunkts wird direkt eine Version des Images angegeben. Wird sie weggelassen, wird automatisch das Tag latest verwendet. Die Definition container_name wird hier nur beim Selenium Hub verwendet, um die Verlinkung mit dem PHP-Docker-Container zu vereinfachen. Dadurch kann zwar nicht skaliert werden, was hier aber auch nicht notwendig ist. Der ports-Eintrag definiert, welcher Port vom Host auf den Container zeigen soll. Docker vergibt beim Skalieren automatisch einen neuen Port auf dem Host und zeigt ihn automatisch auf den im Docker-Image definierten Port. Dadurch können weitere Selenium Nodes erzeugt werden, ohne den Port anpassen zu müssen. Der Selenium-Hub bekommt den festen Port 4444 auf dem Host zugewiesen, um später Zugriff auf die Konsole zu erhalten. Über links wird eine Verbindung zu anderen Docker-Containern hergestellt, da sich andernfalls z. B. der Firefox Node nicht mit dem Selenium Hub verbinden kann. Der Vorteil hierbei ist, dass Docker einen etc/hosts-Eintrag beim Container, der die links-Anweisung nutzt, erstellt. Somit ist der Selenium Hub für die Selenium Nodes über den Hostname hub erreichbar.

Es sei anzumerken, dass dies mit der Version 2 der Docker-Compose-Konfiguration nicht mehr notwendig ist, da dort automatisch alle Container über den Namen miteinander kommunizieren können. Das wird mit dem integrierten DNS-Server für benutzerdefinierte Netzwerke ermöglicht.

Mit dem Befehl docker-compose up -d wird das Selenium-Grid im Hintergrund gestartet. Nachdem die Docker-Images heruntergeladen wurden und die Container gestartet sind, steht die Selenium-Grid-Konsole unter http://localhost:4444/grid/console zur Verfügung. In Abbildung 1 wartet ein Firefox Node und ein Google Chrome Node auf Arbeit.

Abb. 1: Selenium-Grid-Konsole mit Firefox und Chrome Node

Um Selenium Nodes für Internet Explorer und Safari zu registrieren, kann eine virtuelle Maschine eingerichtet werden, die sich automatisch beim Start am Selenium Hub registriert. Dazu wird die jeweilige Browser-Extension und eine Standalone-Selenium-Server-.jar-Datei benötigt. Eine einfache Registrierung für Safari wird mit dem Befehl

java -jar selenium-server-standalone-2.53.0.jar -role node http://[IP des Selenium Hubs]:4444/grid/register -browser browserName=safari

durchgeführt. Es stehen noch weitere Optionen zur Verfügung, die die Browsereigenschaften beschreiben. Es empfiehlt sich auch hier, einen Blick in die Selenium-Dokumentation zu werfen.

Mit der Konfiguration aus Listing 1 ist es möglich, einen Test mit einem Firefox- und Google-Chrome-Browser parallel auszuführen. Weitere Selenium Nodes werden einfach anhand des Namens und der Anzahl der zu startenden Nodes dem Selenium Hub hinzugefügt. Der Befehl docker-compose scale firefox=5 startet beispielsweise fünf Firefox Nodes, die nun auch in der Selenium-Grid-Konsole zu sehen sein sollten. Durch den Befehl docker-compose scale firefox=1 kann das Ganze rückgängig gemacht werden.

Mit einem VNC-Viewer wie xvnc4viewer für Ubuntu kann man den Test beobachten. Mit Selenium ist es aber auch möglich, Screenshots vom Test anzufertigen, was sich vor allem im Fehlerfall als nützlich erweisen kann.

International PHP Conference 2018

Getting Started with PHPUnit

by Sebastian Bergmann (thePHP.cc)

Squash bugs with static analysis

by Dave Liddament (Lamp Bristol)

API Conference 2018

API Management – was braucht man um erfolgreich zu sein?

mit Andre Karalus und Carsten Sensler (ArtOfArc)

Web APIs mit Node.js entwickeln

mit Sebastian Springer (MaibornWolff GmbH)

Um einen Test beobachten zu können, wird zuerst der verwendete Port mit docker-compose port firefox 5900 für Firefox und mit docker-compose port chrome 5900 für Chrome bestimmt. Nun kann man sich mit dem VNC-Viewer und der IP bzw. dem Port über den Befehl vncviewer 0.0.0.0:49338 verbinden. Wie das aussieht, wird in Abbildung 2 dargestellt. Das Standardpasswort für den Aufbau der Verbindung ist „secret“. Wer ein eigenes Passwort nutzen möchte, muss sich ein eigenes Docker-Image mit der entsprechenden Konfiguration erstellen.

Abb. 2: Tests beobachten mit einem VNC-Viewer

Tests parallel ausführen

PHPUnit startet die Tests nacheinander. Deswegen wird eine zusätzliche Anwendung benötigt, um Tests parallel auszuführen. Für PHPUnit gibt es das Plug-in ParaTest, das sich als Composer Dependency hinzufügen lässt; die Tests werden dann darüber gestartet. Der Vorteil von ParaTest ist, dass die Tests nicht nur auf Testdateiebene, sondern auch auf Funktionsebene parallelisiert werden können. Da in der Docker-Compose-Konfiguration kein PHP-Docker-Container enthalten ist, nutzen wir einfach ein PHP-Image von Docker Hub. Der dafür benötigte Befehl lautet:

 docker run --rm -it --volume $(pwd):/app --link hub:hub prooph/php:7.0-cli vendor/bin/paratest

Dieser Befehl wird gut in der Docker-Run-Dokumentation erläutert. Interessant ist hier die link-Anweisung, mit der Selenium Hub dem Container unter dem Hostname hub zur Verfügung gestellt wird.

Eine andere Variante unter Linux ist die Nutzung von parallel [12]. Die Anwendung ist etwas aufwendiger, dafür wird PHPUnit jedoch nativ genutzt, und jede Testdatei kann in einem eigenen Docker-Container ausgeführt werden. Zunächst müssen die einzelnen Testdateien ermittelt und an parallel übergeben werden. Daraufhin wird ein PHP-Docker-Container gestartet und die Datei an den PHPUnit-Aufruf weitergegeben. Dieser etwas kryptische anmutende Befehl lautet:

find test/ -name "*Test.php" | parallel --gnu -P 0 'docker run --rm --volume $(pwd):/app --link hub:hub prooph/php:7.0-cli vendor/bin/phpunit {};echo "Runned {} tests";'

Es lassen sich so auch beliebige Tests parallelisieren.

Testszenario

Nachdem nun die Grundlagen geklärt wurden, wird noch ein Testfall benötigt. Ein mögliches Testszenario könnte sein, sicherzustellen, dass die Suche auf einer Webseite funktioniert. Der User befindet sich auf der Wikipedia-Startseite und gibt den Begriff „PHP“ in das Suchfeld ein. Wenn auf den Button SEARCH geklickt wird, soll auf der Webseite der Text „PHP: Hypertext Preprocessor“ enthalten sein. Listing 2 zeigt den Aufbau der Testklasse.

class WikipediaTest extends \PHPUnit_Framework_TestCase
{
  public function testSearch()
  {
    $baseUrl = 'https://en.wikipedia.org/wiki/Main_Page';

    $driver = new \Behat\Mink\Driver\Selenium2Driver(
      'firefox',
      ['selenium-version' => '2.53.0'],
      'http://hub:4444/wd/hub'
    );
    $session = new \Behat\Mink\Session($driver);
    $session->start();
    $session->visit($baseUrl);

    $page = $session->getPage();
    $page->fillField('search', 'PHP');
    $page->pressButton('searchButton');

    $content = $page->getContent();

    $this->assertContains('PHP: Hypertext Preprocessor', $content);
    $this->assertContains('Rasmus Lerdorf', $content);
  }
}

Es handelt sich hierbei um einen sehr einfachen Test. Tests für einen Onlineshop – um z. B. den Bestellprozess zu testen – fallen deutlich komplexer aus. Gerade bei aufwendigen Formularen sollte Wert auf Wiederverwendbarkeit der Bestandteile eines Testaufbaus gelegt werden.

Es empfiehlt sich, zu testende Elemente auf einer Webseite in eigene Objekte auszulagern, die dann in dem entsprechenden Test genutzt werden können. Als Beispiel dient die Log-in-Funktionalität, die meistens auf mehreren Seiten zur Verfügung steht. Das Log-in-Objekt hätte dann die Aufgabe, das Log-in-Formular mit Benutzername und Passwort auszufüllen und abzuschicken. Befindet man sich auf der Startseite, wird der Kunde nach einem Log-in in den Kundenbereich weitergeleitet. Beim Check-out-Prozess sieht dies allerdings anders aus: Hier soll der Kunde nach dem erfolgreichen Log-in auf der Check-out-Seite bleiben und nicht in den Kundenbereich weitergeleitet werden. Die Funktionalität des Log-in-Objekts wird für den Startseiten- und Check-out-Test verwendet. Sollte sich etwas am Log-in ändern, muss nur das Log-in-Objekt und nicht alle Tests angepasst werden. Dasselbe kann man übrigens auch für Produktslider, Formulare oder andere Widgets auf der Seite umsetzen. Auf GitHub steht eine voll funktionsfähige und leicht erweiterte Version, inklusive Kurzanleitung, für das parallele Ausführen von Tests mit dem Docker-Selenium-Grid bereit.

Fazit

Wer schon einmal ein Selenium-Grid aufgesetzt hat, weiß, dass dies im besten Fall mehrere Stunden dauert. Man muss die entsprechenden Abhängigkeiten wie Java auf dem System installieren und entsprechende Konfigurationen vornehmen. Mit Docker ist es auch für Nichtsystemadministratoren möglich, dies umzusetzen; und das in Minuten, inklusive Skalierung.

Selenium-Tests verursachen eine hohe Last auf dem Selenium-Grid-Rechner. Die Anzahl der Selenium Nodes und die parallele Ausführung von Tests sollten daher vorsichtig ausgelotet werden; ansonsten kann es sein, dass die Tests fehlschlagen. Mit Docker lassen sich nicht nur Tests, sondern auch andere Dinge auf leichte Art und Weise parallelisieren.

PHP Magazin

Entwickler MagazinDieser Artikel ist im PHP Magazin erschienen. Das PHP Magazin deckt ein breites Spektrum an Themen ab, die für die erfolgreiche Webentwicklung unerlässlich sind.

Natürlich können Sie das PHP Magazin über den entwickler.kiosk auch digital im Browser oder auf Ihren Android- und iOS-Devices lesen. In unserem Shop ist das Entwickler Magazin ferner im Abonnement oder als Einzelheft erhältlich.

Unsere Redaktion empfiehlt:

Relevante Beiträge

Hinterlasse einen Kommentar

Hinterlasse den ersten Kommentar!

avatar
400
  Subscribe  
Benachrichtige mich zu:
X
- Gib Deinen Standort ein -
- or -