Testdriven JavaScript-utveckling i praktiken

TDD är en iterativ utvecklingsprocess där varje iteration börjar genom att skriva ett test som utgör en del av specifikationen vi implementerar. De korta iterationerna möjliggör mer snabb återkoppling av koden vi skriver, och dåliga designbeslut är lättare att fånga. Genom att skriva testerna före någon produktionskod kommer god testenhet med territoriet, men det är bara en välkommen sidoeffekt.

Publicerad handledning

Varje par veckor besöker vi några av våra läsares favoritinlägg från hela webbplatsens historia. Denna handledning publicerades först i november 2010.


Turning Utveckling upp och ner

I traditionell programmering löses problem genom programmering tills ett koncept är fullt representerat i kod. Idealiskt följer koden vissa övergripande arkitektoniska designhänsyn, men i många fall, kanske speciellt i JavaScript-världen, så är det inte så. Denna typ av programmering löser problem genom att gissa vilken kod som krävs för att lösa dem, en strategi som enkelt kan leda till uppblåsta och tätt kopplade lösningar. Om det inte heller finns några enhetstester kan lösningar som produceras med detta tillvägagångssätt även innehålla kod som aldrig exekveras, såsom felhanteringslogik och "flexibel" argumenthantering, eller det kan innehålla kantfall som inte har testats noga, om de testas alls.

Testdriven utveckling förvandlar utvecklingscykeln upp och ner. I stället för att fokusera på vilken kod som krävs för att lösa ett problem börjar testdriven utveckling genom att definiera målet. Enhetstesterna utgör både specifikationen och dokumentationen för vilka åtgärder som stöds och redovisas. Beviljas, målet för TDD testar inte och det finns ingen garanti för att den hanteras t.ex. kantfall bättre. Men eftersom varje kodlinje testas av ett representativt stycke provkod, kommer TDD sannolikt att producera mindre överskottskod, och den funktionalitet som redovisas är sannolikt mer robust. Korrekt testdriven utveckling säkerställer att ett system aldrig kommer att innehålla kod som inte körs.


Processen

Den testdrivna utvecklingsprocessen är en iterativ process där varje iteration består av följande fyra steg:

  • Skriv ett test
  • Kör test, se det nya testet misslyckas
  • Gör testpasset
  • Refactor för att ta bort dubbelarbete

I varje iteration är testet specifikationen. När tillräcklig produktionskod (och inte mer) har skrivits för att göra provet klar, är vi färdiga, och vi kan refactor koden för att ta bort dubbelarbete och / eller förbättra designen, så länge testen fortfarande passerar.


Praktisk TDD: Observatörsmönstret

Observermönstret (även kallat Publicera / Prenumerera, eller helt enkelt PubSub) är ett designmönster som gör att vi kan observera objektets tillstånd och meddelas när det ändras. Mönstret kan ge föremål med kraftfulla förlängningspunkter samtidigt som lös koppling upprätthålls.

Det finns två roller i Observer - observerbar och observatör. Observatören är ett objekt eller en funktion som kommer att meddelas när det observerbara tillståndet ändras. Den observerbara bestämmer när man ska uppdatera sina observatörer och vilka data som ska ge dem. Den observerbara ger vanligtvis åtminstone två offentliga metoder: PubSub, som meddelar sina observatörer av nya uppgifter, och PubSub som abonnerar observatörer på händelser.


Det Observable Library

Testdriven utveckling gör det möjligt för oss att flytta i mycket små steg vid behov. I detta första verkliga exempel kommer vi att börja med de minsta stegen. När vi får förtroende för vår kod och process kommer vi gradvis att öka storleken på våra steg när omständigheterna tillåter det (det vill säga koden som ska implementeras är trivial nog). Skriv kod i små frekventa iterationer hjälper oss att designa vår API-bit för bitar och hjälpa oss att göra färre misstag. När det uppstår misstag kommer vi att kunna fixa dem snabbt, eftersom det blir lätt att hitta fel när vi kör test varje gång vi lägger till en handfull kodkod.


Ställa in miljön

I det här exemplet används JsTestDriver för att köra test. En installationsguide finns tillgänglig på den officiella webbplatsen.

Den ursprungliga projektlayouten ser ut som följer:

 chris @ laptop: ~ / projects / observable $ tree. | - jsTestDriver.conf | - src | "- observable.js" - test "- observable_test.js

Konfigurationsfilen är bara minimal JsTestDriver konfiguration:

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

Lägga till observatörer

Vi kommer att starta projektet genom att genomföra ett sätt att lägga till observatörer i ett objekt. Om vi ​​gör det tar vi oss genom att skriva det första testet, se det misslyckas, passera det på skäligaste sätt och slutligen refactoring det till något mer förnuftigt.


Det första testet

Det första testet försöker lägga till en observatör genom att ringa addObserver metod. För att verifiera att detta fungerar kommer vi att vara trubbiga och anta att observerbara lagrar sina observatörer i en array och kontrollera att observatören är det enda objektet i den gruppen. Testet hör hemma i test / observable_test.js och ser ut som följande:

 TestCase ("ObservableAddObserverTest", "test bör lagra funktion": funktion () var observerbar = ny tddjs.Observable (); var observer = funktion () ; observerbar.addObserver (observatör); assertEquals (observatör, observerbar. observatörer [0]););

Kör testet och titta på det misslyckas

Vid första anblicken är resultatet av att springa vårt allra första test förödande:

 Totalt 1 test (Passat: 0; Fel: 0; Fel: 1) (0.00 ms) Firefox 3.6.12 Linux: Kör 1 test (Passat: 0; Fel: 0; Fel 1) (0.00 ms) ObservableAddObserverTest.test bör lagra funktionsfel (0.00 ms): \ tddjs är inte definierat /test/observable_test.js:3 Test misslyckades.

Gör provet

Frukta inte! Fel är faktiskt en bra sak: Det berättar för oss var vi ska fokusera på våra ansträngningar. Det första allvarliga problemet är att tddjs inte existerar. Låt oss lägga till namnrymdobjektet i src / observable.js:

 var tddjs = ;

Om du kör testen igen ger ett nytt fel:

 E Totalt 1 test (Passat: 0; Fel: 0; Fel: 1) (0.00 ms) Firefox 3.6.12 Linux: Kör 1 test (Passat: 0; Fel: 0; Fel 1) (0.00 ms) ObservableAddObserverTest.test bör lagringsfel (0.00 ms): \ tddjs.Observable är inte en konstruktör /test/observable_test.js:3 Test misslyckades.

Vi kan fixa det här nya problemet genom att lägga till en tom Observable constructor:

 var tddjs = ; (funktion () funktion Observable ()  tddjs.Observable = Observable; ());

Kör testet igen leder oss direkt till nästa problem:

 E Totalt 1 test (Passat: 0; Fel: 0; Fel: 1) (0.00 ms) Firefox 3.6.12 Linux: Kör 1 test (Passat: 0; Fel: 0; Fel 1) (0.00 ms) ObservableAddObserverTest.test bör lagringsfel (0.00 ms): \ observerbar.addObserver är inte en funktion /test/observable_test.js:6 Test misslyckades.

Låt oss lägga till den saknade metoden.

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

Med metoden på plats misslyckas testet nu i stället för en saknad observatörsgrupp.

 E Totalt 1 test (Passat: 0; Fel: 0; Fel: 1) (0.00 ms) Firefox 3.6.12 Linux: Kör 1 test (Passat: 0; Fel: 0; Fel 1) (0.00 ms) ObservableAddObserverTest.test bör lagringsfel (0.00 ms): \ observerbar.observatörer är odefinierade /test/observable_test.js:8 Test misslyckades.

Så udda som det kan tyckas, kommer jag nu att definiera observatörsuppsättningen inuti PubSub metod. När ett test misslyckas instruerar TDD oss att göra det enklaste som kan fungera, oavsett hur smutsigt det känns. Vi får chansen att granska vårt arbete när testet passerar.

 funktion addObserver (observatör) this.observers = [observatör];  Framgång! Testet passerar nu:. Totalt 1 test (Passat: 1; Fel: 0; Fel: 0) (1,00 ms) Firefox 3.6.12 Linux: Kör 1 tester (Passat: 1; Fel: 0; Fel 0) (1,00 ms)

refactoring

Samtidigt som vi utvecklar den nuvarande lösningen har vi tagit den snabbaste möjliga vägen till ett godkänd test. Nu när baren är grön kan vi granska lösningen och utföra eventuell refactoring som vi anser nödvändiga. Den enda regeln i det här sista steget är att hålla fältet grönt. Det betyder att vi kommer att behöva refactor i små steg, se till att vi inte av misstag bryter något.

Det nuvarande genomförandet har två frågor som vi ska hantera. Testet gör detaljerade antaganden om genomförandet av Observable och the addObserver genomförandet är hårdkodat för vårt test.

Vi kommer att ta itu med hårdkodningen först. För att avslöja den hårdkodade lösningen kommer vi att öka testet så att det lägger till två observatörer istället för en.

 "test ska lagra funktion": funktion () var observerbar = ny tddjs.Observable (); var observatörer = [funktion () , funktion () ]; observable.addObserver (observatörer [0]); observable.addObserver (observatörer [1]); assertEquals (observatörer, observable.observers); 

Som förväntat misslyckas testet nu. Testet förväntar sig att funktioner som läggs till som observatörer ska stapla upp som något element som läggs till ett PubSub. För att uppnå detta kommer vi att flytta array-instantiering till konstruktören och delegera addObserver till array metod push:

 funktion Observable () this.observers = [];  funktion addObserver (observatör) this.observers.push (observatör); 

Med denna implementering på plats passerar testet igen, vilket visar att vi har tagit hand om den hårdkodade lösningen. Emellertid är frågan om att få tillgång till en offentlig egendom och göra vilda antaganden om genomförandet av Observable fortfarande ett problem. En observerbar PubSub bör observeras av ett antal objekt, men det är inte intressant för utomstående hur eller var de observerbara lagrar dem. Helst skulle vi vilja kunna kontrollera med det observerbara om en viss observatör är registrerad utan att krossa sig på insidan. Vi noterar lukten och fortsätter. Senare kommer vi att komma tillbaka för att förbättra detta test.


Kontrollera efter observatörer

Vi lägger till en annan metod för Observable, hasObserver, och använd den för att ta bort några av de rörelser vi lade till när vi genomförde addObserver.


Testet

En ny metod börjar med ett nytt test, och nästa önskat beteende för hasObserver metod.

 TestCase ("ObservableHasObserverTest", "testet ska returnera sant när har observatör": funktion () var observerbar = ny tddjs.Observable (); var observer = funktion () ; observerbar.addObserver (observatör); assertTrue .hasObserver (observatör)););

Vi förväntar oss att detta test misslyckas inför en saknas hasObserver, vilket det gör.


Gör provet

Återigen använder vi den enklaste lösningen som möjligen skulle kunna klara det aktuella testet:

 funktionen harObserver (observatör) return true;  Observable.prototype.hasObserver = hasObserver;

Trots att vi vet att detta inte löser våra problem på lång sikt, håller testen grön. Att försöka granska och refactor lämnar oss tomhänt eftersom det inte finns några tydliga punkter där vi kan förbättra. Testerna är våra krav, och för närvarande behöver de bara hasObserver att återvända sant. För att fixa det kommer vi att introducera ett annat test som förväntar oss hasObserver till returnera falskt för en obefintlig observatör, som kan hjälpa till att tvinga den verkliga lösningen.

 "testet ska returnera falskt när inga observatörer": funktion () var observerbar = ny tddjs.Observable (); assertFalse (observerbar.hasObserver (funktion () )); 

Detta test misslyckas, med tanke på det hasObserver alltid returnerar sant, tvinga oss att producera den verkliga implementeringen. Kontrollera om en observatör är registrerad är en enkel fråga om att kontrollera att arrayen this.observers innehåller objektet som ursprungligen skickades till addObserver:

 funktion harObserver (observatör) returnera this.observers.indexOf (observatör)> = 0; 

De Array.prototype.indexOf Metoden returnerar ett tal mindre än 0 om elementet inte är närvarande i array, så kontrollera att den returnerar ett tal som är lika med eller större än 0 kommer berätta för oss om observatören existerar.


Oplösning av webbläsare

Att köra testet i mer än en webbläsare ger något överraskande resultat:

 chris @ laptop: ~ / projects / observable $ jstestdriver - testar alla ... E Totalt 4 tester (Passat: 3; Fel: 0; Fel: 1) (11.00 ms) Firefox 3.6.12 Linux: Kör 2 tester , Fel: 0; Fel 0) (2,00 ms) Microsoft Internet Explorer 6.0 Windows: Kör 2 tester \ (Passat: 1; Fel: 0; Fel 1) (0.00 ms) ObservableHasObserverTest.test ska returnera sant när har observatörsfel \ 0.00 ms): Objektet stöder inte den här egenskapen eller metoden Test misslyckades.

Internet Explorer version 6 och 7 misslyckades testet med deras mest generiska felmeddelanden: "Objektet stöder inte den här egenskapen eller metoden ". Detta kan indikera vilket antal problem som helst:

  • vi ringer en metod på ett objekt som är null
  • vi ringer en metod som inte existerar
  • Vi kommer åt en egendom som inte existerar

Lyckligtvis, TDD-ing i små steg, vet vi att felet måste relatera till det nyligen tillagda samtalet till index för på våra observatörer array. Som det visar sig stöder IE 6 och 7 inte JavaScript 1.6-metoden Array.prototype.indexOf (för vilket vi inte kan skylla på det, var det bara nyligen standardiserat med ECMAScript 5, december 2009). Vid denna tidpunkt har vi tre alternativ:

  • Omgå användningen av Array.prototype.indexOf i hasObserver, vilket effektivt duplicerar inbyggd funktionalitet i stödjande webbläsare.
  • Implementera Array.prototype.indexOf för icke-stödjande webbläsare. Alternativt implementera en hjälparfunktion som ger samma funktion.
  • Använd ett bibliotek från tredje part som tillhandahåller antingen den saknade metoden eller en liknande metod.

Vilket av dessa tillvägagångssätt passar bäst för att lösa ett givet problem kommer att bero på situationen - de har alla sina fördelar och nackdelar. För att hålla Observable självständigt, kommer vi helt enkelt att genomföra hasObserver i form av en slinga i stället för index för ringa, effektivt arbeta runt problemet. För övrigt verkar det också vara det enklaste som kan fungera vid denna tidpunkt. Skulle vi komma i en liknande situation senare, skulle vi rekommenderas att ompröva vårt beslut. Den uppdaterade hasObserver ser ut som följer:

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

refactoring

Med baren tillbaka till grön är det dags att granska våra framsteg. Vi har nu tre tester, men två av dem verkar konstigt lika. Det första testet vi skrev för att verifiera riktigheten av addObserver i princip testar för samma saker som testet vi skrev för att verifiera refactoring . Det finns två viktiga skillnader mellan de två testen: Det första testet har tidigare förklarats illaluktande, eftersom det direkt åtkomst till observatörsfältet i det observerbara objektet. Det första testet lägger till två observatörer och ser till att de båda läggs till. Vi kan nu ansluta testerna till en som verifierar att alla observatörer som läggs till det observerbara faktiskt läggs till:

 "testet ska lagra funktioner": funktion () var observerbar = ny tddjs.Observable (); var observatörer = [funktion () , funktion () ]; observable.addObserver (observatörer [0]); observable.addObserver (observatörer [1]); assertTrue (observable.hasObserver (observatörer [0])); assertTrue (observable.hasObserver (observatörer [1])); 

Anmälande observatörer

Att lägga till observatörer och kontrollera deras existens är trevligt, men utan att kunna meddela dem intressanta förändringar är Observable inte särskilt användbart. Det är dags att genomföra anmälningsmetoden.


Se till att observatörer kallas

Den viktigaste uppgiften att anmäla utför ringer alla observatörer. För att göra detta behöver vi något sätt att verifiera att en observatör har blivit kallad efter det faktum. För att verifiera att en funktion har ringts kan vi ställa in en egenskap på funktionen när den heter. För att verifiera testet kan vi kontrollera om egenskapen är inställd. Följande test använder detta koncept i det första testet för anmälan.

 TestCase ("ObservableNotifyTest", "testet ska kalla alla observatörer": funktion () var observerbar = ny tddjs.Observable (); var observer1 = funktion () observer1.called = true;; var observer2 = funktion observer2.called = true;; observerbar.addObserver (observer1); observerbar.addObserver (observer2); observerbar.notify (); assertTrue (observer1.called); assertTrue (observer2.called););

För att klara provet måste vi slinga observatörerna och ringa varje funktion:

 funktion meddela () för (var i = 0, l = this.observers.length; i < l; i++)  this.observers[i]();   Observable.prototype.notify = notify;

Passerar argument

För närvarande kallas observatörer, men de matas inga data. De vet att något hände - men inte nödvändigtvis vad. Vi kommer att göra anmälan ta några argumenter, helt enkelt överföra dem till varje observatör:

 "testet ska gå igenom argument": funktion () var observerbar = ny tddjs.Observable (); var faktiskt; observerbar.addObserver (funktion () actual = arguments;); observable.notify ("String", 1, 32); assertEquals (["String", 1, 32], actual); 

Testet jämförs mottagna och skickade argument genom att tilldela de mottagna argumenten till en variabel lokal till testet. Observatören vi just skapat är faktiskt en väldigt enkel manuell testspion. Att köra testet bekräftar att det misslyckas, vilket inte är förvånande, eftersom vi för närvarande inte rör argumenten i meddelandet.

För att klara det test som vi kan använda gäller när vi ringer observatören:

 funktion meddela () för (var i = 0, l = this.observers.length; i < l; i++)  this.observers[i].apply(this, arguments);  

Med dessa enkla fixtest går du tillbaka till grönt. Observera att vi skickade in detta som det första argumentet att ansöka, vilket innebär att observatörer kommer att kallas med det observerbara som detta.


Felhantering

Vid denna punkt Observable är funktionellt och vi har test som verifierar sitt beteende. Testen kontrollerar emellertid bara att observerbarheten fungerar korrekt som svar på förväntad inmatning. Vad händer om någon försöker registrera ett objekt som observatör istället för en funktion? Vad händer om en av observatörerna spränger? Det är frågor vi behöver våra test för att svara på. Att säkerställa korrekt beteende i förväntade situationer är viktigt - det är vad våra föremål kommer att göra mest av tiden. Åtminstone så kunde vi hoppas. Riktigt beteende, även när kunden misshandlar, är dock lika viktigt för att garantera ett stabilt och förutsägbart system.


Lägga till Bogus Observers

Den nuvarande implementeringen accepterar blindt någon form av argument till addObserver. Även om vår implementering kan använda någon funktion som observatör, kan den inte hantera något värde. Följande test förväntar sig att det observerbara att kasta ett undantag när man försöker lägga till en observatör som inte kan ringas.

 "testet ska kasta för ofrånkomlig observatör": funktion () var observerbar = ny tddjs.Observable (); assertException (funktion () observerbar.addObserver ();, "TypeError"); 

Genom att kasta ett undantag redan när man lägger till observatörer behöver vi inte oroa oss för ogiltiga data senare när vi meddelar observatörer. Hade vi programmerat enligt kontrakt, kunde vi säga att det var en förutsättning för addObserver Metoden är att ingången måste kunna ringas. De postcondition är att observatören läggs till observerbar och är garanterad att bli kallad när de observerbara samtalen meddelar.

Testet misslyckas, så vi flyttar vårt fokus för att få baren grön igen så fort som möjligt. Tyvärr finns det inget sätt att förfalska genomförandet här - kasta ett undantag för alla anrop till addObserver kommer att misslyckas alla andra test. Lyckligtvis är genomförandet ganska trivialt:

 funktion addObserver (observatör) om (typ av observatör! = "funktion") släng ny TypeError ("observatör är inte funktion");  this.observers.push (observatör); 

addObserver kontrollerar nu att observatören faktiskt är en funktion innan den läggs till i listan. Att köra testerna ger den söta känslan av framgång: Alla gröna.


Misbehaving Observers

Den observerbara garanterar nu att någon observatör läggs till addObserver kan kallas. Ändå kan anmälan fortfarande misslyckas hemskt om en observatör kastar ett undantag. Nästa test förväntar sig att alla observatörer ska ringas även om en av dem kastar ett undantag.

 "testet bör meddela alla även när vissa misslyckas": funktion () var observerbar = ny tddjs.Observable (); var observer1 = function () kasta nytt fel ("oops"); ; var observer2 = funktion () observer2.called = true; ; observable.addObserver (observer1); observable.addObserver (observer2); observable.notify (); assertTrue (observer2.called); 

Genom att utföra testet avslöjas att det aktuella genomförandet blåser upp tillsammans med den första observatören, vilket orsakar att den andra observatören inte kallas. I själva verket bryter meddelandet sin garanti för att det alltid kommer att ringa alla observatörer när de väl har lagts till. För att rätta till situationen måste metoden förberedas för det värsta:

 funktion meddela () för (var i = 0, l = this.observers.length; i < l; i++)  try  this.observers[i].apply(this, arguments);  catch (e)   

Undantaget är tyst kasserat. Det är observatörens ansvar att se till att eventuella fel hanteras ordentligt, det observerbara är helt enkelt att avvärja dåligt uppträdande observatörer.


Dokumentation av samtalsorder

Vi har förbättrat robustheten hos den observerbara modulen genom att ge den rätt felhantering. Modulen kan nu ge garantier för drift så länge det blir bra input och det kan återhämta sig om en observatör inte uppfyller sina krav. Det sista testet vi lagt till gör dock ett antagande om observerade egenskaper hos observerbara: Det förutsätter att observatörer kallas i den ordning de tillkom. För närvarande fungerar denna lösning eftersom vi använde en matris för att genomföra observatörslistan. Skulle vi bestämma oss för att ändra detta, kan våra test bryta oss. Så vi måste bestämma: gör vi refactor testet för att inte anta uppringningsorder, eller lägger vi helt enkelt till ett test som förväntar oss samtalsorder - därmed dokumenterar samtalsorder som en funktion? Samtalsorder verkar som en förnuftig funktion, så vårt nästa test kommer att se till att Observable håller detta beteende.

 "testet ska kalla observatörer i den ordning de lagts till": funktion () var observerbar = ny tddjs.Observable (); var samtal = []; var observer1 = funktion () calls.push (observator1); ; var observer2 = funktion () calls.push (observator2); ; observable.addObserver (observer1); observable.addObserver (observer2); observable.notify (); assertEquals (observatör1, samtal [0]); assertEquals (observatör2, samtal [1]); 

Eftersom implementeringen redan använder en matris för observatörerna, lyckas detta test omedelbart.


Observation av godtyckliga objekt

I statiska språk med klassiskt arv görs godtyckliga objekt observerbara av subclassing Den observerbara klassen. Motivationen för klassiskt arv i dessa fall kommer från en önskan att definiera mönstret på ett ställe och återanvända logiken över stora mängder orelaterade föremål. I JavaScript har vi flera alternativ för kodåteranvändning bland objekt, så vi behöver inte begränsa oss till en emulering av den klassiska arvsmodellen.

I intresse av att bryta sig fri från den klassiska emuleringen som konstruktörer tillhandahåller, överväga följande exempel som antar att tddjs.observable är ett objekt snarare än en konstruktör:

Notera tddjs.extend Metoden introduceras någon annanstans i boken och kopierar helt enkelt egenskaper från ett objekt till ett annat.

 // Skapa ett enda observerbart objekt var observerbar = Object.create (tddjs.util.observable); // Utvidga ett enda objekt tddjs.extend (tidningen, tddjs.util.observable); // En konstruktör som skapar observerbara objekt funktion Newspaper () / * ... * / Newspaper.prototype = Object.create (tddjs.util.observable); // Utöka en befintlig prototyp tddjs.extend (Newspaper.prototype, tddjs.util.observable);

Att enkelt implementera det observerbara som ett enda objekt ger en stor flexibilitet. För att komma dit måste vi refactor den befintliga lösningen för att bli av med konstruktören.


Gör byggaren föråldrad

För att bli av med konstruktören bör vi först refactor Observable så att konstruktören inte gör något arbete. Lyckligtvis initierar konstruktören endast observatörsfältet, vilket inte borde vara för svårt att ta bort. Alla metoder på Observable.prototype får tillgång till arrayen, så vi måste se till att de alla kan hantera fallet där det inte har initierats. För att testa för detta behöver vi bara skriva ett test per metod som kallar metoden ifråga innan vi gör någonting annat.

Som vi redan har test som ringer addObserver och hasObserver innan vi gör någonting annat kommer vi att koncentrera oss på anmälningsmetoden. Denna metod testas endast efter addObserver har blivit kallad. Våra nästa test förväntar oss att det går att ringa denna metod innan vi lägger till några observatörer.

 "testet ska inte misslyckas om inga observatörer": funktion () var observerbar = ny tddjs.Observable (); assertNoException (function () observable.notify ();); 

Med detta test på plats kan vi tömma konstruktören:

 funktion Observable () 

När testen körs visas att allt utom en saknas nu, alla med samma meddelande: "this.observers är inte definierad". Vi kommer att hantera en metod i taget. Först upp är addObserver metod:

funktion addObserver (observatör)
om (! this.observers)
this.observers = [];

/ * ... * /

Kör testen igen avslöjar att den uppdaterade addObserver Metoden fixar alla utom de två testen som inte börjar genom att ringa den. Därefter ser vi till att vi returnerar falska direkt från hasObserver om arrayen inte existerar.

 funktion harObserver (observatör) om (! this.observers) return false;  / * ... * /

Vi kan tillämpa exakt samma åtgärd att meddela:

 funktion meddela (observatör) om (! this.observers) return;  / * ... * /

Byta byggaren med ett objekt

Nu när konstruktör gör ingenting som det säkert kan tas bort. Vi lägger sedan till alla metoder direkt till tddjs.observable objekt, som sedan kan användas med t.ex. Object.create eller tddjs.extend att skapa observerbara objekt. Observera att namnet inte längre aktiveras eftersom det inte längre är en konstruktör. Den uppdaterade implementeringen följer:

 funktionen () function * * * funktionen har observer (observatör) / * ... * / funktion meddela () / * ... * / tddjs.observable = addObserver: addObserver, hasObserver : hasObserver, meddela: meddela; ());

Visst, att avlägsna konstruktören orsakar alla test så långt som möjligt att bryta. Att fixa dem är dock lätt. Allt vi behöver göra är att ersätta det nya uttalandet med ett samtal till Object.create. Dock stöder de flesta webbläsare inte Object.create ändå, så vi kan shim det. Eftersom metoden inte är möjlig att perfektimulera, kommer vi att tillhandahålla vår egen version på tddjs objekt:

 (funktion () funktion F ()  tddjs.create = funktion (objekt) F.prototype = objekt; returnera nytt F ();; / * Observable implementation går här ... * / ());

Med shim på plats kan vi uppdatera testen i en fråga som kommer att fungera även i gamla webbläsare. Den slutliga testpaketet följer:

 TestCase ("ObservableAddObserverTest", setUp: function () this.observable = tddjs.create (tddjs.observable);, "test ska lagra funktioner": funktion () var observers = [function () , funktion () ]; this.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);, "testet ska returnera falskt när inga observatörer": funktion () assertFalse (this.observable.hasObserver funktion () ));); TestCase ("ObservableNotifyTest", setUp: function () this.observable = tddjs.create (tddjs.observable);, "testet ska kalla alla observatörer": funktion () var observer1 = function () observer1.called = true;; var observer2 = funktion () observer2.called = true;; this.observable.addObserver (observer1); this.observable.addObserver (observatör2); this.observable.notify (); assertTrue (observatör1. kallas), assertTrue (observer2.called);, "testet ska gå igenom argument": funktion () var actual; this.observable.addObserver (function () actual = arguments;); this.observable.notify "String", 1, 32); assertEquals (["String", 1, 32], actual);, "testet ska kasta för obalansbar observatör": funktion () var observerbar = this.observable; assertException ) observerbar.addObserver ();, "TypeError");, "testet bör meddela alla även när vissa misslyckas": funktion () var observer1 = function () kasta nytt fel ("Oops"); ; var observer2 = funktion () observer2.called = true; ; this.observable.addObserver (observer1); this.observable.addObserver (observer2); this.observable.notify (); assertTrue (observer2.called); , "testet ska kalla observatörer i den ordning de lagts till": funktion () var calls = []; var observer1 = funktion () calls.push (observator1); ; var observer2 = funktion () calls.push (observator2); ; this.observable.addObserver (observer1); this.observable.addObserver (observer2); this.observable.notify (); assertEquals (observatör1, samtal [0]); assertEquals (observatör2, samtal [1]); , "testet ska inte misslyckas om inga observatörer": funktion () var observerbar = this.observable; assertNoException (function () observable.notify ();); );

För att undvika att duplicera tddjs.create samtal fick varje testfall en a inrätta metod som sätter upp observerbar för testning. Testmetoderna måste uppdateras i enlighet därmed och ersättas observerbart med detta. Observerbara.


Sammanfattning


Genom detta utdrag ur boken har vi haft en mjuk introduktion till testdriven utveckling med JavaScript. Naturligtvis är API för närvarande begränsat i sina möjligheter, men boken expanderar vidare på det genom att låta observatörer observera och anmäla anpassade händelser, till exempel observable.observe ("beforeLoad", myObserver).

<