BDD I JavaScript Med CucumberJS

0

Av Todd Anderson

jeg har tidligere skrevet om testdrevet utvikling (tdd)I JavaScript, spesielt ved hjelp av behavior driven development (BDD) style library Jasmine i en serie om å bygge En Testdrevet Dagligvareliste Søknad. I den innleggsserien gikk jeg gjennom Å tenke På Brukerhistorier For Funksjoner og Scenarier som faktiske utviklingsoppgaver, og – leser tilbake på det-det er alt veldig grønt (ingen ordspill ment) så langt jeg finner en måte å levere testdrevet kode på. Det er ikke noe galt med det, og jeg vil mest sannsynlig se på dette og påfølgende innlegg på samme måte. Med det sagt, jeg fortsatt holder sant AT TDD er den beste måten å levere konsis, testet og gjennomtenkt kode.

siden den gang har jeg imidlertid innlemmet et annet verktøy i MIN tdd-arbeidsflyt for JavaScript-baserte prosjekter som gir meg integrering av funksjonsspesifikasjoner nærmere min utvikling Og virkelig omfatter mitt nåværende ideal For Atferdsdrevet Utvikling: CucumberJS. I hovedsak tillater det meg å virkelig følge TDD mens jeg utvikler fra utsiden i løpende automatiserte tester som mislykkes til jeg har skrevet kode som støtter en funksjon.

Forutsetninger Og Notater

for eksemplene i dette innlegget antas det at Du er kjent Med NodeJS, npm, utvikler Nodemoduler og felles enhetstestpraksis da disse emnene er for store til å diskutere i dette innlegget.

Støttefiler relatert til dette emnet vil være tilgjengelige på:
https://github.com/bustardcelly/cucumberjs-examples

CucumberJS

CucumberJS Er En JavaScript-port av det populære bdd-verktøyet Cucumber (som i seg selv var En omskrivning Av RSpec). Den lar Deg definere Funksjonsspesifikasjoner i Et Domenespesifikt Språk (Dsl) – kalt Gherkin-og kjøre spesifikasjonene dine ved hjelp av et kommandolinjeverktøy som vil rapportere bestått og/eller sviktende scenarier og trinnene de består av.

Det er viktig å merke Seg At Agurk selv ikke gir et standard påstandsbibliotek. Det er et testrammeverk som gir et kommandolinjeverktøy som bruker definerte Funksjoner og validerer Scenarier ved å kjøre Trinn som er skrevet I JavaScript. Det er utviklerne valg å inkludere ønsket påstand biblioteket brukes for å gjøre disse trinnene passere eller mislykkes. Det er min hensikt å klargjøre prosessen ved eksempel gjennom en Enkelt Funksjon med flere Scenarier i dette innlegget.

Installasjon

du kan installere CucumberJS i prosjektet ditt ved hjelp av npm:

$ npm install cucumber --save-dev

Gherkin

hvis du hadde fulgt med i den forrige Tdd-Serien, finner du spesifikasjonene definert i den serien som Ligner På Gherkin. Faktisk vil jeg re-hashing en funksjonsspesifikasjon fra den serien for å demonstrere å jobbe gjennom din første cuke (aka, passing feature spec).

hvis Vi skulle remake Handleliste søknad UNDER TDD / BDD bruker Agurk, ville vi først starte med en funksjon ved Hjelp Av Gherkin syntaks:

/ features / add-item.funksjon

Feature: Shopper can add an item to their Grocery List As a grocery shopper I want to add an item to my grocery list So that I can remember to buy that item at the grocery store Scenario: Item added to grocery list Given I have an empty grocery list When I add an item to the list Then The grocery list contains a single item Scenario: Item accessible from grocery list Given I have an empty grocery list When I add an item to the list Then I can access that item from the grocery list

Funksjonen definerer en forretningsverdi, Mens Scenariene definerer trinnene som gir denne verdien. Ofte, i programvareutviklingsverdenen, er det fra disse Scenariene at utviklingsoppgaver blir tatt på og QA-tester er definert.

jeg stoppet ved to Scenarier, men vi kan veldig enkelt legge til flere scenarier til denne funksjonen; umiddelbart det som kommer til å tenke er elementinnsettingsregler og validering av egenskaper som tillater at et element blir lagt til eller avvist. I ettertid kan det være mer fornuftig å lage separate funksjonsspesifikasjoner for slike detaljer. Vi kunne bruke et helt innlegg på slike emner skjønt, så la oss komme tilbake til funksjonen som allerede er definert.

Innenfor hvert Scenario er en liste over sekvensielle Trinn: Gitt, Når og Da. Det er disse trinnene Som CucumberJS vil utfore etter a ha konsumert denne funksjonen spec. Etter hver av dem, du kan eventuelt Ha Og Og Men, men – selv om nødvendig og uunngåelig til tider – jeg prøver å holde seg borte fra slike ekstra trinn klausuler.

Kjører den

etter å ha lagret det ned til en fil i en /features-katalog, kan vi kjøre den under Agurk:

$ node_modules/.bin/cucumber-js

Som standard vil CucumberJS forbruke alle funksjonsspesifikasjoner som finnes i relative /features-katalogen.

den nåværende konsollutgangen vil se omtrent slik ut som i hovedsak betyr at alle trinnene ikke er plassert eller definert:

UUUUUU2 scenarios (2 undefined)6 steps (6 undefined)You can implement step definitions for undefined steps with these snippets:this.Given(/^I have an empty grocery list$/, function(callback) { // express the regexp above with the code you wish you had callback.pending();});this.When(/^I add an item to the list$/, function(callback) { // express the regexp above with the code you wish you had callback.pending();});this.Then(/^The grocery list contains a single item$/, function(callback) { // express the regexp above with the code you wish you had callback.pending();});this.Then(/^I can access that item from the grocery list$/, function(callback) { // express the regexp above with the code you wish you had callback.pending();});

så vi har 6 udefinerte Trinn som utgjør 2 Scenarier, og CucumberJS ci-verktøyet gir til og med eksempler på hvordan du definerer dem!

en viktig del av den koden å forstå er at det bare er 4 trinn å implementere. I Vår Funksjon har vi 2 Scenerios hver med 3 Trinn. Det er totalt 6 trinn, men vi trenger bare å definere 4. Årsaken er at hvert Scenario deler samme Gitt Og når trinn; disse trenger bare å bli definert en gang og vil bli kjørt separat for hvert Scenario. I hovedsak, hvis du definerer lignende Trinn ved hjelp av samme kontekst, vil den gjenbruke «setup» for et Enkelt Trinn i hvert Scenario.

jeg bruker «setup» i anførselstegn fordi jeg mener det mer i rollen som å definere kontekst For når og deretter trinn.

jeg ønsker ikke å få det forvirret med oppsett / teardown metoder for andre enhet testing praksis-som er kjent Som før / Etter støtteoppgaver I CucumberJS – og bære mer av en kontekst for å sette opp et miljø der tester deretter utføres (for eksempel å fylle EN DB av brukere) og deretter rive ned som satt opp.

Trinndefinisjoner

i forrige avsnitt så vi at kjører CucumberJS mot Vår Add Item-Funksjon varslet oss om at vi har udefinerte (og, men ikke trykt i rødt, sviktende) scenarier for å støtte funksjonen. Som standard Leser CucumberJS i alle funksjoner fra / features-katalogen i forhold til hvor kommandoen ble kjørt, men den kunne ikke finne de støttede trinnfilene der disse metodene er definert.

Som nevnt tidligere gir CucumberJS ikke et påstandsbibliotek. Den eneste antakelsen på dette punktet – Siden CucumberJS-verktøyet kjøres Under NodeJS-er at de støttede trinnene vil bli lastet som nodemoduler med en eksportert funksjon som skal utføres. Når vi begynner å implementere trinnene, må vi bestemme på påstandsbiblioteket som skal brukes til å validere vår logikk. Vi legger den avgjørelsen på hyllen for øyeblikket og får barebones-oppsettet til å mislykkes.

For å starte, la oss ta de trinndefinisjonene som tilbys Av CucumberJS ci-verktøyet og slippe dem inn i en nodemodul:

/funksjoner/stepdefinitions / add-item.fremgangsmåte.js_

'use strict';module.exports = function() { this.Given(/^I have an empty grocery list$/, function(callback) { callback.pending(); }); this.When(/^I add an item to the list$/, function(callback) { callback.pending(); }); this.Then(/^The grocery list contains a single item$/, function(callback) { callback.pending(); }); this.Then(/^I can access that item from the grocery list$/, function(callback) { callback.pending(); });};

Som standard Vil CucumberJS se etter trinn som skal lastes inn i en mappe med tittelen step_definitions under / features-katalogen i forhold til hvor du utsteder kommandoen. Du kan eventuelt bruke alternativet -r for Å få CucumberJS lastetrinn fra et annet sted. Kjører standard er det samme som å sette følgende trinn definisjon katalog alternativ:

./node_modules/.bin/cucumber-js -r features/step_definitions

konsollutgangen vil nå se ut som følgende:

P--P--2 scenarios (2 pending)6 steps (2 pending, 4 skipped)

Ikke så overraskende å se som vi varsle tilbakeringing av en pending tilstand. CucumberJS går inn i forste trinn (Dato) og returneres umiddelbart med et ventende varsel. Som sådan bryder det seg ikke med å legge inn noen påfølgende trinn og markerer dem som hoppet over.

Merk: Det er for mye å komme inn i en diskusjon om klientmoduler og AMD vs CommonJS. I forbindelse med dette eksemplet vil jeg bruke CommonJS, som jeg min nåværende interesser ligger i å utnytte Browserify for klient-side utvikling. I lang tid var jeg en talsmann For RequireJS og AMD. Igjen, det er en helt annen diskusjon.

Gitt

for å komme nærmere grønt, tar vi Det Gitte trinnet først:

/funksjoner/stepdefinitions/add-item.trinn.js_

'use strict';var GroceryList = require(process.cwd() + '/script/model/grocery-list');module.exports = function() { var myList; this.Given(/^I have an empty grocery list$/, function(callback) { myList = GroceryList.create(); callback(); }); ...};

hvis vi skulle kjøre det igjen, ville vi få et unntak med en gang:

$ ./node_modules/.bin/cucumber-jsmodule.js:340 throw err; ^Error: Cannot find module './script/model/grocery-list' at Function.Module._resolveFilename (module.js:338:15) at Function.Module._load (module.js:280:25) at Module.require (module.js:364:17) at require (module.js:380:17) at Object.<anonymous> (/Users/toddanderson/Documents/workspace/custardbelly/cucumberjs-example/features/step_definitions/add-item.steps.js:3:19)

Som er forståelig siden vi ikke har definert noen annen kode, men denne trinndefinisjonsmodulen og prøver å kreve en modul som ikke eksisterer. I stikker MED TDD, dette er en god ting-vi vet hvorfor det er sviktende, og vi forventer det – jeg ville være å trekke håret mitt ut hvis det ikke kaste et unntak!

for å få dette til å passere, oppretter vi En Nodemodul i den angitte katalogen som eksporterer et objekt med en create metode:

/ script / model/grocery-list.js

'use strict';module.exports = { create: function() { return Object.create(null); }};

Vi har gitt det minste kravet for å få vårt Gitte skritt til å passere. Vi vil bekymre oss for detaljene når vi nærmer oss de siste trinnene.

Kjør det igjen, Og CucumberJS går Inn I Når-trinnet i hvert Scenario og avbryter på grunn av ventende retur.

$ ./node_modules/.bin/cucumber-js.P-.P-2 scenarios (2 pending)6 steps (2 pending, 2 skipped, 2 passed)

Når

i den forrige delen, for Å gjøre det Gitte trinnet pass på hvert Scenario, implementerte vi begynnelsen på En Handleliste modell generert fra en fabrikkmetode, create, fra modulen grocery-list. Jeg ønsker ikke å komme inn i en debatt om objektopprettelse, operatøren new, klasser og prototyper-i hvert fall ikke i dette innlegget-og vil anta at du er kjent og komfortabel (i hvert fall i å lese kode) Med Objekt.opprett definert For ECMAScript 5.

ved gjennomgang Av Når-trinnet For Scenariene:

When I add an item to the list

…vi må gi en måte å legge til et element i Dagligvarelisten som er opprettet i Det Gitte-og gjør det i så liten kode for å gjøre trinnet pass.

først definerer vi vår forventning om sminke og add signatur Av Dagligvarelisten i trinndefinisjonene:

/funksjoner/stepdefinitions / add-item.trinn.js_

...module.exports = function() { var myList, listItem = 'apple'; this.Given(/^I have an empty grocery list$/, function(callback) { myList = GroceryList.create(); callback(); }); this.When(/^I add an item to the list$/, function(callback) { myList.add(listItem); callback(); }); ...};

hvis vi kjører det igjen:

$ ./node_modules/.bin/cucumber-js.F-.F-(::) failed steps (::)TypeError: Object object has no method 'add'

oooo-weee! Nå snakker vi. Stor, lys rød F ‘ s. 🙂

for å få det til å komme tilbake til bestått, endrer vi grocery-list med så liten kode som mulig:

/skript/modell / dagligvare-liste.js

'use strict';var groceryList = { add: function(item) { // }};module.exports = { create: function() { return Object.create(groceryList); }};

Kjør igjen, Og CucumberJS har utviklet Seg Til de da trinnene som rapporterer en pending tilstand.

$ ./node_modules/.bin/cucumber-js..P..P2 scenarios (2 pending)6 steps (2 pending, 4 passed)

Deretter

vi utviklet oss gjennom trinnimplementeringene våre og har nådd trinnene der vi hevder operasjoner og egenskaper som viser at scenariet vårt gir den tiltenkte verdien. Som nevnt tidligere Gir CucumberJS ikke et påstandsbibliotek. Min preferanse i påstandsbiblioteker er en kombinasjon Av Chai, Sinon Og Sinon-Chai, men for eksemplene i dette innlegget skal jeg bare bruke modulen assert som følger Med NodeJS. Jeg oppfordrer deg til å sjekke ut andre påstandsbiblioteker og legge igjen et notat hvis du har en favoritt.

Merk: Denne delen vil være et lite eksempel tungt da vi raskt bytter fra å endre koden vår og kjører spec runner ofte.

Første Scenario

ved gjennomgang av Det Første Scenarioets deretter trinn:

Then The grocery list contains a single item

…vi må bevise at Dagligvarelisten forekomsten vokser med en faktor på 1 for hvert nytt element lagt til.

Oppdater trinnet for å definere hvordan vi forventer at spesifikasjonen skal valideres:

/ feature / stepdefinitions/add-item.trinn.js_

...var assert = require('assert');...module.exports = function() {... this.Then(/^The grocery list contains a single item$/, function(callback) { assert.equal(myList.getAll().length, 1, 'Grocery List should grow by one item.'); callback(); });...};...

vi har trukket inn modulen assert og forsøkt å validere at Lengden På Handlelisten har vokst med en verdi på 1 etter å ha kjørt det forrige trinnet når du legger til varen.

Kjør det, og vi får et unntak:

$ ./node_modules/.bin/cucumber-js..F..P(::) failed steps (::)TypeError: Object #<Object> has no method 'getAll'

La oss legge den metoden til Vår Dagligvareliste modell:

/skript/modell / dagligvare-liste.js

'use strict';var groceryList = { add: function(item) { // }, getAll: function() { // }};module.exports = { create: function() { return Object.create(groceryList); }};

og tilbake til å kjøre våre spesifikasjoner:

$ ./node_modules/.bin/cucumber-js..F..P(::) failed steps (::)TypeError: Cannot read property 'length' of undefined

Siden koden ikke returnerer noe fra getAll() , kan vi ikke få tilgang til en length eiendom for vår påstandstest.

hvis vi endrer koden for å returnere En Matrise:

/ feature / stepdefinitions / add-item.trinn.js_

...getAll: function() { return ;}...

og kjør spesifikasjonene igjen, vi får påstandsfeilmeldingen vi oppgav:

$ ./node_modules/.bin/cucumber-js..F..P(::) failed steps (::)AssertionError: Grocery List should grow by one item.

Nå har vi en skikkelig Feil som rapporteres til oss fra en påstand som fører til at trinnet ikke går forbi. Hurra!

Ta En Pust

La oss pause her for et sekund før du legger til mer kode for å få dette trinnet til å passere. Problemet ved hånden er egentlig ikke å legge til et element i matrisen som returneres, det handler mer om å sikre at et element legges til via add – metoden, og resultatet fra getAll er en liste utvidet med det elementet.

implementeringsdetaljene som er involvert i å gjøre dette testpasset, er hvor teamet ditt bruker arkitekturopplevelsen, men det kreves at bare den viktigste koden legges til. Du bør unngå å gå over i å tenke på internals Av Handleliste samling modell. Det er et glatt tau som lett kan falle ned i et kaninhull-akkurat som den dårlig formulerte metaforen 🙂

Kom Tilbake til Jobb!

i dette eksemplet bruker vi argumentet propertiesObject for Object.create for å definere en list getter som vil fungere som et foranderlig utvalg for våre dagligvareliste:

/ script / model / grocery-list.js

'use strict';var groceryList = { add: function(item) { this.list.push(item); }, getAll: function() { return this.list; }};module.exports = { create: function() { return Object.create(groceryList, { 'list': { value: , writable: false, enumerable: true } }); }};

hvis vi kjører det, finner vi at det første Scenariet nå går forbi!

$ ./node_modules/.bin/cucumber-js.....P2 scenarios (1 pending, 1 passed)6 steps (1 pending, 5 passed)

Andre Scenario

ved gjennomgang av det siste trinnet i Vårt 2. Scenario får den ventende implementeringen tilgang til det ekstra elementet:

Then I can access that item from the grocery list

For å gjøre dette trinnet pass må vi bekrefte at vi kan få tilgang til elementet lagt Til Handleliste ved å påkalle add() med en vare.

Som med implementeringen av å få tilgang til Lengden På Handlelisten, er det flere måter vi kan gjøre denne testen passere i koden. Igjen føler jeg at dette er hvor programvareutviklingserfaring og smak kommer inn i spill med hensyn til arkitektur, men jeg foretrekker også å prøve å produsere minst mulig kode mulig; og jeg vil være den første til å innrømme at noen ganger går jeg litt fraværende og lager mer kode enn det som er nødvendig…

når det er sagt, må vi også ta hensyn til språk-og miljøspesifikasjoner i hvordan vi adresserer å gjøre påstanden pass – og nettleseren, med sin historie, har mange å vurdere. Det er ikke en liten, det er bare en forethought i å sette forventninger til krav.

Spesifikt: anta at vi skulle si at trinnet kan verifiseres ved hjelp av metoden Array.indexOf() på samlingen returnert fra ‘getAll ()’ på Dagligvarelisten? Uten en polyfill, så begrenser vi oss til å passere påstander OM IE 9 og eldre. Slike hensyn er bare toppen av isfjellet når du bestemmer deg for hva du skal introdusere i kodebasen for å få testene dine til å passere, og bør virkelig overlates til en gruppediskusjon om hva som anses nødvendig for å få produktet til produksjon.

jeg kunne fortsette og fortsette, men la oss bare anta at vi vil dekke alle baser når det gjelder nettlesere (DVS. 6 og opp, shudder). Etter min mening, for å gjøre dette Andre Scenariet grønt, vil vi legge til en getItemIndex() metode med følgende signatur:

+ getItemIndex(itemValue):int

Vi endrer først trinnet for å mislykkes:

/funksjon/stepdefinitions / legg til element.trinn.js_

this.Then(/^I can access that item from the grocery list$/, function(callback) { assert.notEqual(myList.getItemIndex(listItem), -1, 'Added item should be found at non-negative index.'); callback();});

godkjenningen for at denne testen skal bestå er at indeksen der det ekstra elementet ligger i samlingen, ikke er negativ. I dette scenariet prøver vi ikke å validere en spesifikasjon om hvor et nytt element legges til i en liste(f. eks.

Kjører som vil gi et unntak:

$ ./node_modules/.bin/cucumber-js.....F(::) failed steps (::)TypeError: Object #<Object> has no method 'getItemIndex'

la oss endre Vår Handleliste objekt for å støtte getItemIndex metode:

/script/model / grocery-list.js

'use strict';var groceryList = { add: function(item) { this.list.push(item); }, getAll: function() { return this.list; }, getItemIndex: function(value) { var index = this.list.length; while(--index > -1) { if(this.list === value) { return index; } } return -1; }};module.exports = { create: function() { return Object.create(groceryList, { 'list': { value: , writable: false, enumerable: true } }); }};

i vår implementering av getItemIndex blir listen krysset, og hvis varen er funnet, returneres indeksen. Ellers returneres en verdi på -1. I hovedsak, hvordan Array.indexOf – metoden fungerer Av ECMAScript 5.

Merk: Jeg vet at det kan virke dumt å bruke Objekt.lag Fra ECMAScript 5, men Ikke Array.indeks av. Årsaken – for det meste-er at jeg normalt alltid inkluderer en polyfill For Objekt.lag og ikke For Array.indeks av. Jeg antar vane.

nå hvis vi kjører spesifikasjonene igjen Under CucumberJS:

$ ./node_modules/.bin/cucumber-js......2 scenarios (2 passed)6 steps (6 passed)

våre cukes ER GRØNNE! (Dette er poenget du tørker pannen og sakte klapp).

Konklusjon

i dette innlegget introduserte jeg hvordan JEG bruker bdd-verktøyet CucumberJS for å følge Testdrevet Utvikling I JavaScript. Jeg gikk gjennom å bruke et eksempel på en Enkelt Funksjon med to Scenarier og snu sviktende Trinn til green cukes. Hvis du ikke er kjent med prosessen med å lage tester, mislykkes først bare for å produsere kode for å gjøre testpasset, håper jeg du fulgte med; Jeg kan være ordlig og prosessen kan synes å ta mye tid, men utviklingen under slik praksis begynner å bevege seg jevnt når du kommer i sporet. I tillegg tror jeg det er en stor belønning i å ha koden din under en testsele når det gjelder refactoring og feilretting-både i utviklerhelse og virksomhet.

denne artikkelen ble opprinnelig postet på http://custardbelly.com/blog/blog-posts/2014/01/08/bdd-in-js-cucumberjs/index.html

Bilde gjengitt avhttp://commons.wikimedia.org/wiki/File:Og%C3%B3rki…jpg

e6b2cff6a55928c8f62b9d602add3fed?s = 100d = mmr = g

Todd Anderson er en applikasjonsutvikler med en lidenskap for arkitektur og utviklingsprosesser.

som en sterk talsmann for smidig praksis og Testdrevet Utvikling med over ti års erfaring, har han bidratt til å levere web -, mobil-og skrivebordsløsninger med mange selskaper i bedrifts-og underholdningsindustrien, inkludert Adobe, THQ, [email protected] Nast Publications og Motorola.

han skriver ofte om programvare og teknologi på sin blogg, støtter åpen Kildekode og på grunn av mitt beste for å gi tilbake til utviklingssamfunnet gjennom GitHub og har hatt den respekterte glede å være medforfatter på flere titler Fra O ‘ Reilly Og Wiley Wrox: Amazon profile.

Følg Todd På Twitter

Besøk Todds nettsted

Legg igjen en kommentar

Din e-postadresse vil ikke bli publisert.