Date post: | 27-Jun-2015 |
Category: |
Technology |
Upload: | goldoraf |
View: | 2,489 times |
Download: | 5 times |
TDDDÉVELOPPEMENT DIRIGÉ PAR LES
TESTS
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
RÉACTIONS TYPIQUESC'est stupide !
On perd du temps à écrire des tests !
Cela perturbe mon flux !
POURTANT...En pratiquant le TDD, je ne suis jamais à plus
de 5 minutes d'un code totalementfonctionnel !
AVANTAGESVérification permanente du bon fonctionnement du codeDocumentation toujours à jourConfiance dans son refactoMoins de temps passé à débuggerCode mieux conçu
QUE VOULONS-NOUS ?Exécution rapide !Exécution dans un environnement réalisteSimulation des évènements utilisateur
LES (VIEUX) OUTILS
JsUnitYUI TestDojo...
QUNIT
TEST FIRSTtest("On peut créer une vue", function() { var view = new App.ContactsView('#contacts');
ok(view);});
ON IMPLÉMENTEwindow.App = {};
App.ContactsView = function(selector) { this.elt = $(selector);};
ON TESTEtest("On peut ajouter un contact", function() { var view = new App.ContactsView('#contacts'); view.addContact('John Doe');
ok($('#contacts').html().match('John Doe'));});
ON IMPLÉMENTEApp.ContactsView.prototype.addContact = function(name) { this.elt.append('<li>' + name + '</li>');};
FACILE, NON ?MAIS IL Y A D'AUTRES OPTIONS...
JASMINEdescribe("A suite", function() { it("contains spec with an expectation", function() { expect(true).toBe(true); });});
MOCHAdescribe('Contacts service', function() { it('should persists all user\'s searches', function() { ... });
describe('search', function() { beforeEach(function() { ... });
it('...', function() { ... }); });});
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 }); }}
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(); });});
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(); }); });});
MOCK ET XHRAVEC MOCKJAX
$.mockjax({ url: '/search', contentType: 'text/json', responseTime: 750, responseText: { results: [...] }});
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' }));});
SINON.JSSPIES
it("should call subscribers on publish", function () { var callback = sinon.spy(); PubSub.subscribe("message", callback); PubSub.publishSync("message"); assertTrue(callback.called);});
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));});
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);});
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);
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);
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);
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); }}
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); }}
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); }}
CROSS-BROWSER TESTINGJsTestDriverTestemDalekJS
Testacular
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
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
TESTS D'INTÉGRATIONSelenium / WebDriverWindmillPhantomJS
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/ZONE OBJECTSExpose les "services" rendus par une page/zoneEncapsule la structure HTMLNe doivent pas porter la responsabilité des assertions
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/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');});
MERCI DE VOTREATTENTION !DES QUESTIONS ?