test driven java script development
DESCRIPTION
JavaScript ist eine sehr dynamische Sprache und verhält sich zudem je nach Browser unterschiedlich. Daher sind automatisierte Tests besonders wertvoll. Dieser Vortrag zeigt, wie Cross-Browser-Tests für JavaScript entwickelt werden können.Vortrag von der MobileTech Conference 2011TRANSCRIPT
Test-‐Driven JavaScript Development
Tobias Bosch & Stefan Scheidt / OPITZ CONSULTING GmbH
Wer sind wir?
tobias.bosch@opitz-‐consulLng.com (@Lgbro)
stefan.scheidt@opitz-‐consulLng.com
(@beezlebug)
© OPITZ CONSULTING GmbH 2011 Seite 3
OPITZ CONSULTING Vorlage Powerpoint 2011; Version 1.3; 10.05.2011; TGA, KSH
1Pager • Layout ausschließlich für den
1Pager • Einsatz ist bei Konferenzen,
ext. Veranstaltungen etc. obligatorisch. Die Folie ist Folie 2 (nach der Titelfolie)
• Der Inhalt darf nicht verändert werden.
• Ausnahme: Der Block Märkte darf situativ um Partnerlogos (ORACLE, etc.) ergänzt werden
© OPITZ CONSULTING GmbH 2011
Märkte n Java n SOA n ORACLE n BI/DWH n Outtasking
Kunden n Branchen-
übergreifend n Über 600
Kunden
Leistungs- angebot n IT-Strategie n Beratung n Implementierung n Betrieb n Training
Fakten n Gründung 1990 n 400 Mitarbeiter n 8 Standorte in D/
PL
Industrie / Versorger / Telekommunikation
29%
Handel / Logistik / Dienstleistungen 29%
42% Öffentliche Auftraggeber /
Banken & Versicherungen / Vereine & Verbände
Wer sind Sie?
In diesem Vortrag geht‘s um...
...automaLsiertes Testen ...durch Entwickler
Warum testen?
Warum JavaScript-‐Code testen?
Laufzeitumgebungen für JavaScript
...
In diesem Vortrag:
Icon Set by Paul Irish (h4p://paulirish.com/2010/high-‐res-‐browser-‐icons/)
Tests formulieren
„Klassisch“ mit xUnit-‐Asserts
„BDD-‐Style“ à la RSpec
Demo: BDD mit Jasmine
describe("parseFloat", function() { it("should return undefined for undefined", function () { expect(util.parseFloat(undefined)).toBeUndefined(); }); it("should return 0 for empty string", function () { expect(util.parseFloat('')).toEqual(0); }); it("should return number for number strings", function () { expect(util.parseFloat('1.5')).toEqual(1.5); }); // ... });
Hello Jasmine!
describe("parseFloat", function() { it("should return undefined for undefined", function () { expect(util.parseFloat(undefined)).toBeUndefined(); }); it("should return 0 for empty string", function () { expect(util.parseFloat('')).toEqual(0); }); it("should return number for number strings", function () { expect(util.parseFloat('1.5')).toEqual(1.5); }); // ... });
Hello Jasmine!
Suite
describe("parseFloat", function() { it("should return undefined for undefined", function () { expect(util.parseFloat(undefined)).toBeUndefined(); }); it("should return 0 for empty string", function () { expect(util.parseFloat('')).toEqual(0); }); it("should return number for number strings", function () { expect(util.parseFloat('1.5')).toEqual(1.5); }); // ... });
Hello Jasmine!
Specs
describe("parseFloat", function() { it("should return undefined for undefined", function () { expect(util.parseFloat(undefined)).toBeUndefined(); }); it("should return 0 for empty string", function () { expect(util.parseFloat('')).toEqual(0); }); it("should return number for number strings", function () { expect(util.parseFloat('1.5')).toEqual(1.5); }); // ... });
Hello Jasmine!
Matcher
Hello Jasmine!
expect(x).toEqual(y);
expect(x).toBe(y);
expect(x).toBeDefined();
expect(x).toBeUndefined();
expect(x).toBeTruthy();
expect(x).toBeFalsy();
...
Matcher
Hello Jasmine!
Spec Runner
Fortgeschriaene Konzepte
Spies
Spies describe("execute fadein", function() { it("should eventually set opacity to 1", function () { var element = document.createElement("div"); spyOn(window, 'setTimeout').andCallFake( function(callback) { callback(); }); spyOn(util, 'opacity'); fadein.execute(element); expect(util.opacity.mostRecentCall.args[1]).toEqual(1); }); });
Spies describe("execute fadein", function() { it("should eventually set opacity to 1", function () { var element = document.createElement("div"); spyOn(window, 'setTimeout').andCallFake( function(callback) { callback(); }); spyOn(util, 'opacity'); fadein.execute(element); expect(util.opacity.mostRecentCall.args[1]).toEqual(1); }); });
Spies describe("execute fadein", function() { it("should eventually set opacity to 1", function () { var element = document.createElement("div"); spyOn(window, 'setTimeout').andCallFake( function(callback) { callback(); }); spyOn(util, 'opacity'); fadein.execute(element); expect(util.opacity.mostRecentCall.args[1]).toEqual(1); }); });
Arrange
Spies describe("execute fadein", function() { it("should eventually set opacity to 1", function () { var element = document.createElement("div"); spyOn(window, 'setTimeout').andCallFake( function(callback) { callback(); }); spyOn(util, 'opacity'); fadein.execute(element); expect(util.opacity.mostRecentCall.args[1]).toEqual(1); }); });
// waits for 'delay' seconds // then calls callback window.setTimeout = function (callback, delay) { ... };
Arrange
Spies describe("execute fadein", function() { it("should eventually set opacity to 1", function () { var element = document.createElement("div"); spyOn(window, 'setTimeout').andCallFake( function(callback) { callback(); }); spyOn(util, 'opacity'); fadein.execute(element); expect(util.opacity.mostRecentCall.args[1]).toEqual(1); }); });
Arrange // set opacity of 'element' to 'opacity' util.opacity = function (element, opacity) { ... };
Spies describe("execute fadein", function() { it("should eventually set opacity to 1", function () { var element = document.createElement("div"); spyOn(window, 'setTimeout').andCallFake( function(callback) { callback(); }); spyOn(util, 'opacity'); fadein.execute(element); expect(util.opacity.mostRecentCall.args[1]).toEqual(1); }); });
Act
Spies
Assert
describe("execute fadein", function() { it("should eventually set opacity to 1", function () { var element = document.createElement("div"); spyOn(window, 'setTimeout').andCallFake( function(callback) { callback(); }); spyOn(util, 'opacity'); fadein.execute(element); expect(util.opacity.mostRecentCall.args[1]).toEqual(1); }); });
// set opacity of 'element' to 'opacity' util.opacity = function (element, opacity) { ... };
FunkLon Der Spy soll...
spy.andCallThrough() ... die ursprüngliche FunkLon aufrufen
spy.andReturn(argument) ... das übergebene Argumente zurückgeben
spy.andThrow(exception) ... die übergebene ExcepLon werfen
spy.andCallFake(function) ... die übergebene FunkLon aufrufen
FunkLonen von Spies
FunkLon Prüb, ob der Spy...
toHaveBeenCalled() ... aufrufen wurde
toHaveBeenCalledWith(args) ... mit den übergebenen Argumenten aufgerufen wurde
Matcher für Spies
FunkLon Bedeutung
spy.callCount Anzahl der Aufrufe
spy.argsForCall[i] Argumente des i-‐ten Aufrufs
spy.mostRecentCall.args Argumente des letzten Aufrufs
Eigenschaben von Spies
Asynchrone Tests
it("should eventually set opacity to 1", function() { var element = document.createElement("div"); spyOn(util, 'opacity'); runs(function() { fadein.execute(element); }); waitsFor(function () { return opacitySpy.mostRecentCall.args[1] === 1; }); runs(function() { // some other expectations expect(opacitySpy.mostRecentCall.args[1]).toEqual(1); }); });
Asynchrone Tests
it("should eventually set opacity to 1", function() { var element = document.createElement("div"); spyOn(util, 'opacity'); runs(function() { fadein.execute(element); }); waitsFor(function () { return opacitySpy.mostRecentCall.args[1] === 1; }); runs(function() { // some other expectations expect(opacitySpy.mostRecentCall.args[1]).toEqual(1); }); });
Asynchrone Tests
Arrange
it("should eventually set opacity to 1", function() { var element = document.createElement("div"); spyOn(util, 'opacity'); runs(function() { fadein.execute(element); }); waitsFor(function () { return opacitySpy.mostRecentCall.args[1] === 1; }); runs(function() { // some other expectations expect(opacitySpy.mostRecentCall.args[1]).toEqual(1); }); });
Asynchrone Tests
Act
it("should eventually set opacity to 1", function() { var element = document.createElement("div"); spyOn(util, 'opacity'); runs(function() { fadein.execute(element); }); waitsFor(function () { return opacitySpy.mostRecentCall.args[1] === 1; }); runs(function() { // some other expectations expect(opacitySpy.mostRecentCall.args[1]).toEqual(1); }); });
Asynchrone Tests
Wait...
// set opacity of 'element' to 'opacity' util.opacity = function (element, opacity) { ... };
it("should eventually set opacity to 1", function() { var element = document.createElement("div"); spyOn(util, 'opacity'); runs(function() { fadein.execute(element); }); waitsFor(function () { return opacitySpy.mostRecentCall.args[1] === 1; }); runs(function() { // some other expectations expect(opacitySpy.mostRecentCall.args[1]).toEqual(1); }); });
Asynchrone Tests
Assert
UI-‐Tests
it("should fade in hello div on button click", function () { var win, field, button; loadHtml("/js-‐fadein/index.html"); runs(function() { win = testwindow(); field = win.document.getElementById('hello'); button = win.document.getElementById('fadein'); }); runs(function () { expect(win.util.opacity(field)).toEqual(0); jasmine.ui.simulate(button, 'click'); }); waitsForAsync(); runs(function () { expect(win.util.opacity(field)).toEqual(1); }); });
UI-‐Test mit Jasmine-‐UI
it("should fade in hello div on button click", function () { var win, field, button; loadHtml("/js-‐fadein/index.html"); runs(function() { win = testwindow(); field = win.document.getElementById('hello'); button = win.document.getElementById('fadein'); }); runs(function () { expect(win.util.opacity(field)).toEqual(0); jasmine.ui.simulate(button, 'click'); }); waitsForAsync(); runs(function () { expect(win.util.opacity(field)).toEqual(1); }); });
The index pa
ge...
UI-‐Test mit Jasmine-‐UI
it("should fade in hello div on button click", function () { var win, field, button; loadHtml("/js-‐fadein/index.html"); runs(function() { win = testwindow(); field = win.document.getElementById('hello'); button = win.document.getElementById('fadein'); }); runs(function () { expect(win.util.opacity(field)).toEqual(0); jasmine.ui.simulate(button, 'click'); }); waitsForAsync(); runs(function () { expect(win.util.opacity(field)).toEqual(1); }); });
Arrange, Part 1
UI-‐Test mit Jasmine-‐UI
it("should fade in hello div on button click", function () { var win, field, button; loadHtml("/js-‐fadein/index.html"); runs(function() { win = testwindow(); field = win.document.getElementById('hello'); button = win.document.getElementById('fadein'); }); runs(function () { expect(win.util.opacity(field)).toEqual(0); jasmine.ui.simulate(button, 'click'); }); waitsForAsync(); runs(function () { expect(win.util.opacity(field)).toEqual(1); }); });
Arrange, Part 2
UI-‐Test mit Jasmine-‐UI
it("should fade in hello div on button click", function () { var win, field, button; loadHtml("/js-‐fadein/index.html"); runs(function() { win = testwindow(); field = win.document.getElementById('hello'); button = win.document.getElementById('fadein'); }); runs(function () { expect(win.util.opacity(field)).toEqual(0); jasmine.ui.simulate(button, 'click'); }); waitsForAsync(); runs(function () { expect(win.util.opacity(field)).toEqual(1); }); });
Act
UI-‐Test mit Jasmine-‐UI
it("should fade in hello div on button click", function () { var win, field, button; loadHtml("/js-‐fadein/index.html"); runs(function() { win = testwindow(); field = win.document.getElementById('hello'); button = win.document.getElementById('fadein'); }); runs(function () { expect(win.util.opacity(field)).toEqual(0); jasmine.ui.simulate(button, 'click'); }); waitsForAsync(); runs(function () { expect(win.util.opacity(field)).toEqual(1); }); });
Wait...
UI-‐Test mit Jasmine-‐UI
it("should fade in hello div on button click", function () { var win, field, button; loadHtml("/js-‐fadein/index.html"); runs(function() { win = testwindow(); field = win.document.getElementById('hello'); button = win.document.getElementById('fadein'); }); runs(function () { expect(win.util.opacity(field)).toEqual(0); jasmine.ui.simulate(button, 'click'); }); waitsForAsync(); runs(function () { expect(win.util.opacity(field)).toEqual(1); }); });
Assert
UI-‐Test mit Jasmine-‐UI
Testausführung automaLsieren
Pro Contra Beispiele
Im Browser testen
Laufen in ProdukLons-‐umgebung
-‐ Schwerer zu automaLsieren
-‐ Schwerer in CI einzubeaen
QUnit YUI Test Jasmine
Headless Browser
-‐ Leicht zu automaLsieren
-‐ Leicht in CI einzubeaen
Browser wird nur simuliert
Envjs
PhantomJS Zombie.js HtmlUnit
Browser fernsteuern
JsTestDriver
h4p://code.google.com/p/js-‐test-‐driver/
Demo: JsTestDriver
Fazit
Es gibt ein umfangreiches Toolset zum Testen von JavaScript-‐Code.
Testen Sie Ihren Code!
Testen Sie insbesondere Ihren JavaScript-‐Code! Sie haben keine Ausrede...
Links
Jasmine hap://pivotal.github.com/jasmine/
Jasmine UI haps://github.com/Lgbro/jasmine-‐ui
JsTestDriver hap://code.google.com/p/js-‐test-‐driver/
saturated wriLng (By Eduordo, hap://www.flickr.com/photos/tnarik/366393127/)
Smiley Keyboard
(By ~Prescoa, hap://www.flickr.com/photos/ppym1/93571524/)
Spyhole, Paris (By smith of smiths, hap://www.flickr.com/photos/hesketh/232015302/)
Sorta synchronised diving
(By Aaron Retz, hap://www.flickr.com/photos/aretz/309469339/)
Stamp Collector (By C. CasLllo, hap://www.flickr.com/photos/carolinedecasLllo/2750577684/)
bios [bible]
(By Gastev, hap://www.flickr.com/photos/gastev/2174505811/)
Day 276/365: in hot pursuit (By Tina Vega, hap://www.flickr.com/photos/Lnavega/3066602429/)
Besuchen Sie uns Morgen beim Vortrag
JavaScript Data Binding mit jQuery Mobile
um 10:00 Uhr im Watordsaal
Vielen Dank für Ihr Interesse!
@Lgbro
@beezlebug