Testdrevet JavaScript-utvikling i praksis

TDD er en iterativ utviklingsprosess hvor hver iterasjon starter ved å skrive en test som utgjør en del av spesifikasjonen vi implementerer. De korte iterasjonene gir mer umiddelbar tilbakemelding på koden vi skriver, og dårlige designbeslutninger er enklere å fange. Ved å skrive testene før noen produksjonskode kommer god dekningstesting med territoriet, men det er bare en velkommen sideeffekt.

Publisert opplæring

Noen få uker besøker vi noen av leserens favorittinnlegg fra hele historien til nettstedet. Denne opplæringen ble først publisert i november 2010.


Snu utviklingen Upside-Down

Ved tradisjonell programmering løses problemene ved å programmere til et konsept er fullt representert i kode. Ideelt sett følger koden noen generelle arkitektoniske utformingshensyn, men i mange tilfeller, kanskje spesielt i JavaScript-verdenen, er dette ikke tilfelle. Denne typen programmering løser problemer ved å gjette på hvilken kode som kreves for å løse dem, en strategi som lett kan føre til oppblåst og tett koblede løsninger. Hvis det heller ikke er noen enhetstester, kan løsninger som produseres med denne tilnærmingen til og med inneholde kode som aldri blir utført, for eksempel feilhåndteringslogikk og "fleksibel" argumenthåndtering, eller det kan inneholde kantsaker som ikke er grundig testet, hvis testet i det hele tatt.

Testdrevet utvikling gjør utviklingssyklusen opp ned. I stedet for å fokusere på hvilken kode som kreves for å løse et problem, begynner testdrevet utvikling ved å definere målet. Enhetstester danner både spesifikasjon og dokumentasjon for hvilke handlinger som støttes og regnskapsføres. Gitt, målet med TDD er ikke testing og så er det ingen garanti for at den håndterer f.eks. kanten tilfeller bedre. Men fordi hver linje av kode er testet av et representativt stykke prøvekode, vil TDD sannsynligvis produsere mindre overskytende kode, og funksjonaliteten som regnes for, vil trolig være mer robust. Riktig testdrevet utvikling sikrer at et system aldri vil inneholde kode som ikke blir utført.


Prosessen

Den testdrevne utviklingsprosessen er en iterativ prosess hvor hver iterasjon består av følgende fire trinn:

  • Skriv en test
  • Kjør tester, se den nye testen mislykkes
  • Gjør testpasningen
  • Refactor for å fjerne duplisering

I hver iterasjon er testen spesifikasjonen. Når nok produksjonskoden (og ikke mer) er skrevet for å få testen til å passere, er vi ferdige, og vi kan refactor koden for å fjerne duplisering og / eller forbedre designet, så lenge testene fremdeles passerer.


Praktisk TDD: Observatørmønsteret

Observer-mønsteret (også kjent som Publiser / Abonner, eller bare PubSub) er et mønster som lar oss observere tilstanden til et objekt og bli varslet når den endres. Mønsteret kan gi gjenstander med kraftige forlengelsespunkter, samtidig som det opprettholdes løs kopling.

Det er to roller i observatøren - observerbar og observatør. Observatøren er et objekt eller en funksjon som vil bli varslet når tilstanden til observerbare endringer. Den observerbare bestemmer når man skal oppdatere observatørene og hvilke data som skal gi dem. Det observerbare gir vanligvis minst to offentlige metoder: PubSub, som underretter sine observatører av nye data, og PubSub som abonnerer observatører på hendelser.


Det observerbare biblioteket

Testdrevet utvikling gjør det mulig for oss å bevege seg i svært små trinn når det er nødvendig. I dette første virkelige eksemplet vil vi starte med de minste trinnene. Når vi får tillit til koden og prosessen, vil vi gradvis øke størrelsen på trinnene våre når forholdene tillater det (det vil si at koden som skal implementeres er trivial nok). Skrivekode i små hyppige iterasjoner vil hjelpe oss med å designe vårt API-stykke i tillegg, og hjelpe oss med å gjøre færre feil. Når feil oppstår, vil vi kunne fikse dem raskt, da feil blir lette å spore når vi driver tester hver gang vi legger til en håndfull kodelinjer.


Sette opp miljøet

Dette eksemplet bruker JsTestDriver til å kjøre tester. En oppsettguide er tilgjengelig fra det offisielle nettstedet.

Det opprinnelige prosjektoppsettet ser ut som følger:

 chris @ laptop: ~ / prosjekter / observerbar $ tree. | - jsTestDriver.conf | - src | '- observable.js' - test '- observable_test.js

Konfigurasjonsfilen er bare minimal JsTestDriver konfigurasjon:

 server: http: // localhost: 4224 load: - lib / *. js - test / *. js

Legge til observatører

Vi vil starte prosjektet ved å implementere et middel for å legge til observatører i et objekt. Å gjøre det vil ta oss gjennom å skrive den første testen, se på den mislykkes, passere den på den skitteneste måten og til slutt refactoring det til noe mer fornuftig.


Den første testen

Den første testen vil forsøke å legge til en observatør ved å ringe addObserver metode. For å verifisere at dette virker, vil vi være stumme og anta at observerbare lagrer observatørene i en matrise og kontrollerer at observatøren er det eneste elementet i den gruppen. Testen tilhører test / observable_test.js og ser ut som følgende:

 TestCase ("ObservableAddObserverTest", "test skal lagre funksjon": funksjon () var observerbar = ny tddjs.Observable (); var observatør = funksjon () observerbar.addObserver (observatør); assertEquals (observatør, observerbar. observatører [0]););

Kjører testen og ser det mislykkes

Ved første øyekast er resultatet av å kjøre vår aller første test ødeleggende:

 Totalt 1 tester (Passet: 0; Feil: 0; Feil: 1) (0.00 ms) Firefox 3.6.12 Linux: Kjør 1 tester (Passet: 0; Feil: 0; Feil 1) (0.00 ms) ObservableAddObserverTest.test bør lagre funksjonsfeil (0.00 ms): \ tddjs er ikke definert /test/observable_test.js:3 Testene mislyktes.

Å gjøre testpasset

Frykt ikke! Feil er faktisk en god ting: Det forteller oss hvor du skal fokusere vår innsats. Det første alvorlige problemet er at tddjs ikke eksisterer. La oss legge til navneområdeobjektet i src / observable.js:

 var tddjs = ;

Kjører testene igjen gir en ny feil:

 E Totalt 1 tester (Passet: 0; Feil: 0; Feil: 1) (0.00 ms) Firefox 3.6.12 Linux: Kjør 1 tester (Passet: 0; Feil: 0; Feil 1) (0.00 ms) ObservableAddObserverTest.test skal lagringsfunksjonsfeil (0.00 ms): \ tddjs.Observable er ikke en konstruktør /test/observable_test.js:3 Testene mislyktes.

Vi kan fikse dette nye problemet ved å legge til en tom Observerbar konstruktør:

 var tddjs = ; (funksjon () funksjon Observerbar ()  tddjs.Observable = Observerbar; ());

Kjører testen igjen fører oss direkte til det neste problemet:

 E Totalt 1 tester (Passet: 0; Feil: 0; Feil: 1) (0.00 ms) Firefox 3.6.12 Linux: Kjør 1 tester (Passet: 0; Feil: 0; Feil 1) (0.00 ms) ObservableAddObserverTest.test skal lagringsfunksjonsfeil (0.00 ms): \ observerbar.addObserver er ikke en funksjon /test/observable_test.js:6 Testene mislyktes.

La oss legge til den manglende metoden.

 funksjon addObserver ()  Observable.prototype.addObserver = addObserver;

Med metoden på plass mislykkes testen i stedet for et manglende observatørarray.

 E Totalt 1 tester (Passet: 0; Feil: 0; Feil: 1) (0.00 ms) Firefox 3.6.12 Linux: Kjør 1 tester (Passet: 0; Feil: 0; Feil 1) (0.00 ms) ObservableAddObserverTest.test skal lagringsfunksjonsfeil (0.00 ms): \ observerbar.observatorer er undefined /test/observable_test.js:8 Testene mislyktes.

Så merkelig som det kan virke, vil jeg nå definere observatørmatrisen inne i PubSub metode. Når en test feiler, instruerer TDD oss å gjøre det enkleste som muligens kan fungere, uansett hvor skittent det føles. Vi får sjansen til å se gjennom arbeidet vårt når testen er bestått.

 funksjon addObserver (observatør) this.observers = [observatør];  Suksess! Testen går nå:. Totalt 1 tester (Passet: 1; Mislykkes: 0; Feil: 0) (1,00 ms) Firefox 3.6.12 Linux: Kjør 1 tester (Passet: 1; Feil: 0; Feil 0) (1,00 ms)

refactoring

Mens vi utvikler den nåværende løsningen, har vi tatt den raskeste mulige ruten til en bestått test. Nå som baren er grønn, kan vi se gjennom løsningen og utføre refactoring som vi anser nødvendig. Den eneste regelen i dette siste trinnet er å holde linjen grønn. Dette betyr at vi må også reflektere i små skritt, og sørg for at vi ikke ved et uhell bryter noe.

Den nåværende implementeringen har to problemer vi bør håndtere. Testen gir detaljerte forutsetninger om gjennomføringen av Observable og addObserver implementeringen er hardkodd til vår test.

Vi vil ta opp hardkodingen først. For å avsløre den hardkodede løsningen vil vi øke testen slik at den legger til to observatører i stedet for en.

 "testen skal lagre funksjon": funksjon () var observerbar = ny tddjs.Observable (); var observatører = [funksjon () , funksjon () ]; observable.addObserver (observatører [0]); observable.addObserver (observatører [1]); assertEquals (observatører, observable.observers); 

Som forventet mislykkes testen nå. Testen forventer at funksjoner som legges til som observatører, burde stable opp som noe element lagt til en PubSub. For å oppnå dette vil vi flytte array instantiation til konstruktøren og bare delegere addObserver til matrise metode push:

 funksjon Observerbar () this.observers = [];  funksjon addObserver (observatør) this.observers.push (observatør); 

Med denne implementeringen på plass går testen igjen, og viser at vi har tatt vare på den hardkodede løsningen. Men problemet med å få tilgang til en offentlig eiendom og å gjøre vanlige forutsetninger om gjennomføringen av Observable er fortsatt et problem. En observerbar PubSub bør observeres av et hvilket som helst antall objekter, men det er ikke av interesse for utenforstående hvordan eller hvor observerbare lagrer dem. Ideelt sett vil vi kunne sjekke med det observerbare hvis en viss observatør er registrert uten å gruve rundt innsiden. Vi merker lukten og fortsetter. Senere vil vi komme tilbake for å forbedre denne testen.


Sjekker etter observatører

Vi vil legge til en annen metode for å observeres, hasObserver, og bruk den til å fjerne noen av rotene vi la til ved implementering addObserver.


Testen

En ny metode starter med en ny test, og den neste ene ønsket oppførsel for hasObserver metode.

 TestCase ("ObservableHasObserverTest", "testen skal returnere sann når har observatør": funksjon () var observerbar = ny tddjs.Observable (); var observatør = funksjon () ; observerbar.addObserver (observatør); assertTrue .hasObserver (observatør)););

Vi forventer at denne testen mislykkes i møte med en manglende hasObserver, som det gjør.


Å gjøre testpasset

Igjen, bruker vi den enkleste løsningen som muligens kan gi den nåværende testen:

 funksjon hasObserver (observatør) return true;  Observable.prototype.hasObserver = hasObserver;

Selv om vi vet at dette ikke vil løse våre problemer i det lange løp, holder testene grønt. Forsøk å se gjennom og refactor etterlater oss tomhendt da det ikke er noen åpenbare poeng der vi kan forbedre. Tester er våre krav, og for tiden krever de bare hasObserver å returnere sant. For å fikse det vil vi introdusere en annen test som forventer hasObserver til returner falsk for en ikke-eksisterende observatør, som kan bidra til å tvinge den virkelige løsningen.

 "testen skal returnere falsk når ingen observatører": funksjon () var observerbar = ny tddjs.Observable (); assertFalse (observerbar.hasObserver (funksjon () )); 

Denne testen mislykkes dårlig, gitt det hasObserver alltid returnerer sant, tvinger oss til å produsere den virkelige implementeringen. Kontroller om en observatør er registrert, er et enkelt spørsmål om å sjekke at denne.observers array inneholder objektet som ble opprinnelig sendt til addObserver:

 funksjon harObserver (observatør) returner this.observers.indexOf (observatør)> = 0; 

De Array.prototype.indexOf Metoden returnerer et tall mindre enn 0 hvis elementet ikke er til stede i matrise, så sjekk at det returnerer et tall lik eller større enn 0 vil fortelle oss om observatøren eksisterer.


Løsning av nettleser Uforlikeligheter

Kjører testen i mer enn én nettleser gir noe overraskende resultater:

 chris @ laptop: ~ / projects / observable $ jstestdriver - tester alle ... E Totalt 4 tester (Passet: 3; Feil: 0; Feil: 1) (11.00 ms) Firefox 3.6.12 Linux: Kjør 2 tester , Mislykkes: 0; feil 0) (2,00 ms) Microsoft Internet Explorer 6.0 Windows: Kjør 2 tester \ (Passet: 1; Mislykkes: 0; Feil 1) (0.00 ms) ObservableHasObserverTest.test bør returnere sant når har observatørfeil \ 0.00 ms): Objektet støtter ikke denne egenskapen eller metoden Testene mislyktes.

Internet Explorer versjon 6 og 7 mislyktes testen med deres mest generiske av feilmeldinger: "Objektet støtter ikke denne egenskapen eller metoden ". Dette kan indikere et antall problemer:

  • vi ringer en metode på et objekt som er null
  • vi ringer en metode som ikke eksisterer
  • vi får tilgang til en eiendom som ikke eksisterer

Heldigvis, TDD-ing i små skritt, vet vi at feilen må forholde seg til det nylig lagt til oversikt over på våre observatører matrise. Som det viser seg, støtter IE 6 og 7 ikke JavaScript 1.6-metoden Array.prototype.indexOf (som vi egentlig ikke kan klandre det, var det bare nylig standardisert med ECMAScript 5, desember 2009). På dette tidspunktet har vi tre alternativer:

  • Omgå bruken av Array.prototype.indexOf i hasObserver, effektivt duplisere innfødt funksjonalitet i støttende nettlesere.
  • Implementer Array.prototype.indexOf for ikke-støttende nettlesere. Alternativt implementere en hjelperfunksjon som gir samme funksjonalitet.
  • Bruk et tredjepartsbibliotek som inneholder enten den manglende metoden eller en lignende metode.

Hvilken av disse tilnærmingene er best egnet til å løse et gitt problem vil avhenge av situasjonen - de har alle sine fordeler og ulemper. For å holde Observable selvstendig, vil vi ganske enkelt implementere hasObserver i form av en løkke i stedet for oversikt over ring, effektivt arbeide rundt problemet. Forresten synes det også å være den enkleste tingen som muligens kan fungere på dette punktet. Skulle vi komme til en lignende situasjon senere, vil vi bli bedt om å revurdere vår beslutning. Den oppdaterte hasObserver ser slik ut:

 funksjon harObserver (observatør) for (var i = 0, l = this.observers.length; i < l; i++)  if (this.observers[i] == observer)  return true;   return false; 

refactoring

Med baren tilbake til grønt, er det på tide å vurdere vår fremgang. Vi har nå tre tester, men to av dem virker merkelig likt. Den første testen vi skrev for å verifisere korrektheten av addObserver i utgangspunktet tester for de samme tingene som testen vi skrev for å verifisere refactoring . Det er to viktige forskjeller mellom de to testene: Den første testen er tidligere blitt erklært stinkende, da den direkte får tilgang til observatørmatrisen i det observerbare objektet. Den første testen legger til to observatører, slik at de begge blir lagt til. Vi kan nå delta i testene i en som bekrefter at alle observatører som legges til det observerbare, blir lagt til:

 "testen skal lagre funksjoner": funksjon () var observerbar = ny tddjs.Observable (); var observatører = [funksjon () , funksjon () ]; observable.addObserver (observatører [0]); observable.addObserver (observatører [1]); assertTrue (observable.hasObserver (observatører [0])); assertTrue (observable.hasObserver (observatører [1])); 

Meldende observatører

Å legge til observatører og sjekke for deres eksistens er fint, men uten evne til å varsle dem om interessante endringer, er Observable ikke veldig nyttig. Det er på tide å implementere meldingsmetoden.


Sikre at observatører kalles

Den viktigste oppgaven varsler utfører ringer alle observatørene. For å gjøre dette trenger vi noen måte å verifisere at en observatør er blitt kalt etter faktum. For å bekrefte at en funksjon er blitt kalt, kan vi sette en egenskap på funksjonen når den blir kalt. For å bekrefte testen kan vi sjekke om eiendommen er satt. Følgende test bruker dette konseptet i den første testen for å varsle.

 TestCase ("ObservableNotifyTest", "test skal kalle alle observatører": funksjon () var observerbar = ny tddjs.Observable (); var observer1 = funksjon () observer1.called = true;; var observer2 = funksjon observer2.called = true;; observerbar.addObserver (observer1); observerbar.addObserver (observer2); observerbar.notify (); assertTrue (observer1.called); assertTrue (observer2.called););

For å bestå testen må vi løse observatørene og ringe til hver funksjon:

 funksjon varsle () for (var i = 0, l = this.observers.length; i < l; i++)  this.observers[i]();   Observable.prototype.notify = notify;

Passerende argumenter

For tiden blir observatørene kalt, men de blir ikke matet noen data. De vet at noe skjedde - men ikke nødvendigvis hva. Vi vil gi beskjed ta noen argumenter, bare passere dem til hver observatør:

 "testen skal passere gjennom argumenter": funksjon () var observerbar = ny tddjs.Observable (); var faktisk; observerbar.addObserver (funksjon () actual = arguments;); observable.notify ("String", 1, 32); assertEquals (["String", 1, 32], faktisk); 

Testen sammenligner mottatt og bestått argumenter ved å tildele de mottatte argumentene til en variabel lokal til testen. Observatøren vi nettopp opprettet, er faktisk en veldig enkel manuell testspion. Kjører testen bekrefter at den mislykkes, noe som ikke er overraskende da vi for øyeblikket ikke berører argumentene i varslingen.

For å bestå testen kan vi bruke gjelder når du ringer observatøren:

 funksjon varsle () for (var i = 0, l = this.observers.length; i < l; i++)  this.observers[i].apply(this, arguments);  

Med denne enkle reparasjonstesten går du tilbake til grønt. Legg merke til at vi sendte inn dette som det første argumentet som skal brukes, noe som betyr at observatører vil bli kalt med det observerbare som dette.


Feilhåndtering

På dette punktet Observable er funksjonelt og vi har tester som bekrefter atferd. Testene kontrollerer imidlertid bare at observablene oppfører seg riktig som svar på forventet inngang. Hva skjer hvis noen prøver å registrere et objekt som observatør i stedet for en funksjon? Hva skjer hvis en av observatørene springer opp? Det er spørsmål vi trenger våre tester for å svare på. Å sikre riktig oppførsel i forventede situasjoner er viktig - det er hva våre objekter vil gjøre mesteparten av tiden. I hvert fall så kunne vi håpe. Imidlertid er riktig oppførsel, selv når klienten feiler, like viktig for å sikre et stabilt og forutsigbart system.


Legger til Bogus Observers

Den nåværende implementeringen aksepterer blindt enhver form for argument til addObserver. Selv om implementeringen vår kan bruke noen funksjon som observatør, kan den ikke håndtere noen verdi. Følgende test forventer at det observerbare å kaste et unntak når man forsøker å legge til en observatør som ikke kan kalles.

 "testen skal kaste for uovervinnelig observatør": funksjon () var observerbar = ny tddjs.Observable (); assertException (function () observable.addObserver ();, "TypeError"); 

Ved å kaste et unntak allerede ved å legge til observatørene trenger vi ikke å bekymre deg for ugyldige data senere når vi melder observatører. Hadde vi vært programmering på kontrakt, kan vi si det en forutsetning for addObserver Metoden er at inngangen må kunne kalles. De postcondition er at observatøren blir lagt til det observerbare og garantert å bli kalt når de observerbare samtalene gir beskjed.

Testen feiler, så vi skifter fokus for å få baren grønn igjen så raskt som mulig. Dessverre er det ingen måte å forfølge implementeringen dette - kaster et unntak på noen anrop til addObserver vil mislykkes alle de andre testene. Heldigvis er implementeringen ganske trivial:

 funksjon addObserver (observatør) if (type observatør! = "funksjon") kaste ny TypeError ("observatør er ikke funksjon");  this.observers.push (observatør); 

addObserver Kontrollerer nå at observatøren faktisk er en funksjon før den legges til listen. Kjører tester gir den gode følelsen av suksess: Alle grønne.


Misbehaving Observers

Den observerbare garanterer nå at noen observatør legges til addObserver kan kalles. Likevel kan meldingen fortsatt mislykkes horribelt hvis en observatør kaster et unntak. Den neste testen forventer at alle observatørene blir kalt selv om en av dem kaster et unntak.

 "testen skal varsle alle selv når noen mislykkes": funksjon () var observerbar = ny tddjs.Observable (); var observer1 = funksjon () kaste ny feil ("Ups"); ; var observer2 = funksjon () observer2.called = true; ; observable.addObserver (Observer1); observable.addObserver (observer2); observable.notify (); assertTrue (observer2.called); 

Kjører testen avslører at den nåværende implementeringen blåser opp sammen med den første observatøren, og forårsaker at den andre observatøren ikke blir kalt. I virkeligheten, er underretning bryte sin garanti for at det alltid vil ringe alle observatører når de har blitt lagt til. For å rette opp situasjonen må metoden være forberedt på det verste:

 funksjon varsle () for (var i = 0, l = this.observers.length; i < l; i++)  try  this.observers[i].apply(this, arguments);  catch (e)   

Unntaket er tydelig kassert. Det er observatørens ansvar å sørge for at feilen håndteres ordentlig, det observerbare er rett og slett å avvike dårlige opptredene observatører.


Dokumentere anropsordre

Vi har forbedret robustheten til den observerbare modulen ved å gi den riktig feilhåndtering. Modulen er nå i stand til å gi garantier for drift så lenge det blir god inngang og det er i stand til å gjenopprette hvis en observatør ikke oppfyller sine krav. Den siste testen vi la til, antar imidlertid at det ikke er dokumenterte objekter som kan observeres: Det antas at observatører blir kalt i den rekkefølgen de ble lagt til. For tiden fungerer denne løsningen fordi vi brukte en matrise for å implementere observatørlisten. Skulle vi bestemme oss for å endre dette, kan våre tester imidlertid bryte. Så vi må bestemme: Gjør vi refactor testen for ikke å anta anropsordre, eller legger vi ganske enkelt til en test som forventer anropsordre - derved dokumenterer anropsordre som en funksjon? Anropsordre virker som en fornuftig funksjon, så vår neste test vil sørge for at Observable beholder denne oppførselen.

 "testen skal ringe observatører i den rekkefølgen de ble lagt til": funksjon () var observerbar = ny tddjs.Observable (); var samtaler = []; var observer1 = funksjon () calls.push (observator1); ; var observer2 = funksjon () calls.push (observator2); ; observable.addObserver (Observer1); observable.addObserver (observer2); observable.notify (); assertEquals (observatør1, samtaler [0]); assertEquals (observatør2, samtaler [1]); 

Siden implementeringen allerede bruker en matrise for observatørene, lykkes denne testen med en gang.


Observerer vilkårlige objekter

I statiske språk med klassisk arv blir det oppdaget vilkårlig gjenstand subclassing Den observerbare klassen. Motivasjonen for klassisk arv i disse tilfellene kommer fra et ønske om å definere mønsterets mekanikk på ett sted og gjenbruke logikken over store mengder urelaterte objekter. I JavaScript har vi flere alternativer for kodegenbruk blant objekter, så vi trenger ikke begrense oss til en emulering av den klassiske arvsmodellen.

For å bryte fri for den klassiske emuleringen som konstruktørene gir, bør du vurdere følgende eksempler som antar at tddjs.observable er en gjenstand i stedet for en konstruktør:

Merk: Den tddjs.extend Metoden er introdusert andre steder i boken og kopierer bare egenskaper fra ett objekt til et annet.

 // Opprette et enkelt observerbart objekt var observerbar = Object.create (tddjs.util.observable); // Utvide et enkelt objekt tddjs.extend (avis, tddjs.util.observable); // En konstruktør som lager observerbare objekter funksjon Avis () / * ... * / Newspaper.prototype = Object.create (tddjs.util.observable); // Utvide en eksisterende prototype tddjs.extend (Newspaper.prototype, tddjs.util.observable);

Bare å implementere det observerbare som et enkelt objekt gir en stor fleksibilitet. For å komme dit må vi refactor den eksisterende løsningen for å kvitte seg med konstruktøren.


Gjør konstruksjonen utelatt

For å bli kvitt konstruktøren bør vi først refactor Observable slik at konstruktøren ikke gjør noe arbeid. Heldigvis initierer konstruktøren bare observatørmatrisen, som ikke bør være for vanskelig å fjerne. Alle metodene på Observable.prototype får tilgang til arrayet, så vi må sørge for at de alle kan håndtere saken der den ikke er initialisert. For å teste dette må vi bare skrive en test per metode som kaller den aktuelle metoden før du gjør noe annet.

Som vi allerede har tester som ringer addObserver og hasObserver før vi gjør noe annet, vil vi konsentrere oss om varslingsmetoden. Denne metoden blir bare testet etter addObserver har blitt kalt. Våre neste tester forventer at det blir mulig å ringe denne metoden før du legger til observatører.

 "testen bør ikke mislykkes hvis ingen observatører": funksjon () var observerbar = ny tddjs.Observable (); assertNoException (function () observable.notify ();); 

Med denne testen på plass kan vi tømme konstruktøren:

 funksjon Observerbar () 

Kjører testene viser at alt annet enn en er i ferd med å feile, alle med samme melding: "this.observers er ikke definert". Vi vil håndtere en metode om gangen. Først opp er addObserver metode:

funksjon addObserver (observatør)
hvis (! this.observers)
this.observers = [];

/ * ... * /

Kjører testene igjen viser at den oppdaterte addObserver Metoden retter alle sammen, men de to tester som ikke starter ved å ringe det. Deretter sørger vi for at du returnerer falsk direkte fra hasObserver hvis array ikke eksisterer.

 funksjon harObserver (observatør) hvis (! this.observers) return false;  / * ... * /

Vi kan søke nøyaktig samme løsning for å varsle:

 funksjon varsle (observatør) hvis (! this.observers) return;  / * ... * /

Bytte ut konstruksjonen med et objekt

Nå da konstruktør gjør ikke noe det kan fjernes trygt. Vi vil da legge alle metodene direkte til tddjs.observable gjenstand, som deretter kan brukes med f.eks. Object.create eller tddjs.extend å opprette observerbare objekter. Merk at navnet ikke lenger er aktivert fordi det ikke lenger er en konstruktør. Den oppdaterte implementeringen følger:

 (funksjon () funksjon addObserver (observatør) / * ... * / funksjon hasObserver (observatør) / * ... * / funksjon varsle () / * ... * / tddjs.observable = addObserver: addObserver, hasObserver : hasObserver, varsle: varsle; ());

Sikkert fjerner konstruktøren alle testene så langt å bryte. Å fikse dem er imidlertid lett. Alt vi trenger å gjøre er å erstatte den nye setningen med en samtale til Object.create. Imidlertid støtter de fleste nettlesere ikke Object.create ennå, så vi kan skumme det. Fordi metoden ikke er mulig å perfekt emulere, vil vi gi vår egen versjon på tddjs gjenstand:

 (funksjon () funksjon F ()  tddjs.create = funksjon (objekt) F.prototype = objekt; returner ny F ();; / * Observerbar implementering går her ... * / ());

Med shim på plass, kan vi oppdatere testene i et spørsmål som vil fungere selv i gamle nettlesere. Den endelige testpakken følger:

 TestCase ("ObservableAddObserverTest", setUp: function () this.observable = tddjs.create (tddjs.observable);, "test skal lagre funksjoner": funksjon () var observers = [function () , funksjon () ]; dette.observable.addObserver (observatører [0]); this.observable.addObserver (observatører [1]); assertTrue (this.observable.hasObserver (observatører [0]); assertTrue (this.observable .hasObserver (observatører [1]));); TestCase ("ObservableHasObserverTest", setUp: function () this.observable = tddjs.create (tddjs.observable);, "testen skal returnere falsk når ingen observatører": funksjon () assertFalse (this.observable.hasObserver funksjon () ));); TestCase ("ObservableNotifyTest", setUp: function () this.observable = tddjs.create (tddjs.observable);, "test skal kalle alle observatører": funksjon () var observer1 = funksjon () observator1.called = true;; var observer2 = funksjon () observer2.called = true;; this.observable.addObserver (observer1); this.observable.addObserver (observer2); this.observable.notify (); assertTrue (observer1. kalles); assertTrue (observer2.called);, "test skal passere gjennom argumenter": funksjon () var faktisk; this.observable.addObserver (funksjon () actual = arguments;); this.observable.notify "String", 1, 32); assertEquals (["String", 1, 32], faktisk);, "testen skal kaste for uovervinnelig observatør": funksjon () var observerbar = this.observable; assertException ) observable.addObserver ();, "TypeError");, "test skal varsle alle selv når noen mislykkes": funksjon () var observer1 = funksjon () kaste ny feil ; var observer2 = funksjon () observer2.called = true; ; this.observable.addObserver (Observer1); this.observable.addObserver (observer2); this.observable.notify (); assertTrue (observer2.called); , "testen skal ringe observatører i den rekkefølgen de ble lagt til": funksjon () var calls = []; var observer1 = funksjon () calls.push (observator1); ; var observer2 = funksjon () calls.push (observator2); ; this.observable.addObserver (Observer1); this.observable.addObserver (observer2); this.observable.notify (); assertEquals (observatør1, samtaler [0]); assertEquals (observatør2, samtaler [1]); , "testen burde ikke mislykkes hvis ingen observatører": funksjon () var observerbar = this.observable; assertNoException (function () observable.notify ();); );

For å unngå duplisering av tddjs.create ring, hver test sak fikk a Setup metode som setter opp observerbar for testing. Testmetodene må oppdateres tilsvarende, erstatte observerbare med dette. Observable.


Sammendrag


Gjennom dette utdraget fra boken har vi hatt en myk introduksjon til testdrevet utvikling med JavaScript. Selvfølgelig er API-en for tiden begrenset i sine evner, men boken utvides videre på den ved å tillate observatører å observere og varsle tilpassede hendelser, for eksempel observable.observe ("beforeLoad", myObserver).

Boken gir også innsikt i hvordan du kan bruke TDD til å utvikle kode som f.eks. stoler sterkt på DOM-manipulasjon og Ajax, og til slutt bringer alle prøveprosjektene sammen i et fullt funksjonelt nettleserbasert chatprogram.

Dette utdraget er basert på boken "Testdrevet JavaScript-utvikling", forfattet av Christ