Nieder mit den Webtests!
Wie man Browser JavaScript richtig testet
Leonard Ehrenfried - http://leonard.io
Freelance Software Consultant
Ich gebe es zu
Der Titel ist ein bisschen reißerisch.
tl;dr: Webtests sind nicht an und für sich schlecht, aber oft das falsche Tool.
Agenda
- Disclaimer, Begriffsklärung, Einleitung
- Wofür sind Webtests gut?
- Was sind die Probleme von Webtests?
- Unit Tests für JavaScript
- Umsetztung in Backbone und Angular
- Schritt für Schritt zu getestetem JavaScript
- Q&A
Disclaimer
Vieles, wovon ich spreche, bezieht sich auf Web-Applikationen und nicht auf Webseiten.
Was ist der Untschied?
Wenn der Zweck der Seite ist, Inhalt zu präsentieren, dann ist es eine Webseite.
Wenn es darum geht, mit der Seite zu interagieren, dann ist es eine Web-Applikation.
Testing
Ist kein neuer Trend - Kent Becks SUnit
wurde 1998 released
Ziemlich weitläufig als gute Praxis anerkannt
TDD hat sich auf Serverseite durchgesetzt
Browser JS Testing
Sehr viel neuer
Für lange Zeit gab es nicht viel JS, das zu testen war
JS wurde für lange Zeit nicht ernst genommen, aber das hat sich sehr geändert
JS ist schwach typisiert; Tests sind viel wichtiger
Was ist ein Webtest?
Webtests sind die Test, die den gesamten Userablauf inklusive der Interaktion mit dem Browser testen.
Ziel: Fährt das Auto?
Das bei weitestem beliebteste Tool ist Selenium/Webdriver.
Nachteile von Webtests
- Langsam: Tests werden nicht vor dem Commit ausgeführt
- Wartungsaufwändig: Asynchronität führt zu vielen Problemen
- Sehr grobe Granularität: Unklar, was den Test fehlschlagen lassen hat
- Nonlokalität: schwierig ein Szenario unter Test aufzubauen
- Nondeterministisch und flaky: Viele false negatives, Entwickler fangen an fehlgeschlagene Tests zu ignoreren
- Schlechte Debuggability: Man kann nicht durch die Tests steppen, da der Browser unabhängig vom Testrunner ist
- Keine Unit Tests, sondern Scenariotests
Wofür sind Webtests gut?
Einmal alle Schichten durchzutesten
Happy path
Testbarkeit
Server-seitiger Code von 1998: wahrscheinlich nicht besonders testbar geschrieben
- Globale Variablen
- Statische Methoden
- Feste Abhängigkeit
Verbesserungen
- DI
- Lokale Variablen
- Polymorphism, "ask, don't tell"
Problemfälle der JS-Testbarkeit
- DOM-Manipulation
- AJAX
- Animationen,
setTimeout()
HTML und JS
- HTML enthält oft Logik
- Wird selten mitgetestet
- HTML-Schnipsel nicht global aus dem DOM holen!
- Als explizite Abhängigkeiten definieren, z.B. mit requireJS
Beispiel RequireJS Text Plugin
define(["text!templates/view.html"], function(template){
return function(){
var div = $("<div>").html(template);
div.delegate("a", "click", function(){
div.find("h1").css("color", "red");
});
return div;
};
});
Probleme: AJAX
- Benötigt laufenden Server, der Antworten zurückschickt
- Macht Testsetup komplizierter und -ausführung langsamer
- Ziel sollte sein, Tests ohne Server laufen zu lassen
Lösungen: AJAX
- In server-seitigem Code mit DAO-Schicht gemockt
- Globales
XMLHttpRequest
monkey-patchen
- Oder Mock HTTP-Service injecten
Exkurs: sinon.js
Sehr beliebte Library, um HTTP zu mocken
var fakeAjax = function(func) {
var xhr = sinon.useFakeXMLHttpRequest();
var requests = [];
xhr.onCreate = function(request){
requests.push(request);
}
func(requests);
xhr.restore();
});
Anwendung
fakeAjax(function(requests){
var customer = new Customer({ id:1234 });
customer.fetch();
var req = requests[0];
req.respond(200, {id : 1234, name: "Horst Kasuppke"});
expect(customer.get("name")).toBe("Horst Kasuppke");
});
Führt zu einem deterministischen Test ohne implizites Warten
Probleme: Animationen, setTimeout()
- Animationen arbeiten oft mit
setTimeout()
var callbackCalled = false;
jasmine.Clock.useMock();
setTimeout(function() {
callbackCalled = true;
}, 100);
jasmine.Clock.tick(101);
expect(callbackCalled).toBe(true);
- Tests könnnen ohne implizite Waits ausgeführt werden und bleiben blitzschnell
Fazit bis hierhin
JS ist oft höchst asynchron.
Mit einem zweiten Programm/VM
sinnvolle Asserts zu schreiben ist extrem wartungsaufwändig.
Um schnelle Unit Tests für Browser JS zu schreiben, müssen wir viel
näher an den Code, als es mit Selenium möglich ist.
Um Frontendcode testbar zu machen, müssen wir ihn aus den Klauen des Servers befreien.
Backbone
- Sehr frühes, minimalistisches Frontend-MVC Framework
- Gute Wahl für Apps mit niedriger Komplexität und recht wenig Features
- Sehr testbar, da sehr simpel strukturiert
- Muss sich allerdings Disziplin selbst auferlegen
Wie testet man eine Backbone View?
fakeAjax(function(requests){
var view = new FooView();
view.render();
var req = requests[0];
req.respond(200, [
{ name: "Guido van Rossum" },
{ name: "Brendan Eich" }]);
view.$el.find("button").click();
expect(view.$el.find("ul li").size()).toBe(2);
});
Angular
- Im Moment der Darling der JavaScript-Welt
- Generell sehr testbar aufgebaut: MVC, DI, Separation of Concerns, Controllers, Mocks
- Autor (Miško Hevery) ist ein bekannter Testing-Coach
Angulars Stärken
- Sehr mächtige Templating-Sprache
- Testen wird sehr groß geschrieben
- Alle Abhängigkeiten können injected werden
- ajax,
window
, document
, setTimeout
können einfach gemockt werden
Schwächen (nicht viele)
- Steile Lernkurve
- Controllers werden ohne HTML-Templates getestet
- Verlässt sich auf sogenannte "end-to-end"-Tests
- Fühlt sich wie ein Webtest an
Engines: wie führe ich meine Tests aus?
- Während der Entwicklung: Browser (Demo)
- PhantomJS: headless Webkit
- Experimentell: node.js mit jsdom, contextify
- Gründlich, aber aufwendig: In allen Zielbrowsern
Schritt für Schritt zu einem testbaren Frontend
Konzeptionelle Verbesserungen
- In vielen Software-Shops wird zuletzt über das Frontend gedacht
- Am wichtigsten: Bewusstsein für Frontend-Probleme schaffen
- Frontend-Entwicklung zu einer Priorität machen
- Gleiche Rigorosität anwenden wie für die Backend-Entwicklung
- Frontend-Spezialisten einstellen
- Toxischen IE-Support beenden
Sorgen der Frontend-Entwickler ernst nehmen.
Technische Verbesserungen
Wie legt man einen Sumpf trocken?
- Ersten Unit Test schreiben
- CI für JS aufsetzen
- Limitationen der Testbarkeit der Applikation kennenlernen
- Neue Features test-getrieben entwickeln
- Langer steiniger Weg
Meine Empfehlungen für Libraries
- Jasmine als Testframework
- Sinon für HTTP Mocking
- Handlebars für client-side Templating
- Backbone für kleinere Apps
- Angular für komplexere Apps
Zusammenfassung
Um einen Scheibenwischer zu testen, muss man keine Probefahrt im Regen machen.
THE END - FRAGEN?
Leonard Ehrenfried