Testgetriebene Entwicklung mit Jasmine und Karma hat sich mittlerweile schon als defacto-Standard etabliert. Routinen ohne Abhängigkeiten lassen sich damit ohne Probleme testen. Die Schwierigkeiten beginnen jedoch schon, wenn es um die Auflösung von Abhängigkeiten geht. In diesem Vortrag werden verschiedene Strategien und Werkzeuge vorgestellt, mit denen Abhängigkeiten zu Objekten und Funktionen oder zum Server abgedeckt werden können. Aber nicht nur Abhängigkeiten stellen Schwierigkeiten bei der testgetriebenen Entwicklung dar, auch der Umgang mit Fixtures ist bei der testgetriebenen Entwicklung mit JavaScript relevant. Abgerundet wird dieser Vortrag mit einigen Best Practices für die testgetriebenen Entwicklung mit JavaScript.
2. WHO AM I?
• Sebastian Springer
• aus München
• arbeite bei Mayflower
• https://github.com/sspringer82
• @basti_springer
• Consultant, Trainer, Autor
4. Macht sich erst mittel- bis langfristig bezahlt. Initial
verursachen Tests mehr Aufwand, als wenn nur Code
geschrieben wird. Bei TDD entstehen die Tests vor dem
eigentlichen Code. Geben Sicherheit beim Erweitern und
Umbauen. Tests dokumentieren Quellcode.
6. Für JavaScript-Testing im Frontend benötigt man ein Test-
Framework, das die Formulierung von Tests ermöglicht.
Zusätzlich zum Test-Framework ist eine Infrastruktur
erforderlich, die Tests auf verschiedenen Browsern und
Automatisierung ermöglicht.
7. Test-Frameworks
Häufig eingesetzte Frameworks für JavaScript-Testing:
• Jasmine (http://jasmine.github.io/)
• Mocha (http://visionmedia.github.io/mocha/)
• qunit (http://qunitjs.com/)
14. Red
Write a failing test.
Die Erwartungshaltung bzw. das zu erreichende Ziel wird als
Test formuliert. Der Test schlägt fehl.
15. Red
describe(‘Calculator’, function() {
it(‘should add 1 and 1’, function() {
var calc = new Calculator();
var result = calc.add(1,1);
expect(result).toBe(2);
});
});
16. Red
$ karma run
[2014-10-15 13:08:56.773] [DEBUG] config - Loading config /karma.conf.js
Chrome 38.0.2125 (Mac OS X 10.9.5) Calculator should add 1 and 1 FAILED
ReferenceError: Calculator is not defined
at Object.<anonymous> (/spec/calc.spec.js:3:24)
Chrome 38.0.2125 (Mac OS X 10.9.5): Executed 1 of 1 (1 FAILED) ERROR
(0.021 secs / 0.002 secs)
17. Green
Make your tests work.
In diesem Schritt unternimmt man alles, um den
fehlschlagenden Test grün zu bekommen. Hier sollte der
kürzeste und schnellstmögliche Weg gewählt werden.
Einfache Probleme können direkt gelöst werden.
Umfangreichere Probleme werden durch einen Fake
(einfaches Return) gelöst.
19. Green
$ karma run
[2014-10-15 13:15:51.258] [DEBUG] config - Loading config /karma.conf.js
Chrome 38.0.2125 (Mac OS X 10.9.5): Executed 1 of 1 SUCCESS (0.019 secs /
0.001 secs)
20. Refactor
Reduce redundancy.
Duplikate im Code entfernen. Im Idealfall durch
Zusammenfassung und Auslagerung. Gilt sowohl für
Produktivcode als auch für Tests.
Nicht optimale Implementierungen verbessern.
Immer nur bei grünen Tests durchführen!
21. Refactor
describe('Calculator', function() {
var calc;
beforeEach(function() {
calc = new Calculator();
});
it('should add 1 and 1', function() {
var result = calc.add(1,1);
expect(result).toBe(2);
});
it('should subtract 1 from 2', function(){…})
});
22. Refactor
$ karma run
[2014-10-15 13:15:51.258] [DEBUG] config - Loading config /karma.conf.js
Chrome 38.0.2125 (Mac OS X 10.9.5): Executed 2 of 2 SUCCESS (0.019 secs /
0.001 secs)
24. TDD und Frameworks
TDD mit JavaScript funktioniert auch, wenn die Applikation mit
einem Framework wie Angular, Backbone oder Ember
erstellt ist.
Also keine Ausreden! - ;)
Manche Frameworks (z.B. Angular) unterstützen Entwickler
sogar bei der testgetriebenen Entwicklung von
Applikationen.
26. Automatisierung
Automatisierung in der Entwicklungsumgebung. Tests
werden automatisch beim Speichern ausgeführt.
Automatisierung in der Continuous Integration. grunt-karma
Plugin für Grunt nutzen. Tests werden beim Push ins
Repository ausgeführt.
28. Kurze Laufzeit
Tests, die lange laufen, werden nicht häufig ausgeführt.
Werden die Tests nicht ausgeführt, leidet die Qualität.
29. Three out of four
Timo Klostermeier / pixelio.de
30. Three out of four
Seid nie mehr als eine Änderung von grünen Tests
entfernt!
Auch wenn Umbaumaßnahmen erforderlich sind.
Die Applikation sollte immer in möglichst kleinen
überschaubaren Schritten umgebaut werden, ansonsten
bringen die Tests nichts. 100 fehlschlagende Tests sind keine
Hilfe, sondern eher ein Hindernis.
32. Obvious Implementation &
Baby Steps
Jeder bestimmt selbst, was offensichtlich ist und nicht
getestet werden muss. Auch die Schrittweite bei den Tests
muss jeder selbst festlegen.
Grundsätzlich gilt allerdings: Alles, was potenziell kaputt
gehen kann, muss getestet werden.
Wird man vom Verhalten der Applikation überrascht, sollte
man auf jeden Fall einen Test schreiben.
34. Codequalität
Die Tests existieren länger als der eigentliche Quellcode. Für
die Tests sollten die gleichen Qualitätskriterien gelten wie für
den eigentlichen Quellcode.
Die Wartbarkeit und Erweiterbarkeit der Tests müssen
erhalten bleiben.
37. Spy
Wrapper um eine Funktion. Aufrufe werden direkt an die
ursprüngliche Funktion weitergeleitet. Aufrufe werden
aufgezeichnet.
Spy-Objekt kann später abgefragt werden.
Spys sollten später zurückgesetzt werden.
38. Spy
it("should create a spy for a method", function () {
var myObj = {
name: 'Klaus',
getName: function () {
return this.name;
}
};
var spy = sinon.spy(myObj, 'getName');
myObj.getName();
expect(spy.calledOnce).toBeTruthy();
myObj.getName.restore();
});
40. Stub
Wrapper um eine Funktion wie Spys. Weisen ein definiertes
Verhalten auf. Leiten den Aufruf nicht direkt an die
ursprüngliche Funktion weiter.
Reduzieren Abhängigkeiten und vereinfachen
Testumgebungen.
41. Stub
it('should return and throw', function () {
var myObj = {
getName: function () {},
setName: function () {}
}
var stub1 = sinon.stub(myObj, 'getName').returns('Klaus');
var stub2 = sinon.stub(myObj, ‘setName').throws(
new Error(‘BOOH!’)
);
expect(stub1()).toBe('Klaus');
});
43. Mock
Ebenfalls Wrapper um eine Funktion. Dienen dazu, die
korrekte Verwendung einer Funktion sicherzustellen. Wird
die Funktion nicht korrekt verwendet, wird eine Exception
geworfen.
Best Practice: Nicht mehr als ein Mock pro Test.
44. Mock
it ('should work with mocks', function () {
var myObj = {
name: 'Klaus',
getName: function () {
return this.name;
}
}
var mock = sinon.mock(myObj);
mock.expects('getName').once();
myObj.getName();
myObj.getName();
mock.verify();
});
49. Server
Wrapper um XMLHttpRequest bzw. ActiveXObject. Tests
werden unabhängig von einer Server-Infrastruktur ausgeführt.
Kontrolle über die Antworten des Fake Servers.
52. Fixtures
HTML-Struktur, die für die Tests benötigt wird.
Unabhängigkeit der Tests soll verbessert werden. Durch
vorbereitete Strukturen können Ausschnitte von Workflows
getestet werden.
Auslieferung von HTML über den html2js preprocessor von
Karma.
jasmine-jquery als Helper für den Umgang mit Fixtures.
53. Fixtures
beforeEach(function () {
$('body').append(window.__html__['fixtures/fx.html']);
$('#registerForm').on('submit', validate);
});
it ("should show four messages", function () {
$('#firstname').val('Klaus');
$('#registerForm').submit();
expect($('.message.visible').length).toBe(4);
});
59. KONTAKT
Sebastian Springer
sebastian.springer@mayflower.de
Mayflower GmbH
Mannhardtstr. 6
80538 München
Deutschland
@basti_springer
https://github.com/sspringer82