Ember.js testen

Toen ik bijna een jaar geleden met Ember.js begon te spelen, liet het testbaarheidsverhaal te wensen over. U kunt een object probleemloos testen, maar een unit-test is maar één manier om feedback te krijgen wanneer u een softwareproduct bouwt. Naast unit tests, wilde ik een manier om de integratie van meerdere componenten te verifiëren. Dus zoals de meeste mensen die rijke JavaScript-applicaties testen, bereikte ik de moeder van alle testtools, Selenium.

Nu, voordat ik het sla, zonder een goede introductie, is het de moeite waard te vermelden dat Selenium een ​​geweldige manier is om te verifiëren dat uw hele webapplicatie werkt met een volledige productieachterloopdatabase en al uw productie-afhankelijkheden, enz. En vanuit een QA-perspectief, deze tool kan een geweldige hulpbron zijn voor teams die behoefte hebben aan end-to-end UI-acceptatietests.

Maar na verloop van tijd kan een ogenschijnlijk kleine testsuite gebouwd op Selenium de snelheid van uw team naar een slakkengang slepen. Een eenvoudige manier om deze pijn te verminderen is om te voorkomen dat er in de eerste plaats een grote applicatie wordt gebouwd. Als u in plaats daarvan een handvol kleinere webtoepassingen bouwt, kan dit u misschien wat langer in de lucht houden omdat geen enkele individuele build het team zal verpletteren, terwijl u groeit.

Maar zelfs bij een klein project is het echte probleem met Selenium dat het geen deel uitmaakt van het testgestuurde ontwikkelingsproces. Wanneer ik rood / groen / refactor doe, heb ik geen tijd voor langzame feedback in welke vorm dan ook. Ik had een manier nodig om zowel unit- als integratietests te schrijven die snel feedback zouden geven om me te helpen de software die ik aan het schrijven was op een meer iteratieve manier vorm te geven. Als je een versie van Ember.js> = RC3 gebruikt, heb je geluk want het schrijven van een eenheid of integratietest is een wandeling in het deel.


De testrunner installeren

Nu we JavaScript-tests voor onze applicatie kunnen schrijven, hoe kunnen we deze uitvoeren? De meeste ontwikkelaars gebruiken de browser rechtstreeks, maar omdat ik iets wilde dat ik zonder hoofd kon uitvoeren vanaf de opdrachtregel in een CI-omgeving met een rijk ecosysteem vol plug-ins, keek ik naar Karma.

Wat ik leuk vond aan Karma, is dat het alleen je testrunner wil zijn. Het maakt niet uit wat voor JavaScript-testraamwerk u gebruikt of welk client-side MVC-framework u gebruikt. Het is eenvoudig om aan de slag te gaan en het schrijven van tests die worden uitgevoerd tegen uw productie-app Ember.js is slechts een paar regels configuratie.

Maar voordat we Karma kunnen configureren, moeten we het met npm installeren. Ik raad aan om het lokaal te installeren, zodat je je npm-modules per project kunt isoleren. Hiertoe voegt u een bestand met de naam package.json'naar de root van je project dat er ongeveer zo uitziet als het onderstaande.

 "afhankelijkheden": "karma-qunit": "*", "karma": "0.10.2"

Voor dit voorbeeld is zowel Karma als een plug-in voor QUnit vereist. Nadat u de package.json bestand hierboven, ga terug naar de opdrachtregel en typ npm installeren om de vereiste knoopmodules naar beneden te trekken.

Nadat de npm-installatie is voltooid, ziet u nu een nieuwe map met de naam node_modules in de root van uw project. Deze map bevat alle JavaScript-code die we zojuist met npm hebben afgesloten, inclusief Karma en de QUnit-plug-in. Als je nog verder naar beneden bladert node_modules / karma / bin / je zult het Karma-programma zien. We zullen dit gebruiken om de testrunner te configureren, tests uit te voeren vanaf de opdrachtregel, enz.


Configureer de Test Runner

Vervolgens moeten we karma configureren zodat het weet hoe de QUnit-tests moeten worden uitgevoerd. Type karma init vanuit de root van het project. U wordt gevraagd om een ​​lijst met vragen. De eerste zal vragen welk testraamwerk je wilt gebruiken, druk op tab tot je het ziet qunit, dan raken invoeren. Volgend antwoord Nee op de Require.js-vraag, omdat we deze voor deze voorbeeldtoepassing niet zullen gebruiken. tab tot je het ziet PhantomJS voor de derde vraag en je zult moeten slaan invoeren twee keer omdat hier meerdere opties zijn toegestaan. Wat de rest betreft, laat ze gewoon op hun standaardoptie.

Als je klaar bent, zou je moeten zien dat Karma een configuratiebestand met de naam heeft gegenereerd karma.conf.js in de root of uw project. Als u meer wilt weten over de verschillende opties die Karma ondersteunt, vindt u de opmerkingen wellicht nuttig. Omwille van dit voorbeeld heb ik een vereenvoudigde versie van het configuratiebestand om dingen beginnersvriendelijk te houden.

Als u verder wilt gaan, verwijdert u het gegenereerde configuratiebestand en vervangt u dit door dit.

 module.exports = functie (karma) karma.set (basePath: 'js', bestanden: ["vendor / jQuery / jquery.min.js", "vendor / stuur / handlebars.js", "vendor / ember / ember.js "," vendor / jquery-mockjax / jquery.mockjax.js "," app.js "," tests / *. js "], logLevel: karma.LOG_ERROR, browsers: ['PhantomJS'], singleRun: waar, autoWatch: false, frameworks: ["qunit"]); ;

Dit zou redelijk moeten lijken op wat Karma eerder had gegenereerd, ik heb zojuist alle opmerkingen verwijderd en een paar opties weggelaten waar we op dit moment niets om geven. Om de eerste unit-test te schrijven, moest ik Karma wat meer vertellen over de projectstructuur.

Bovenaan het configuratiebestand ziet u dat ik het basePath naar js omdat alle JavaScript-elementen onder deze map in het project leven. Vervolgens vertelde ik Karma waar het de JavaScript-bestanden kan vinden die nodig zijn om onze eenvoudige applicatie te testen. Dit omvat jQuery, Handlebars, Ember.js en de app.js bestand zelf.


De eerste eenheids test schrijven

Nu kunnen we het eerste unit-testbestand aan het project toevoegen. Maak eerst een nieuwe map met de naam testen en nest het onder de js map. Voeg een bestand toe in deze nieuwe map met de naam unit_tests.js dat lijkt zoiets als dit.

 test ('hallo wereld', functie () gelijk (1, 1, ""););

Deze test doet nog niets waardevols, maar het zal ons helpen te verifiëren dat we alles wat met Karma is bedraad hebben om het correct uit te voeren. Merk op in het karma bestanden sectie, we hebben het al toegevoegd js / testen directory. Op deze manier zal Karma alle JavaScript-bestanden gebruiken die we gebruiken om onze applicatie te testen, in de toekomst.

Nu we Karma correct hebben geconfigureerd, voert u de qunit-tests uit vanaf de opdrachtregel met ./ node_modules / karma / bin / karma start.

Als je alles correct hebt ingesteld, zou je moeten zien dat Karma één test uitvoert en succesvol is. Om te verifiëren dat het de test heeft uitgevoerd die we net hebben geschreven, laat je het falen door de gelijkwaardigheidsverklaring te wijzigen. U kunt bijvoorbeeld het volgende doen:

 test ('hallo wereld', functie () gelijk (1, 2, "boom"););

Als je dit kunt laten mislukken en het opnieuw laten passeren, is het tijd om een ​​test te schrijven met een iets meer doel.


De voorbeeldtoepassing

Maar voordat we aan de slag gaan, bespreken we de voorbeeldtoepassing die in dit bericht is gebruikt. In de onderstaande schermafbeelding ziet u dat we een heel eenvoudig raster van gebruikers hebben. In de HTML-tabel wordt elke gebruiker weergegeven op voornaam samen met een knop om die gebruiker te verwijderen. Bovenaan de toepassing ziet u een invoer voor de voornaam, achternaam en ten slotte een knop die een andere gebruiker aan de tabel zal toevoegen wanneer erop wordt geklikt.

https://dl.dropboxusercontent.com/u/716525/content/images/2013/pre-tuts.png

De voorbeeldtoepassing heeft drie problemen. Eerst willen we de voor- en achternaam van de gebruiker laten zien, niet alleen de voornaam. Als u vervolgens op een verwijderknop klikt, wordt de gebruiker niet daadwerkelijk verwijderd. En ten slotte, wanneer u een voornaam en achternaam toevoegt en op Toevoegen klikt, wordt er geen andere gebruiker in de tabel geplaatst.

Aan de oppervlakte lijkt de volledige naamswijziging de eenvoudigste. Het bleek ook een geweldig voorbeeld dat laat zien wanneer je een eenheidscontrole, een integratietest of beide moet schrijven. In dit voorbeeld is de snelste manier om feedback te krijgen, het schrijven van een eenvoudige eenheidscontrole die beweert dat het model een berekende eigenschap heeft voor-en achternaam.


Eenheid die de berekende eigenschap test

Het testen van een object in een eenheid is eenvoudig, u maakt eenvoudig een nieuwe instantie van het object en vraagt ​​naar de voor-en achternaam waarde.

 test ('eigenschap fullName retourneert zowel eerste als laatste', functie () var person = App.Person.create (firstName: 'toran', lastName: 'billups'); var result = person.get ('fullName' ); equal (result, 'toran billups', "fullName was" + result););

Vervolgens als u teruggaat naar de opdrachtregel en uitvoert ./ node_modules / karma / bin / karma start, het zou één mislukte test moeten laten zien met een nuttige beschrijving van het bericht voor-en achternaam zo ongedefinieerd op dit moment. Om dit op te lossen, moeten we de. Openen app.js bestand en voeg een berekende eigenschap toe aan het model dat een reeks van de gecombineerde voor- en achternaamwaarden retourneert.

 App.Person = Ember.Object.extend (firstName: ", lastName:", fullName: function () var firstName = this.get ('firstName'); var lastName = this.get ('lastName'); ga terug firstName + "+ lastName; .property ());

Als u teruggaat naar de opdrachtregel en uitvoert ./ node_modules / karma / bin / karma start u zou nu een test van de passerende eenheid moeten zien. U kunt dit voorbeeld uitbreiden door een paar andere eenheidscontroles uit te voeren om aan te geven dat de berekende eigenschap moet veranderen wanneer de voor- of achternaam wordt bijgewerkt op het model.

 test ('eigenschap fullName retourneert zowel eerste als laatste', functie () var person = App.Person.create (firstName: 'toran', lastName: 'billups'); var result = person.get ('fullName' ); equal (result, 'toran billups', "fullName was" + result);); test ('fullName-eigenschapsupdates wanneer firstName is gewijzigd', function () var person = App.Person.create (firstName: 'toran', lastName: 'billups'); var result = person.get ('fullName' ); equal (resultaat, 'toran billups', 'fullName was' + resultaat); person.set ('firstName', 'wat'); result = person.get ('fullName'); equal (resultaat, 'wat-facturen ', "fullName was" + result);); test ('fullName-eigenschapsupdates wanneer achternaam is gewijzigd', function () var person = App.Person.create (firstName: 'toran', lastName: 'billups'); var result = person.get ('fullName' ); equal (resultaat, 'toran billups', 'fullName was' + result); person.set ('lastName', 'tbozz'); result = person.get ('fullName'); equal (resultaat, 'toran tbozz ', "fullName was" + result););

Als u deze twee extra tests toevoegt en alle drie uitvoert vanaf de opdrachtregel, moet u twee falen. Als u alle drie tests wilt laten slagen, past u de berekende eigenschap aan om te luisteren naar wijzigingen in zowel de voornaam als de achternaam. Nu, als je rent ./ node_modules / karma / bin / karma start van de commandoregel, zou u drie voorbijgaande tests moeten hebben.

 App.Person = Ember.Object.extend (firstName: ", lastName:", fullName: function () var firstName = this.get ('firstName'); var lastName = this.get ('lastName'); ga terug firstName + "+ lastName; .property ('firstName', 'lastName'));

Voeg de Karma-Ember-Preprocessor toe en configureer deze

Nu we een berekende eigenschap in het model hebben, moeten we de sjabloon zelf bekijken omdat we momenteel de nieuwe niet gebruiken voor-en achternaam eigendom. In het verleden moest u alles zelf bedraad maken of Selenium gebruiken om te controleren of de sjabloon correct werd weergegeven. Maar met ember-testen kun je nu integratie testen door een paar regels JavaScript en een plug-in voor Karma toe te voegen.

Open eerst de package.json bestand en voeg de karma-ember-preprocessor-afhankelijkheid toe. Nadat u de update hebt uitgevoerd package.json bestand, doen npm installeren vanaf de opdrachtregel om dit naar beneden te trekken.

 "afhankelijkheden": "karma-ember-preprocessor": "*", "karma-qunit": "*", "karma": "0.10.2"

Nu de pre-processor is geïnstalleerd, moeten we Karma bewust maken van de sjabloonbestanden. In de bestanden gedeelte van uw karma.conf.js bestand voeg het volgende toe om Karma te vertellen over de Stuur-sjablonen.

 module.exports = functie (karma) karma.set (basePath: 'js', bestanden: ["vendor / jQuery / jquery.min.js", "vendor / stuur / handlebars.js", "vendor / ember / ember.js "," vendor / jquery-mockjax / jquery.mockjax.js "," app.js "," tests / *. js "," templates / *. handlebars "], logLevel: karma.LOG_ERROR, browsers: ['PhantomJS'], singleRun: true, autoWatch: false, frameworks: ["qunit"]); ;

Vervolgens moeten we Karma vertellen wat te doen met deze stuurbestanden, omdat we technisch willen dat elke sjabloon wordt geprecompileerd voordat deze wordt overgedragen aan PhantomJS. Voeg de preprocessor-configuratie toe en wijs alles aan met de extensie * .handlebars in de preprocessor van de lamp. Ook moet je de plug-insconfiguratie toevoegen om de ember pre-processor te registreren (samen met een paar andere die normaal worden opgenomen in de standaardconfiguratie van Karma).

 module.exports = functie (karma) karma.set (basePath: 'js', bestanden: ["vendor / jQuery / jquery.min.js", "vendor / stuur / handlebars.js", "vendor / ember / ember.js "," vendor / jquery-mockjax / jquery.mockjax.js "," app.js "," tests / *. js "," templates / *. handlebars "], logLevel: karma.LOG_ERROR, browsers: ['PhantomJS'], singleRun: true, autoWatch: false, frameworks: ["qunit"], plug-ins: ['karma-qunit', 'karma-chrome-launcher', 'karma-ember-preprocessor', 'karma- phantomjs-launcher '], preprocessors: "** / *. handlebars":' ember '); ;

Integratie Het testen van de Data-gebonden sjabloon

Nu we de configuratie van de Karma-configuratie voor integratietests hebben, voegt u een nieuw bestand met de naam integration_tests.js onder de testen map. Binnen deze map moeten we een eenvoudige test toevoegen om te bewijzen dat we de hele Ember.js-applicatie zonder fouten kunnen doorstaan. Voeg een eenvoudige qunit-test toe om te zien of we de kunnen raken '/' route en krijg de standaard HTML geretourneerd. Voor de eerste test beweren we alleen dat de tafel tag bestaat in de gegenereerde HTML.

 test ('Hello World', function () App.reset (); bezoek ("/"). then (function () ok (exists ("table"));););

Merk op dat we een paar helpers gebruiken die zijn ingebouwd in het testen van sintels bezoek en vind. De bezoek helper is een embersvriendelijke manier om de toepassing te vertellen tijdens de uitvoering te zijn. Deze test begint bij de '/' route omdat daar de People-modellen aan de sjabloon worden gebonden en onze HTML-tabel wordt gegenereerd. De vind helper is een snelle manier om elementen op te zoeken in de DOM met behulp van CSS-selectors zoals u met jQuery zou doen om iets te verifiëren over de markup.

Voordat we deze test kunnen uitvoeren, moeten we een testhelperbestand toevoegen waarmee de testhelpers worden geïnjecteerd en een generiek wortelelement wordt ingesteld. Voeg de onderstaande code toe aan een bestand met de naam integration_test_helper.js in hetzelfde testen directory. Dit zorgt ervoor dat onze applicatie de testhelpers heeft tijdens uitvoeringstijd.

 document.write ('
'); App.rootElement = '# ember-testen'; App.setupForTesting (); App.injectTestHelpers (); function exists (selector) return !! find (selector) .length;

Nu moet u vanaf de opdrachtregel de integratietest hierboven kunnen uitvoeren. Als u een slaagtest hebt, verwijdert u de tafel uit het stuursjabloon om deze te laten mislukken (alleen om te helpen bewijzen dat Ember de HTML genereerde met die sjabloon).

Nu we de integratie testen hebben ingesteld, is het tijd om degene te schrijven die beweert dat we elke gebruiker laten zien voor-en achternaam in plaats van hun Voornaam. We willen eerst beweren dat we twee rijen krijgen, één voor elke persoon.

 test ('hallo wereld', functie () App.reset (); bezoek ("/"). then (function () var rows = find ("table tr"). length; equal (rijen, 2, rijen );););

Opmerking: de applicatie retourneert momenteel hard gecodeerde gegevens om alles eenvoudig te houden op dit moment. Als je nieuwsgierig bent waarom we twee mensen krijgen, is dit de vind methode op het model:

 App.Person.reopenClass (people: [], find: function () var first = App.Person.create (firstName: 'x', lastName: 'y'); var last = App.Person.create (firstName: 'x', lastName: 'y'); this.people.pushObject (first); this.people.pushObject (last); return this.people;);

Als we de tests nu uitvoeren, moeten we nog steeds alles laten slagen omdat twee mensen worden teruggestuurd zoals we zouden verwachten. Vervolgens moeten we de tabelcel krijgen die de naam van de persoon laat zien en beweren dat hij de naam gebruikt voor-en achternaam eigendom in plaats van gewoon Voornaam.

 test ('hallo wereld', functie () App.reset (); bezoek ("/"). then (function () var rows = find ("table tr"). length; equal (rijen, 2, rijen ); var fullName = find ("table tr: eq (0) td: eq (0)"). text (); equal (fullName, "xy", "de eerste rij van de tabel had fullName:" + fullName); ););

Als u de bovenstaande test uitvoert, ziet u een fouttest omdat we de sjabloon nog niet hebben bijgewerkt voor-en achternaam. Nu we een fouttest hebben, werkt u de sjabloon bij die moet worden gebruikt voor-en achternaam en voer de tests uit met ./ node_modules / karma / bin / karma start. U zou nu een reeks van zowel unit- als integratietests moeten doorlopen.


Moet ik eenheids- of integratietests schrijven?

Als je jezelf de vraag stelt: "Wanneer moet ik een eenheidstoets schrijven versus een integratietest?", Is het antwoord eenvoudig: wat zal minder pijnlijk zijn? Als het schrijven van een eenheidscontrole sneller gaat en het probleem beter wordt verklaard dan een veel grotere integratietest, dan zeg ik de unit-test. Als de unit-testen minder waardevol lijken omdat je basis CRUD doet en het echte gedrag zit in de interactie tussen componenten, dan zeg ik dat ik de integratietest schrijf. Omdat de integratietests geschreven met ember-test razendsnel zijn, maken ze deel uit van de feedbackcyclus van de ontwikkelaar en moeten ze op dezelfde manier worden gebruikt als een eenheidscontrole als het zinvol is.

Om een ​​CRUD-achtige integratietest in actie te tonen, schrijft u de volgende test om de. Te bewijzen toevoegen knop plaatst de persoon in de verzameling en dat een nieuwe rij wordt weergegeven in het stuursjabloon.

 test ('add zal een andere persoon aan de html-tabel toevoegen', function () App.Person.people = []; App.reset (); bezoek ("/"). then (function () var rows = find ("tabel tr"). lengte gelijk (rijen, 2, "de tabel had" + rijen + "rijen"); fillIn (". firstName", "foo"); fillIn (". lastName", "bar") ; return click (". submit");). then (function () equal (find ("table tr"). length, 3, "the table of people was not complete"); equal (find ("tafel tr: eq (2) td: eq (0) "). text ()," foo bar "," the fullName for the person was incorrect ");););

Begin met de test te vertellen met welke status je wilt werken en gebruik dan de Vul in hulp, voeg een voornaam en achternaam toe. Nu als u op klikt voorleggen knop moet het die persoon toevoegen aan de HTML-tabel, dus bij het terugzenden dan we kunnen stellen dat er drie mensen in de HTML-tabel staan. Voer deze test uit en het zou moeten mislukken omdat de Ember-controller niet compleet is.

Om de test te laten slagen, voeg je de volgende regel toe aan de PeopleController

 App.PeopleController = Ember.ArrayController.extend (actions: addPerson: function () var person = firstName: this.get ('firstName'), lastName: this.get ('lastName'); App.Person .add (persoon););

Nu als u de tests uitvoert met ./ node_modules / karma / bin / karma start het zou drie mensen moeten tonen in de gerenderde HTML.

De laatste test is de verwijdering. We zien dat we de knop voor een specifieke rij vinden en erop klikken. In de volgende dan we verifiëren gewoon dat er minder personen in de HTML-tabel worden weergegeven.

 test ('verwijderen zal de persoon voor een bepaalde rij verwijderen', function () App.Person.people = []; App.reset (); bezoek ("/"). then (function () var rows = find ("tabel tr") .lengte; gelijk (rijen, 2, "de tabel had" + rijen + "rijen"); klik terug ("tabel .verwijderen: eerst");) dan (functie () gelijk (find ("table tr"). length, 1, "the table of people was not complete););")))

Om dit te halen, voegt u gewoon de volgende regel toe aan de PeopleController:

 App.PeopleController = Ember.ArrayController.extend (actions: addPerson: function () var person = firstName: this.get ('firstName'), lastName: this.get ('lastName'); App.Person .add (persoon);, deletePerson: function (person) App.Person.remove (person););

Voer de tests uit vanaf de opdrachtregel en u moet opnieuw een reeks tests uitvoeren.


Conclusie

Dus dat wikkelt onze voorbeeldtoepassing. Stel gerust vragen in de comments.

Bonus: maar ik gebruik Grunt al ...

Als u liever Grunt gebruikt in plaats van de karma-ember-preprocessor, hoeft u alleen de configuratie van plug-ins en preprocessors te verwijderen. Verwijder ook templates / *. stuur uit de bestandensectie omdat Karma de sjablonen niet hoeft voor te compileren. Hier is een vereenvoudigd karma.conf.js dat werkt als je grunt gebruikt om de sjablonen van het stuur te precompileren.

 module.exports = functie (karma) karma.set (basePath: 'js', bestanden: ["lib / deps.min.js", // gebouwd door uw grunttaak "tests / *. js"], logLevel : karma.LOG_ERROR, browsers: ['PhantomJS'], singleRun: true, autoWatch: false, frameworks: ["qunit"]); ;

En dat is het!