tester son js, c'est possible !

39
TESTER SON JS C'EST POSSIBLE ! Raphaël Rougeron /

Upload: goldoraf

Post on 27-Jun-2015

2.490 views

Category:

Technology


5 download

TRANSCRIPT

Page 1: Tester son JS, c'est possible !

TESTER SON JSC'EST POSSIBLE !

Raphaël Rougeron /

Page 2: Tester son JS, c'est possible !

TDDDÉVELOPPEMENT DIRIGÉ PAR LES

TESTS

Page 3: Tester son JS, c'est possible !

LES 3 RÈGLES DU TDDOn n'ajoute du code que pour faire passer un testLes tests doivent être le plus simple possibleOn écrit le minimum de code requis pour faire passer letest

Page 4: Tester son JS, c'est possible !

RÉACTIONS TYPIQUESC'est stupide !

On perd du temps à écrire des tests !

Cela perturbe mon flux !

Page 5: Tester son JS, c'est possible !

POURTANT...En pratiquant le TDD, je ne suis jamais à plus

de 5 minutes d'un code totalementfonctionnel !

Page 6: Tester son JS, c'est possible !

AVANTAGESVérification permanente du bon fonctionnement du codeDocumentation toujours à jourConfiance dans son refactoMoins de temps passé à débuggerCode mieux conçu

Page 7: Tester son JS, c'est possible !

QUE VOULONS-NOUS ?Exécution rapide !Exécution dans un environnement réalisteSimulation des évènements utilisateur

Page 8: Tester son JS, c'est possible !

LES (VIEUX) OUTILS

JsUnitYUI TestDojo...

Page 9: Tester son JS, c'est possible !

QUNIT

Page 10: Tester son JS, c'est possible !

TEST FIRSTtest("On peut créer une vue", function() { var view = new App.ContactsView('#contacts');

ok(view);});

Page 11: Tester son JS, c'est possible !

ON IMPLÉMENTEwindow.App = {};

App.ContactsView = function(selector) { this.elt = $(selector);};

Page 12: Tester son JS, c'est possible !

ON TESTEtest("On peut ajouter un contact", function() { var view = new App.ContactsView('#contacts'); view.addContact('John Doe');

ok($('#contacts').html().match('John Doe'));});

Page 13: Tester son JS, c'est possible !

ON IMPLÉMENTEApp.ContactsView.prototype.addContact = function(name) { this.elt.append('<li>' + name + '</li>');};

Page 14: Tester son JS, c'est possible !

FACILE, NON ?MAIS IL Y A D'AUTRES OPTIONS...

Page 15: Tester son JS, c'est possible !

JASMINEdescribe("A suite", function() { it("contains spec with an expectation", function() { expect(true).toBe(true); });});

Page 16: Tester son JS, c'est possible !

MOCHAdescribe('Contacts service', function() { it('should persists all user\'s searches', function() { ... });

describe('search', function() { beforeEach(function() { ... });

it('...', function() { ... }); });});

Page 17: Tester son JS, c'est possible !

MON TEST ÉCHOUE...POURQUOI ?!?

test("On peut chercher des tweets", function() { App.Contacts.search('John Doe', function(data) { ok(data.results); });});

App.Contacts = { search: function(name, callback) { $.ajax('http://monapp.com/search', { data: { q: name }, dataType: 'jsonp', success: callback }); }}

Page 18: Tester son JS, c'est possible !

TESTER DE L'ASYNCHRONEAVEC QUNIT

asyncTest("On peut chercher des contacts", function() { expect(1); App.Contacts.search('John Doe', function(data) { ok(data.results); start(); });});

Page 19: Tester son JS, c'est possible !

TESTER DE L'ASYNCHRONEAVEC MOCHA

describe('Contacts service', function() { it('should search', function(done) { App.Contacts.search('John Doe', 5, function(data) { data.should.have.property('results').with.lengthOf(5); done(); }); });});

Page 20: Tester son JS, c'est possible !

MOCK ET XHRAVEC MOCKJAX

$.mockjax({ url: '/search', contentType: 'text/json', responseTime: 750, responseText: { results: [...] }});

Page 21: Tester son JS, c'est possible !

MOCK ET XHRAVEC SINON.JS

after(function () { jQuery.ajax.restore();});

it('should search', function () { sinon.stub(jQuery, "ajax"); App.Contacts.search('John Doe', sinon.spy());

assert(jQuery.ajax.calledWithMatch({ url: 'monapp.com' }));});

Page 22: Tester son JS, c'est possible !

SINON.JSSPIES

it("should call subscribers on publish", function () { var callback = sinon.spy(); PubSub.subscribe("message", callback); PubSub.publishSync("message"); assertTrue(callback.called);});

Page 23: Tester son JS, c'est possible !

SINON.JSSTUBS

it("should call all subscribers, even if there are exceptions", function() { var message = 'an example message'; var error = 'an example error message'; var stub = sinon.stub().throws(); var spy1 = sinon.spy(), spy2 = sinon.spy();

PubSub.subscribe(message, stub); PubSub.subscribe(message, spy1); PubSub.subscribe(message, spy2); PubSub.publishSync(message);

assert(spy1.called); assert(spy2.called); assert(stub.calledBefore(spy1));});

Page 24: Tester son JS, c'est possible !

SINON.JSMOCKS

it("should call all subscribers when exceptions", function () { var myAPI = { method: function () {} };

var spy = sinon.spy(); var mock = sinon.mock(myAPI); mock.expects("method").once().throws();

PubSub.subscribe("message", myAPI.method); PubSub.subscribe("message", spy); PubSub.publishSync("message", undefined);

mock.verify(); assert(spy.calledOnce);});

Page 25: Tester son JS, c'est possible !

CHAI.JSchai.should();

foo.should.be.a('string');foo.should.equal('bar');foo.should.have.length(3);tea.should.have.property('flavors') .with.length(3);

Page 26: Tester son JS, c'est possible !

CHAI.JSvar expect = chai.expect;

expect(foo).to.be.a('string');expect(foo).to.equal('bar');expect(foo).to.have.length(3);expect(tea).to.have.property('flavors') .with.length(3);

Page 27: Tester son JS, c'est possible !

CHAI.JSvar assert = chai.assert;

assert.typeOf(foo, 'string');assert.equal(foo, 'bar');assert.lengthOf(foo, 3)assert.property(tea, 'favors');assert.lengthOf(tea.flavors, 3);

Page 28: Tester son JS, c'est possible !

UI TESTINGDECLENCHER DES EVENTS D'UI

function simulateClick(selector) { try { var event = document.createEvent("MouseEvent"); event.initEvent('click', true, true); document.querySelector(selector).dispatchEvent(event); } catch (e) { throw new Error('Can\'t click on element: ' + selector, e); }}

Page 29: Tester son JS, c'est possible !

UI TESTINGDECLENCHER DES EVENTS D'UI #2function simulateClick(selector) { try { var event = new MouseEvent('click', { 'view': window, 'bubbles': true, 'cancelable': true }); document.querySelector(selector).dispatchEvent(event); } catch (e) { throw new Error('Can\'t click on element: ' + selector, e); }}

Page 30: Tester son JS, c'est possible !

UI TESTINGDECLENCHER DES EVENTS D'UI #3function simulateKeyAction(selector, action, chromeCode, ffCode, shiftKeyArg) { try { var event = document.createEvent("KeyboardEvent"); if (event.initKeyboardEvent) { // Chrome, IE event.initKeyboardEvent(action, true, true, document.defaultView, chromeCode, 0, "", false, ""); } else { // FF event.initKeyEvent(action, true, true, document.defaultView, false, false, shiftKeyArg, false, ffCode, 0); } document.querySelector(selector).dispatchEvent(event); } catch (e) { throw new Error('Can\'t ' + action + ' on element: ' + selector, e); }}

Page 31: Tester son JS, c'est possible !

CROSS-BROWSER TESTINGJsTestDriverTestemDalekJS

Testacular

Page 32: Tester son JS, c'est possible !

KARMAraphael@eagle:~/Code/syrahjs$ karma start syrah.conf.js INFO [karma]: Karma server started at http://localhost:9876/INFO [launcher]: Starting browser FirefoxINFO [launcher]: Starting browser ChromeINFO [Chrome 24.0 (Linux)]: Connected on socket id O6e8d00_L-soVDkx_e4GINFO [Firefox 19.0 (Linux)]: Connected on socket id jmBng24HVrIC8xBX_e4HChrome 24.0 (Linux): Executed 58 of 58 SUCCESS (3.189 secs / 2.986 secs)Firefox 19.0 (Linux): Executed 58 of 58 SUCCESS (3.288 secs / 3.039 secs)TOTAL: 116 SUCCESS

Page 33: Tester son JS, c'est possible !

SI VOUS UTILISEZ UNFRAMEWORK

Ne testez pas votre framework !Unit-testez vos propres libsUnit-testez l'intégration avec des libs "3rd-party"Ecrivez des tests d'intégration

Page 34: Tester son JS, c'est possible !

TESTS D'INTÉGRATIONSelenium / WebDriverWindmillPhantomJS

Page 35: Tester son JS, c'est possible !

CASPERcasper.start('http://localhost/sample.html', function() { this.fill('#search-form', {search: 'test'}, true);});casper.then(function() { this.waitFor(function() { return this.evaluate(function() { return document.querySelectorAll('#results li').length > 0; }); }, function then() { this.test.assertTextExists('test'); this.captureSelector('tweets.png', '#results'); });});casper.run(function() { this.test.renderResults(true, 0, 'test-result/tweets.xml');});

Page 36: Tester son JS, c'est possible !

PAGE/ZONE OBJECTSExpose les "services" rendus par une page/zoneEncapsule la structure HTMLNe doivent pas porter la responsabilité des assertions

Page 37: Tester son JS, c'est possible !

PAGE/ZONE OBJECTSvar TwitterSearchPage = function(casper) { this.casper = casper; this.searchForm = '#search-form'; this.results = '#results'; this.tweets = this.results + ' li';};TwitterSearchPage.prototype.search = function(term) { this.casper.fill(this.searchForm, {search: term}, true);};TwitterSearchPage.prototype.waitForResults = function(then) { var tweetsSelector = this.tweets; return this.waitFor(function() { return this.evaluate(function(tweetsSelector) { return document.querySelectorAll(tweetsSelector).length > 0; }, tweetsSelector); }, then);};

Page 38: Tester son JS, c'est possible !

PAGE/ZONE OBJECTScasper.start('http://localhost/sample.html');

casper.then(function() { var page = new TwitterSearchPage(this); page.search('test'); page.waitForResults(function then() { this.test.assertTextExists('test'); });});

casper.run(function() { this.test.renderResults(true, 0, 'test-result/tweets.xml');});

Page 39: Tester son JS, c'est possible !

MERCI DE VOTREATTENTION !DES QUESTIONS ?