Unter Unit-Testing versteht man das Testen von Methoden und Modulen, während man unter Functional-Testing die Überprüfung der Anwendung als Ganzes oder Teilen davon versteht.
Als grober Anhaltspunkt für eine Unterscheidung wird häufig im Zusammenhang mit agilen Entwicklungsmethoden die Test-Pyramide verwendet (s. Bild links).
Die Basis bilden dort die Unit-Tests (Methoden und Module). Auf diesen bauen dann automatisierte Tests von Komponenten und APIs (Services) auf. Die Spitze bilden die Tests des User-Interface (UI bzw. GUI), teils automatisiert, teils auf Basis weitergehender Designs bis hin zum Einbezug realer Testpersonen.
Den Bereich Service-Testen bis hin zu den automatisierten UI-Tests bezeichne ich hier als funktionales Testen und hoffe, dass ich damit mit den üblichen Definitionen weitgehend übereinstimme (vgl. Abb. 1).
Abb. 1: Bereiche und Aufgaben des Functional-Testing innerhalb der Test-Pyramide
Für die Javascript-Frontend-Entwicklung kristallisieren sich für OpenSource-Entwickler zur Zeit zwei technische Linien heraus, die als echte Alternativen betrachtet werden können:
- das relativ integrierte und alle wesentlichen Anforderungen erfüllende Big-Ship Selenium und
- eine Serie von zueinander kompatiblen Tools meist entwickelt auf Basis der Javascript-V8-Engine von Google, welche z.B. mittels Grunt oder ähnlichen Werkzeugen sehr individuell in den eigenen Workflow integriert werden können.
Wir wollen uns in diesem Artikel mit der zweiten Entwicklungslinie berfassen, da diese Tools
- den Charme haben, oft reine Javascript-Lösungen zu sein und das ist offensichtlich die Zukunft,
- als Lightweight-Alternativen zu Selenium gelten und
- spezifischer integrierbar sind in individuelle Entwickler-Workflows, was der Philosophie entspricht, die hier im Blog verfolgt wird, u.a. auch mit unserem 3-Purpose-Grid-Projekt.
In Abb. 2 habe ich durch die Darstellung versucht, einige der interessanten Tools ihren Einsatzbereichen innerhalb der Test-Pyramide bzw. des funktionalen Testens (vgl. mit Abb. 1) zuzuordnen (s. Abb. 2):
Abb.2: Testing-Tools mit ihrem Einsatzbereich innerhalb der Test-Pyramide
Hier ist vor allem das zum Zeitpunkt der Erstellung dieses Artikels am weitesten entwickelte Projekt PhantomCSS hervorzuheben, das CasperJS, PhantomJS und ResembleJS zu einer Gesamtlösung integriert, die sogar das Testen von CSS-Transition-Effekten und ähnlichem im Rahmen von visuellen Regression-Test ermöglicht.
Als Browser-Zielsysteme werden von PhantomCSS die WebKit-Browser (Chrome, Safari) abgedeckt und bei einer zusätzlichen Installation von SlimerJS auch Gecko-Browser (Firefox). Der IE leider nicht.
Für Regression-Tests für den IE gibt es aber für das Headless-Testen das Tool TrifleJS, das sich für die wichtigsten Aufgaben genauso pogrammieren lässt, wie PhantomJS und SlimerJS, so dass damit alle wesentlichen Browser abgedeckt sind. Allerdings wird man Tests, die speziell mit PhantomCSS entwickelt wurden, nicht auf den IE anwenden können.
Schön wäre, wenn PhantomCSS auch noch TrifleJS integrieren würde, dann hätte man eine runde Lösung. Aber der IE will wie immer eine Extra-Wurst.
In diesem Artikel beschränken wir uns darauf, uns einige dieser Tools für das funktionale Testing auf Windows verfügbar zu machen, was bei dem einen oder anderen Tool nicht unbedingt trivial ist. In weiteren Artikeln können wir dann auf diesen Vorarbeiten aufbauen.
Jasmine plus Mocha für das Client/Server-Testing
Wer Kriterien für die Auswahl der drei Unit-Testing-Tools Jasmine, Mocha und QUnit in Abb. 2 benötigt, kann sich in diesem Artikel informieren. QUnit scheint noch wenig ausgereift zu sein, sollte aber für mobile Projekte in Augenschein genommen werden.
Jasmine ist wohl das bestentwickelste Unit-Testing-Tool, asynchrone Prozesse sind aber sehr hakelig damit.
Mocha ist nicht ganz so komfortabel, dafür sollen asynchrone Prozesse ein Kinderspiel sein (s. Artikel).
Relevant wird dieser Umstand für das Testen z.B. von Client/Server-NodeJS-Projekten, womit wir in den Bereich des funktionalen Testens hineinkommen. So schreibt Patrick Simson in einem interessanten Artikel:
In the end, Mocha was good at server-side tests while Jasmine was good at client-side tests, so we thought, "why not use both?"
Der Artikel beschreibt ein einsprechende Test-Szenario für eine simple NodeJS-Client/Server-Anwendung unter Einsatz verschiedener Node-Module wie Grunt, Watch, PhantomJS@1.8.2-0, Jasmine, Mocha-cli, JSHint, Expect.js.
Jasmine
Die Installation von Jasmine als behavior-driven development framework habe ich schon im letzten Artikel im Zusammenhang mit dem Unit-Testing beschrieben.
Mocha
Mocha ist ebenfalls ein Tool für das Unit-Testing. Es gibt verschiedene Grunt-Implementierungen.
Im Zusammenhang mit dem oben genannten Client/Server-Problem verwendet Patrick Simson das Grunt-Modul Mocha-cli für die Server-Seite. Also nicht verwirren lassen durch die Namensgebung.
Installiert wird dieses Modul, wie jedes andere Grunt-Modul. Dies kennen wir schon aus unserer Grunt-Reihe (Stichwort Grunt in die Suche eingeben), weshalb ich hier nicht weiter auf die Installation eingehe.
Mocha zusätzlich zu Jasmine zu installieren ist demnach notwendig, wenn man NodeJS-Client/Server-Systeme entwickelt, was durchaus in einem der nächsten Blogbeiträge der Fall sein wird (NodeJS-Gui für Grunt-Tasks).
PhantomCSS für das Headless-Multi-Browser-Regression-Testing (ohne IE)
Wer sich PhantomCSS installiert, bekommt zusätzlich eine lokale Installation von
- CasperJS,
- PhantomJS und
- ResembleJS
mitgeliefert und zwar in zueinander passenden Versionen, was gar nicht so einfach ist, wenn man versucht, diese Systeme einzeln zu installieren.
Die Installation ist im Grund sehr einfach und das mitgelieferte Testbeispiel sehr instruktiv. Deshalb zunächst einmal nicht per npm installieren, denn dort fehlen die Demos.
Später für ein konkretes Projekt empfiehlt sich die Installation des Grunt-Moduls grunt-phantomcss. Da es dafür aber keinen npm-Namespace gibt, muss man es sich zu Fuß von GitHub herunterladen:
npm i --save-dev git://github.com/anselmh/grunt-phantomcss.git
Aber wir wollen ja zuerst einwenig mit der Demo spielen.
Nach dem Entpacken des Zip-Files von GitHub (also nicht per npm) in einen beliebigen Ordner gehe ich dort in das Root-Verzeichnis und führe über eine Windows-Console folgenden Befehl aus:
casperjs test demo/testsuite.js
casperjs ist ein bat-File, welches mir die Umgebungsvariablen so einrichtet, dass ich auch ohne PATH-Einträge in meine Windows-Umgebung Zugriff auf die drei Tools habe. Ein vorhergehendes Einrichten ist also nicht erforderlich.
Das Demoprogram testsuite.js wird nur richtig ausgeführt, wenn ich casperjs das Argument test mit übergebe. Dies ist eine verwirrende Eigenheit von CasperJS, da eine Instanz des Tester-Objekts nur dann zur Verfügung steht.
Wer mit CasperJS arbeitet, sollte sich das merken. Wenn in Skripten Programmzeilen ausgeführt werden wie: casper.test.somewhat ..., muss dieses Skript mit dem Argument test in der Kommandozeile ausgeführt werden.
Das Skript testsuite.js erzeugt nun einen Ordner screenshots, in dem es beim ersten Start mehrere png-Grafiken erzeugt.
Erst beim zweiten Aufruf wird ein Regression-Test ausgeführt, der natürlich keinen Fehler anzeigt, da die zu testende Seite coffeemachine.html in der Demo keine Unterschiede zu den entsprechenden Grafiken im screenshots-Ordner aufweist.
Erst, wenn man einen Style, z.B. die Font-Größe in coffemachine.html ändert und dann den Test wieder ausführt, wird eine Abweichung in Prozent angezeigt sowie es werden Differenz-Grafiken erzeugt, die die Unterschiede mit einer rötlichen Farbe anzeigen.
Wer sich noch nicht mit Regression-Tests bisher befasst hat, dem sei auch die Seite ResembleJS empfohlen. Dort kann man durch Drag&Drop von eigenen Bildern Experimente anstellen.
Die Kombination PhantomJS, CasperJS und ResembleJS in der Inkarnation PhantomCSS deckt nun fast alle Aspekte des funktionalen Testens für WebKit- und Gecko-Browser (SlimerJS erforderlich) ab, aber nicht für den verflixten Internet-Explorer. Zudem ermöglicht PhantomCSS das Testen von CSS-Effekten, wie z.B. Transitions.
SlimerJS erweitert PhantomCSS für Gecko-Browser (Firefox)
Falls SlimerJS zusätzlich installiert ist, kann PhantomCSS genauso für Gecko-Browser (Firefox) eingesetzt werden, wie für WebKit-Broswer (Chrome, Safari).
Dazu ist nur ein zusätzlicher Parameter für den Aufruf des Skripts erforderlich:
casperjs test demo/testsuite.js --engine=slimerjs
Installation
SlimerJS installiert man am besten, indem man die Standalone-Version runterlädt und unter C:\ in einen Ordner slimerjs entzipped.
In dieser Art installierte Versionen benötigen immer einen entsprechenden Eintrag in die PATH-Umgebungsvariable (;C:\slimerjs).
Test1
Ich erzeuge ein kleines Testprogramm hello.js irgendwo mit einem Texteditor.
console.log("Hello Slimer!");
Dieses Programm führe ich aus mit:
slimerjs hello.js
Ich bekomme eine entsprechende Konsolenausgabe.
Test 2
Wenn ich jetzt den Regression-Test von PhantomCSS oben ausführe und den snapshot-Ordner nicht vorher gelöscht habe, werden die Unterschiede in der Anzeige der Demo-Seite coffeemachine.html für WebKit- und Gecko-Browser ausgegeben.
Test 3
An dieser Stelle stelle ich ein kleines Skript dar, das auch zum Testen von PhantomJS und TrifleJS verwendet werden kann, da alle drei Tools die gleiche API-Schnittstelle verwenden.
var page = require('webpage').create(); var tests= eckige Klammer auf ['http://example.grid.cms.de', 1200, 600], ['http://example.grid.cms.de', 900, 600], ['http://example.grid.cms.de', 700, 600], ['http://example.grid.cms.de', 500, 600], ['http://example.grid.cms.de', 300, 600], ['http://example.grid.cms.de', 250, 600] eckige Klammer zu; function handle_page(url, width, height){ page.viewportSize = { 'width': width, 'height': height }; page.open(url, function(status) { console.log('Url:' + url + " Status: " + status + ' Width:'+width+', Height:' + height); if (status === "success") { page.render('./screenshots/test_' +width+'x'+height+' .png'); } setTimeout(next_page,100); }); } function next_page(){ var test=tests.pop(); if (!test){ phantom.exit(0); } handle_page(test[0], test[1], test[2]); } next_page();
Skript: example.grid.cms.de.js
Das Skript verwendet ein Array für die Definition von Test-Parametern, die eine Seite mit unterschiedlichen (responsiven) Größen aufrufen und erstellt für jeden Test einen Snapshot in einem Ordner.
Einen solchen Test kann man mit dem grunt-shell-Modul einfach automatisieren. Der Aufruf erfolgt genauso wie für hello.js oben.
Unter der Voraussetzung, dass man PhantomJS und TrifleJS nicht über PhantomCSS, sondern als Standalone-Installation eingerichtet hat (am besten immer unter C:\. Achtung: Kompatibilitätsprobleme, falls man auch noch CasperJS standalone installieren möchte, dazu unten mehr) und die entsprechenden Pfade in der PATH-Variablen gesetzt hat, kann man das Skript dreimal aufrufen mit:
- phantomjs example.grid.cms.de.js,
- slimerjs example.grid.cms.de.js oder
- triflejs example.grid.cms.de.js.
Allerdings gilt die API-Kompatiblität nicht für alle API-Befehle. Inweit in SlimerJS und TrifleJS die komplette API von PhantomJS implementiert ist, hängt von den jeweiligen Versionen ab.
Damit man nicht lange überlegen muss, um sich die Ausgabe etwas komfortabler zu gestalten, kann man noch folgendes Html-File test.html im Root zum screenshot-Ordner anlegen:
<!DOCTYPE html> <html> <head> <style> img { position: relative; margin: 0 auto; max-width: 650px; padding: 5px; margin: 10px 0 5px 0; border: 1px solid #ccc; } </style> </head> <body> <h3>Screenshots</h3> <p> <div class="img"> <a target="_blank" href="screenshots/test_250x600 .png"><img src="screenshots/test_250x600 .png"></a> </div> <div class="img"> <a target="_blank" href="screenshots/test_300x600 .png"><img src="screenshots/test_300x600 .png"></a> </div> <div class="img"> <a target="_blank" href="screenshots/test_500x600 .png"><img src="screenshots/test_500x600 .png"></a> </div> <div class="img"> <a target="_blank" href="screenshots/test_700x600 .png"><img src="screenshots/test_700x600 .png"></a> </div> <div class="img"> <a target="_blank" href="screenshots/test_900x600 .png"><img src="screenshots/test_900x600 .png"></a> </div> <div class="img"> <a target="_blank" href="screenshots/test_1200x600 .png"><img src="screenshots/test_1200x600 .png"></a> </div> </p> </body> </html>
Damit hat man schon einen schönen Ausgangspunkt für Experimente mit PhantomJS, SlimerJS und TrifleJS.
Die Installation von TrifleJS ist allerdings etwas tricky, weshalb ich hierauf speziell eingehen möchte.
TrifleJS für das Headless-IE-Regression-Testing
Das schöne an TrifleJS ist, dass es die gleiche API-Schnittstelle verwendet, wie PhantomJS (WebKit-Browser) und natürlich auch SlimerJS (Gecko) und ich somit die wichtigsten Browser abgedeckt habe. Allerdings ist es nicht in PhantomCSS integriert, weshalb ich leider auf CSS-Testing für den IE verzichten muss, jedenfalls vorerst. Vielleicht wird das PhantomCSS-Projekt ja noch in diese Richtung erweitert.
Ein Problem bei der TrifleJS-Installation ist im Gegensatz zu allen anderen Tools, dass ich es nach dem Downloaden von GitHub selbst mit MSBuild compilieren muss. Auf GitHub befinden sich nur die V8-Sourcen.
Vorher muss ich sicherstellen, dass .NET3.5 auf meinem Windows-PC installiert ist.
Dies ist nun auch wieder etwas tricky, da z.B. in meinem Fall die aktuelle Version .NET4.0 installiert ist. Wer deshalb vielleicht Probleme bekommt, da .NET nicht sauber installiert wurde, sollte sich mit dem Automated cleanup tool zunächst alle vorhandenen .NET-Versionen deinstallieren und ruhig das aktuelle .NET4.0 oder höher sauber neu installieren. Die benötigte Version .NET3.5 ist darin enthalten.
Danach muss noch die Registrierung angepasst werden, da TrifleJS dort nach einer vorhandenen Version .NET3.5 sucht, aber diese nicht findet. Bei mir (Vista) musste dabei etwas getrixed werden.
Verschiedene Fehlermeldungen führten mich auf folgende Lösung:
- Mit regedit unter HKEY_LOCAL_MACHINE nach ToolsVersions suchen.
- Dort finde ich mehrere Einträge für MSBUild: 2.0, 3.4, 4.0 oder ähnlich.
- Unter 3.5 ändere ich MSBuildToolsPath auf C:\Windows\Microsoft.NET\Framework64\v3.5\, d.h. ich hänge ein 64 an Framework dran,
- Dann kopiere ich diesen kompletten Eintrag nach 4.0, da ich ja mit .NET4.0 arbeite
- Fertig und speichern.
Jetzt starte ich das Batchfile Build.TrifleJS.bat in meinem TrifleJS-Ordner per Console.
Danach finde ich unter C:\trifleJS-0.4\bin\Release die ausführbare exe-Datei TrifleJS.exe und diverse DLLs.
Allerdings finde ich ebenfalls die Zip-Datei TrifleJS.Latest.zip unter Build\Binary. Dort befindet sich auch eine exe-Datei und eine DLL sowie ein example-Ordner.
Dieses Zip-File extrahiere ich mir dorthin, wo ich es brauche.
Ich teste das Ganze, indem ich folgendes in eine Console eingebe:
TrifleJS.exe --emulate=IE8 --render=http://hauertmann.com
Damit erzeuge ich dann ein schönes Bild meiner Homepage.
Später habe ich dann dieses Zip-File auch auf der Homepage von TrifleJS als Stable v0.4 entdeckt. Da hätte ich mir die ganze Arbeit wahrscheinlich sparen können.
Wer sich aber das jeweils letzte Build auf GitHub installieren möchte, für den sind die Hinweise vielleicht brauchbar.
Kompatibilitätsprobleme von PhantomJS und CasperJS standalone
Ich habe schon erwähnt, dass die schnellste und konfliktfreieste Methode, CasperJS und PhantomJS zu installieren, die ist, einfach PhantomCSS herunterzuladen und zu entpacken. Das Starter-Shell-Skript für CasperJS setzt dann alle erforderlichen Umgebungsvariablen selbst und man kann davon ausgehen, dass die Versionen von PhantomJS und CasperJS zueinander passen. Bei der Installation wird man feststellen, dass ein PhantomJS 1.9x installiert wird, also nicht die aktuellste Version 2.0.
Wer mit den aktuellesten Versionen arbeiten möchte, sollte sich auf jeden Fall die Master-Version von CasperJS über GitHub installieren und dazu Phantom 2.0 direkt von der Homepage. Dann passen beide Versionen zusammen (zumindest bei mir), auch wenn man beim Start eines Testskripts immer die Meldung bekommt, dass die Version Phantom 2.0 nicht unterstützt wird.
Richtet man sich danach und versucht eine ältere Version von Phantom zu installieren, läuft man in eine unangenehme Falle. Denn dann wird es Probleme mit der Tester-Instance von CasperJS geben. Alle Skripte, die Befehle in dieser Art enthalten: capser.test.irgendwas(...) werden scheitern. Es handelt sich um einen Bug, der in diesem Thread diskutiert wird.
Der entscheidende Kommentar dort ist der von rscheuermann, der die Installation wie vorstehend zwar nur für MAC OSX vorstellt, die aber auch bei mir (Vista) funktionierte.
Leider war ich in die Falle gelaufen, nach einer PhantomJS-Version kleiner als 2.0 zu suchen aufgrund des angegebenen Hinweises beim Start des Skripts, dass nämlich 2.0 noch nicht unterstützt wird, was mich dann sehr viel Zeit gekostet hat.
Für das Testen ist auch wichtig zu verstehen, dass CasperJS ein Tester-Objekt enthält (erkennbar an Befehlen wie casper.test.irgendwas ...), das in der Kommandozeile das Argument test voraussetzt, um instanziert zu werden, z.B.:
casperjs test demo/testsuite.js --engine=slimerjs
Ich hatte das ja schon erwähnt.
Falls man das Tester-Objekt nicht benötigt, benötigt man auch dieses Argument nicht für das Starten des Skripts. Dies sollte man wissen, wenn man sich mit CasperJS noch nicht sehr gut auskennt, aber die erste Installation mit einfachen Skripten testen möchte. Wer das nicht weiß, ist schnell verwirrt und hält die ausgegebenen Fehlermeldungen für Fehler in seinem Test-Skript.
Welche Bereiche werden durch die vorgestellten Tools abgedeckt?
Alle in Abb. 2 dargestellten automatisierbaren Einsatz-Bereiche werden mit den hier vorgestellten Tools im wesentlichen abgedeckt bis bis auf das Testen von dynamischem CSS für den IE. Ich hoffe allerdings, dass ich mich mit dieser Behauptung jetzt nicht blamiert habe. Aber ich kann unmöglich alle Aspekte dieser Tools bis in die Einzelheiten hin untersuchen.
Grundsätzlich kann man natürlich auch funktionale Tests für CSS-Testing für den IE alleine mit TrifleJS programmieren und für Regression dann ebenfalls ResembleJS nehmen.
Vielleicht wäre das auch grundsätzlich eine Strategie: Man verzichtet ganz auf CapserJS und programmiert alle funktionalen Tests alleine mit PhantomJS, SlimerJS und TrifleJS. Natürlich fehlt einem dann die komfortable CasperJS-API, aber grundsätzlich ist das möglich.
Manchmal wird als Alternative zu CasperJS für die Automatisierung von Regression-Tests das Tool BackstopJS genannt, das ebenfalls mit ResembleJS zusammenarbeitet. In diesem Artikel wird der Einsatz diskutiert. Ich habe diesen Ansatz nicht näher untersucht. Vorsicht übrigens dort mit der Installation verschiedener Tools per npm. Das riecht nach Versionskonflikten.
Fazit und Ausblick
Die Strategie Selenium / WebDriver wurde hier nicht weiter verfolgt. Stattdessen wurden Tools auf Basis der V8-Javascript-Maschine von Google diskutiert und es wurde versucht, die Anforderungen des funktionalen Testens damit möglichst umfassend abzudecken.
Dies ist nicht ganz gelungen, denn wie sehr oft, ist hier der IE dein Gegner. Zwar ist es mit Sicherheit möglich, auch Tests nur auf Basis der drei Headless-Tools (PhantomJS, SlimerJS und TrifleJS) zu schreiben und auch das CSS-Testing wird mit entsprechendem Programmieraufwand realisierbar sein.
Allerdings wird man auf den Komfort einer API wie sie PhantomCSS (bzw. CasperJS) für das CSS-Testing zur Verfügung stellt, dann verzichten müssen.
In vielen Fällen wird diese Lücke verschmerzbar sein, zumal man auf keinen Fall eine Strategie verfolgen sollte, alles und jedes zu testen.
Wenn man für alle funktionalen Anforderungen seiner Applikation im Prinzip Tools zur Verfügung hat und nur für bestimmte Teile des Visual-Testing noch auf die gute alte Art manuell prüfen muss, ist man mit Lighweight-Tools der hier vorgestellten Art vielleicht ganz gut bedient, zumal die Entwicklung in diesem Bereich rasant ist und sicherlich bald auch für die letzten Lücken Lösungen zur Verfügung stehen.
Zukunftssicher sind V8-Maschine-Lösungen allemal. Die Integrationsmöglichkeiten in spezifische Grunt-Workflows sind vielversprechend.
Ich werde deshalb für die hier im Blog vorgestellten Projekte hier und da, wenn auch sehr sparsam, auf diese Tools zurückgreifen. Das habe ich jetzt bewußt sehr vorsichtig formuliert.