Testgestuurde JavaScript-ontwikkeling in de praktijk

TDD is een iteratief ontwikkelingsproces waarbij elke iteratie begint met het schrijven van een test die deel uitmaakt van de specificatie die we implementeren. De korte iteraties zorgen voor meer directe feedback op de code die we schrijven en slechte ontwerpbeslissingen zijn gemakkelijker te vangen. Door de tests voorafgaand aan een productiecode te schrijven, komt er een goede testdekking bij het grondgebied, maar dat is slechts een welkom neveneffect.

Opnieuw gepubliceerde zelfstudie

Om de paar weken bekijken we enkele van onze favoriete lezers uit de geschiedenis van de site. Deze zelfstudie is voor het eerst gepubliceerd in november 2010.


Ontwikkeling ondersteboven draaien

In traditionele programmering worden problemen opgelost door te programmeren totdat een concept volledig is weergegeven in code. Idealiter volgt de code een aantal algemene architecturale ontwerpoverwegingen, hoewel in veel gevallen, misschien vooral in de wereld van JavaScript, dit niet het geval is. Deze stijl van programmeren lost problemen op door te raden welke code nodig is om ze op te lossen, een strategie die gemakkelijk kan leiden tot opgeblazen en strak gekoppelde oplossingen. Als er ook geen unit tests zijn, kunnen oplossingen die met deze aanpak zijn gemaakt, zelfs code bevatten die nooit wordt uitgevoerd, zoals foutafhandelingslogica en "flexibele" argumentafhandeling, of het kan randgevallen bevatten die niet grondig zijn getest, indien getest helemaal niet.

Testgestuurde ontwikkeling maakt de ontwikkelingscyclus op zijn kop. In plaats van te focussen op welke code nodig is om een ​​probleem op te lossen, begint een testgestuurde ontwikkeling met het definiëren van het doel. Eenheidstests vormen zowel de specificatie als documentatie voor welke acties worden ondersteund en verantwoord. Toegegeven, het doel van TDD is niet testen en dus is er geen garantie dat het met b.v. randgevallen beter. Omdat elke coderegel wordt getest door een representatief stuk voorbeeldcode, zal TDD waarschijnlijk minder overtollige code produceren en is de functionaliteit die wordt verantwoord waarschijnlijk robuuster. Een goede testgestuurde ontwikkeling zorgt ervoor dat een systeem nooit code bevat die niet wordt uitgevoerd.


Het proces

Het testgestuurde ontwikkelingsproces is een iteratief proces waarbij elke iteratie uit de volgende vier stappen bestaat:

  • Schrijf een test
  • Voer tests uit, kijk hoe de nieuwe test mislukt
  • Laat de test slagen
  • Refactor om duplicatie te verwijderen

In elke iteratie is de test de specificatie. Zodra genoeg productiecode (en niet meer) is geschreven om de test te laten slagen, zijn we klaar en kunnen we de code opnieuw gebruiken om duplicatie te verwijderen en / of het ontwerp te verbeteren, zolang de tests nog steeds voorbij zijn.


Praktisch TDD: The Observer Pattern

Het waarnemingspatroon (ook bekend als publiceren / abonneren, of eenvoudigweg pubsub) is een ontwerppatroon waarmee we de staat van een object kunnen observeren en op de hoogte kunnen worden gesteld wanneer het verandert. Het patroon kan objecten voorzien van krachtige verlengingspunten terwijl losse koppeling behouden blijft.

Er zijn twee rollen in The Observer - waarneembaar en waarnemer. De waarnemer is een object of functie die op de hoogte wordt gesteld wanneer de toestand van de waarneembare veranderingen verandert. De waarneembare bepaalt wanneer zijn waarnemers moeten worden bijgewerkt en welke gegevens ze moeten verstrekken. De waarneembare biedt doorgaans ten minste twee openbare methoden: pubsub, die zijn waarnemers nieuwe gegevens meldt, en pubsub die waarnemers inschrijft voor evenementen.


De waarneembare bibliotheek

Testgestuurde ontwikkeling stelt ons in staat om in zeer kleine stappen te bewegen wanneer dat nodig is. In dit eerste voorbeeld uit de praktijk beginnen we met de kleinste stappen. Naarmate we meer vertrouwen krijgen in onze code en het proces, zullen we geleidelijk de omvang van onze stappen vergroten als de omstandigheden dit toestaan ​​(d.w.z. de code die moet worden geïmplementeerd is triviaal genoeg). Door code te schrijven in kleine frequente iteraties kunnen we onze API stukje bij beetje ontwerpen en ons helpen minder fouten te maken. Wanneer er fouten optreden, kunnen we deze snel herstellen omdat fouten eenvoudig kunnen worden opgespoord wanneer we tests uitvoeren telkens wanneer we een handvol regels code toevoegen.


De omgeving instellen

In dit voorbeeld wordt JsTestDriver gebruikt om tests uit te voeren. Een installatiehandleiding is beschikbaar op de officiële website.

De initiële projectlay-out ziet er als volgt uit:

 chris @ laptop: ~ / projecten / waarneembare $ boom. | - jsTestDriver.conf | - src | '- observeable.js' - test '- observeable_test.js

Het configuratiebestand is slechts minimaal JsTestDriver configuratie:

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

Waarnemers toevoegen

We beginnen het project door een middel te implementeren om waarnemers aan een object toe te voegen. Als we dit doen, zullen we de eerste test schrijven, kijken of het niet lukt, het op de meest vervuilde manier doorgeven en het uiteindelijk hergebruiken tot iets zinnigs.


De eerste test

De eerste test zal proberen een waarnemer toe te voegen door de addObserver methode. Om te verifiëren dat dit werkt, zullen we bot zijn en aannemen dat waarneembaar zijn waarnemers in een array opslaat en controleren of de waarnemer het enige item in die array is. De test hoort thuis test / observable_test.js en ziet er als volgt uit:

 TestCase ("ObservableAddObserverTest", "test moet functie opslaan": function () var observationable = new tddjs.Observable (); var observer = function () ; observeable.addObserver (observer); assertEquals (observer, observeable. waarnemers [0]););

De test uitvoeren en bekijken mislukt

Op het eerste gezicht is het resultaat van het uitvoeren van onze allereerste test verwoestend:

 Totaal 1 tests (geslaagd: 0; mislukt: 0; fouten: 1) (0,00 ms) Firefox 3.6.12 Linux: 1 tests uitvoeren (geslaagd: 0; mislukt: 0; fouten 1) (0,00 ms) ObservableAddObserverTest.test moet opslaan function error (0.00 ms): \ tddjs is niet gedefinieerd /test/observable_test.js:3 Tests mislukt.

De testpas maken

Wees niet bang! Falen is eigenlijk een goede zaak: het vertelt ons waar we onze inspanningen op moeten richten. Het eerste serieuze probleem is dat tddjs niet bestaat. Laten we het namespace-object toevoegen src / observable.js:

 var tddjs = ;

Het opnieuw uitvoeren van de tests levert een nieuwe fout op:

 E Totaal 1 tests (geslaagd: 0; mislukt: 0; fouten: 1) (0,00 ms) Firefox 3.6.12 Linux: 1 tests uitvoeren (geslaagd: 0; mislukt: 0; fouten 1) (0,00 ms) ObservableAddObserverTest.test moet fout in de winkelfunctie (0,00 ms): \ tddjs.Observable is geen constructor /test/observable_test.js:3 Tests mislukt.

We kunnen dit nieuwe probleem oplossen door een lege Observable constructor toe te voegen:

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

De test opnieuw uitvoeren brengt ons direct bij het volgende probleem:

 E Totaal 1 tests (geslaagd: 0; mislukt: 0; fouten: 1) (0,00 ms) Firefox 3.6.12 Linux: 1 tests uitvoeren (geslaagd: 0; mislukt: 0; fouten 1) (0,00 ms) ObservableAddObserverTest.test moet fout in de winkelfunctie (0,00 ms): \ observable.addObserver is geen functie /test/observable_test.js:6 Tests mislukt.

Laten we de ontbrekende methode toevoegen.

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

Met de methode op zijn plaats faalt de test nu in de plaats van een ontbrekende matrix van waarnemers.

 E Totaal 1 tests (geslaagd: 0; mislukt: 0; fouten: 1) (0,00 ms) Firefox 3.6.12 Linux: 1 tests uitvoeren (geslaagd: 0; mislukt: 0; fouten 1) (0,00 ms) ObservableAddObserverTest.test moet fout in de winkelfunctie (0,00 ms): \ observable.observers is undefined /test/observable_test.js:8 Tests mislukt.

Hoe raar het ook mag klinken, ik zal nu de array van waarnemers definiëren in de pubsub methode. Wanneer een test mislukt, geeft TDD ons de opdracht om het eenvoudigste te doen dat mogelijk kan werken, hoe vuil het ook aanvoelt. We zullen de kans krijgen om ons werk te beoordelen zodra de test voorbij is.

 functie addObserver (waarnemer) this.observers = [waarnemer];  Succes! De test gaat nu voorbij:. Totaal 1 tests (geslaagd: 1; mislukt: 0; fouten: 0) (1,00 ms) Firefox 3.6.12 Linux: 1 tests uitvoeren (geslaagd: 1; mislukt: 0; fouten 0) (1,00 ms)

refactoring

Bij het ontwikkelen van de huidige oplossing hebben we de snelst mogelijke route naar een passerende test genomen. Nu de balk groen is, kunnen we de oplossing bekijken en een refactoring uitvoeren die we nodig achten. De enige regel in deze laatste stap is om de balk groen te houden. Dit betekent dat we ook in kleine stapjes moeten refactoren, waarbij we ervoor zorgen dat we niet per ongeluk iets breken.

De huidige implementatie kent twee kwesties die we moeten behandelen. De test maakt gedetailleerde aannames over de implementatie van Observable en de addObserver implementatie is hard gecodeerd voor onze test.

We zullen eerst de hardcoding behandelen. Om de hardgecodeerde oplossing bloot te leggen, zullen we de test uitbreiden om er twee waarnemers aan toe te voegen in plaats van één.

 "test moet functie opslaan": function () var observeable = new tddjs.Observable (); var observers = [function () , function () ]; observable.addObserver (waarnemers [0]); observable.addObserver (waarnemers [1]); assertEquals (waarnemers, waarneembare.observers); 

Zoals verwacht mislukt de test. De test verwacht dat functies die als waarnemers worden toegevoegd, moeten worden gestapeld zoals elk element dat aan een wordt toegevoegd pubsub. Om dit te bereiken, zullen we de array-instantiatie naar de constructor verplaatsen en eenvoudig delegeren addObserver naar de rangschikking methode push:

 function Observable () this.observers = [];  functie addObserver (waarnemer) this.observers.push (waarnemer); 

Als deze implementatie is geïmplementeerd, gaat de test opnieuw over, wat bewijst dat we voor de hardgecodeerde oplossing hebben gezorgd. De kwestie van de toegang tot een openbaar eigendom en het maken van wilde veronderstellingen over de implementatie van Observable is echter nog steeds een probleem. Een waarneembaar pubsub moet door een willekeurig aantal objecten kunnen worden waargenomen, maar het is niet van belang voor buitenstaanders hoe of waar de waarneembaar ze opslaat. Idealiter zouden we graag met het waarneembare willen kunnen nagaan of een bepaalde waarnemer is geregistreerd zonder te tasten om zijn binnenkant. We noteren de geur en gaan verder. Later komen we terug om deze test te verbeteren.


Controleren op waarnemers

We zullen een andere methode toevoegen aan Waarneembaar, hasObserver, en gebruik het om een ​​deel van de rommel te verwijderen die we tijdens de implementatie hebben toegevoegd addObserver.


De test

Een nieuwe methode begint met een nieuwe test en het volgende gewenste gedrag voor de hasObserver methode.

 TestCase ("WaarneembareHasObserverTest", "test zou waar moeten zijn als waarnemer": function () var observeerbaar = nieuw tddjs.Observable (); var observer = function () ; observeable.addObserver (observer); assertTrue (observeerbaar .hasObserver (waarnemer)););

We verwachten dat deze test mislukt in het geval van een vermissing hasObserver, wat het doet.


De testpas maken

Nogmaals, we gebruiken de eenvoudigste oplossing die mogelijk de huidige test zou kunnen doorstaan:

 function hasObserver (waarnemer) return true;  Observable.prototype.hasObserver = hasObserver;

Ook al weten we dat dit onze problemen op de lange termijn niet zal oplossen, het houdt de tests groen. Probeert te herzien en te refactoren laat ons met lege handen want er zijn geen voor de hand liggende punten waar we kunnen verbeteren. De tests zijn onze vereisten, en op dit moment alleen nodig hasObserver om waar terug te keren. Om dat te verhelpen, introduceren we nog een test die we verwachten hasObserver naar return false voor een niet-bestaande waarnemer, die kan helpen de echte oplossing te forceren.

 "test moet false retourneren als er geen waarnemers zijn": function () var observeable = new tddjs.Observable (); assertFalse (observeable.hasObserver (function () )); 

Deze test faalt jammerlijk, gezien het feit dat hasObserver altijd geeft waar terug, ons dwingen om de echte implementatie te produceren. Controleren of een waarnemer is geregistreerd, is eenvoudig een kwestie van controleren of de array this.observers het object bevat waaraan oorspronkelijk is doorgegeven addObserver:

 function hasObserver (waarnemer) retourneer this.observers.indexOf (waarnemer)> = 0; 

De Array.prototype.indexOf methode retourneert een getal minder dan 0 als het element niet aanwezig is in de rangschikking, dus controleren of het een getal oplevert dat gelijk is aan of groter is dan 0 zal ons vertellen of de waarnemer bestaat.


Problemen met de browser oplossen

Het uitvoeren van de test in meer dan één browser levert enigszins verrassende resultaten op:

 chris @ laptop: ~ / projecten / waarneembaar $ jstestdriver - test alle ... E Totaal 4 tests (geslaagd: 3; mislukt: 0; fouten: 1) (11.00 ms) Firefox 3.6.12 Linux: uitvoeren van 2 tests (geslaagd: 2 ; Mislukt: 0; fouten 0) (2,00 ms) Microsoft Internet Explorer 6.0 Windows: 2 tests uitvoeren \ (geslaagd: 1; mislukt: 0; fouten 1) (0,00 ms) ObservableHasObserverTest.test zou true moeten retourneren wanneer waarnemerfout \ ( 0.00 ms): Object ondersteunt deze eigenschap of methode niet. Tests mislukt.

Internet Explorer 6 en 7 hebben de test niet doorstaan ​​met de meest algemene foutmeldingen: "Object ondersteunt deze eigenschap of methode niet ". Dit kan op een aantal problemen duiden:

  • we roepen een methode op voor een object dat null is
  • we roepen een methode aan die niet bestaat
  • we hebben toegang tot een woning die niet bestaat

Gelukkig, TDD-ing in kleine stapjes, weten we dat de fout te maken heeft met de recent toegevoegde oproep aan index van op onze waarnemers rangschikking. Het blijkt dat IE 6 en 7 de JavaScript 1.6-methode niet ondersteunen Array.prototype.indexOf (waar we het niet echt de schuld van kunnen geven, het is pas sinds kort gestandaardiseerd met ECMAScript 5, december 2009). Op dit moment hebben we drie opties:

  • Omzeil het gebruik van Array.prototype.indexOf in hasObserver, waardoor native functionaliteit in ondersteunende browsers effectief wordt gedupliceerd.
  • Implementeer Array.prototype.indexOf voor niet-ondersteunende browsers. U kunt ook een helperfunctie implementeren die dezelfde functionaliteit biedt.
  • Gebruik een externe bibliotheek die de ontbrekende methode of een vergelijkbare methode biedt.

Welke van deze benaderingen het meest geschikt is om een ​​bepaald probleem op te lossen, is afhankelijk van de situatie - ze hebben allemaal hun voor- en nadelen. In het belang van Observable self-contained te houden, zullen we eenvoudig implementeren hasObserver in termen van een lus in plaats van de index van bel, effectief rond het probleem werken. Overigens lijkt dat ook het eenvoudigste dat op dit moment zou kunnen werken. Mochten we later in een soortgelijke situatie komen, dan zouden we geadviseerd worden om onze beslissing te heroverwegen. De bijgewerkte hasObserver ziet er als volgt uit:

 function hasObserver (waarnemer) for (var i = 0, l = this.observers.length; i < l; i++)  if (this.observers[i] == observer)  return true;   return false; 

refactoring

Als de balk weer groen is, is het tijd om onze voortgang te bekijken. We hebben nu drie tests, maar twee lijken vreemd op elkaar. De eerste test die we hebben geschreven om de juistheid van te verifiëren addObserver in feite tests voor dezelfde dingen als de test die we schreven om te verifiëren refactoring . Er zijn twee belangrijke verschillen tussen de twee tests: de eerste test is eerder stinken verklaard, omdat deze rechtstreeks toegang heeft tot de array van waarnemers binnen het waarneembare object. De eerste test voegt twee waarnemers toe, zodat beide zijn toegevoegd. We kunnen nu toetreden tot de tests die bevestigen dat alle waarnemers toegevoegd aan het waarneembare feitelijk zijn toegevoegd:

 "test zou functies moeten opslaan": function () var observeable = new tddjs.Observable (); var observers = [function () , function () ]; observable.addObserver (waarnemers [0]); observable.addObserver (waarnemers [1]); assertTrue (observable.hasObserver (waarnemers [0])); assertTrue (observable.hasObserver (waarnemers [1])); 

Waarnemers verwittigen

Het toevoegen van waarnemers en het controleren op hun bestaan ​​is leuk, maar zonder het vermogen om hen op de hoogte te stellen van interessante veranderingen, is Observable niet erg nuttig. Het is tijd om de kennisgevingsmethode te implementeren.


Zorgen dat waarnemers worden genoemd

De belangrijkste taak die wordt gemeld, is het oproepen van alle waarnemers. Om dit te doen, hebben we een manier nodig om te verifiëren dat een waarnemer is genoemd naar het feit. Om te controleren of een functie is aangeroepen, kunnen we een eigenschap op de functie instellen wanneer deze wordt aangeroepen. Om de test te verifiëren, kunnen we controleren of de eigenschap is ingesteld. De volgende test gebruikt dit concept in de eerste test voor melding.

 TestCase ("ObservableNotifyTest", "test zou alle waarnemers moeten aanroepen": function () var observable = new tddjs.Observable (); var observer1 = function () observer1.called = true;; var observer2 = function () observer2.called = true;; observeable.addObserver (observer1); observeable.addObserver (observer2); observeable.notify (); assertTrue (observer1.called); assertTrue (observer2.called););

Om de test te doorstaan, moeten we de array van waarnemers doorlopen en elke functie aanroepen:

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

Argumenten passeren

Momenteel worden de waarnemers gebeld, maar ze krijgen geen gegevens te zien. Ze weten dat er iets is gebeurd - maar dat hoeft niet per se te gebeuren. We zullen ervoor zorgen dat een melding een aantal argumenten bevat, door ze gewoon door te geven aan elke waarnemer:

 "test zou door argumenten moeten gaan": function () var observeable = new tddjs.Observable (); var actueel; observeable.addObserver (function () actual = arguments;); observeable.notify ("String", 1, 32); assertEquals (["String", 1, 32], actueel); 

De test vergelijkt ontvangen en doorgegeven argumenten door de ontvangen argumenten toe te wijzen aan een lokale variabele voor de test. De waarnemer die we zojuist hebben gecreëerd, is in feite een heel eenvoudige, handmatige testspion. Het uitvoeren van de test bevestigt dat het mislukt, wat niet zo verwonderlijk is omdat we momenteel de argumenten binnen de melding niet aanraken.

Om de test te halen, kunnen we toepassen toepassen bij het aanroepen van de waarnemer:

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

Met deze eenvoudige reparatietests gaat u terug naar groen. Merk op dat we dit hebben ingestuurd als het eerste argument om toe te passen, wat betekent dat waarnemers zullen worden opgeroepen met het waarneembare als dit.


Foutafhandeling

Op dit moment is Observable functioneel en hebben we tests die het gedrag ervan verifiëren. De tests verifiëren echter alleen dat de observeerbare objecten zich correct gedragen als reactie op de verwachte invoer. Wat gebeurt er als iemand probeert een object te registreren als waarnemer in plaats van een functie? Wat gebeurt er als een van de waarnemers opblaast? Dat zijn vragen die we met onze tests moeten beantwoorden. Zorgen voor correct gedrag in verwachte situaties is belangrijk - dat is wat onze objecten het vaakst zullen doen. Althans, dat kunnen we hopen. Correct gedrag, zelfs wanneer de klant zich misdraagt, is net zo belangrijk om een ​​stabiel en voorspelbaar systeem te garanderen.


Bogus Observers toevoegen

De huidige implementatie accepteert blindelings elk argument addObserver. Hoewel onze implementatie elke functie als waarnemer kan gebruiken, kan deze geen waarde aan. De volgende test verwacht dat de waarneembare een uitzondering vormt bij het proberen een waarnemer toe te voegen die niet opvraagbaar is.

 "test zou moeten gooien voor niet-waarneembare waarnemer": function () var observeable = new tddjs.Observable (); assertException (function () observable.addObserver ();, "TypeError"); 

Door al bij het toevoegen van de waarnemers een uitzondering te werpen hoeven we ons later geen zorgen te maken over ongeldige gegevens wanneer we waarnemers waarschuwen. Hadden we per contract geprogrammeerd, dan zouden we kunnen zeggen dat dit een voorwaarde is voor de addObserver methode is dat de invoer calleerbaar moet zijn. De postconditie is dat de waarnemer wordt toegevoegd aan het waarneembare en gegarandeerd wordt gebeld zodra de waarneembare oproepen melden.

De test mislukt, dus we verleggen onze focus om de balk zo snel mogelijk weer groen te maken. Helaas is er geen manier om de implementatie hiervan te faken - het werpen van een uitzondering op elke oproep naar addObserver zal alle andere tests niet doorstaan. Gelukkig is de implementatie vrij triviaal:

 functie addObserver (observator) if (typeof observer! = "function") gooi nieuwe TypeError ("waarnemer is geen functie");  this.observers.push (waarnemer); 

addObserver controleert nu of de waarnemer in feite een functie is voordat deze aan de lijst wordt toegevoegd. Het uitvoeren van de tests levert dat zoete gevoel van succes op: Allemaal groen.


Waarachtig waarnemers

Het waarneembare garandeert nu dat elke waarnemer er doorheen gaat addObserver is opvraagbaar. Toch kan een melding nog steeds falen als een waarnemer een uitzondering genereert. De volgende test verwacht dat alle waarnemers worden opgeroepen, zelfs als een van hen een uitzondering genereert.

 "test moet alles melden, zelfs wanneer sommigen falen": function () var observeable = new tddjs.Observable (); var observer1 = function () throw new Error ("Oops"); ; var observer2 = function () observer2.called = true; ; observable.addObserver (observer1); observable.addObserver (observer2); observable.notify (); assertTrue (observer2.called); 

Het uitvoeren van de test onthult dat de huidige implementatie samen met de eerste waarnemer wordt opgeblazen, waardoor de tweede waarnemer niet wordt gebeld. In feite verbreekt de kennisgeving zijn garantie dat hij altijd alle waarnemers zal bellen zodra deze met succes zijn toegevoegd. Om de situatie recht te zetten, moet de methode op het ergste worden voorbereid:

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

De uitzondering wordt in stilte weggegooid. Het is de verantwoordelijkheid van de waarnemer om ervoor te zorgen dat eventuele fouten correct worden afgehandeld, het waarneembare is eenvoudigweg afweren van slecht gedragende waarnemers.


Call Order documenteren

We hebben de robuustheid van de Waarneembare module verbeterd door hem de juiste foutafhandeling te geven. De module kan nu garanties geven voor de werking zolang deze goede input krijgt en hij kan herstellen als een waarnemer niet aan zijn vereisten voldoet. De laatste test die we hebben toegevoegd, maakt echter een veronderstelling over ongedocumenteerde kenmerken van het waarneembare: het veronderstelt dat waarnemers worden geroepen in de volgorde waarin ze zijn toegevoegd. Momenteel werkt deze oplossing omdat we een array hebben gebruikt om de waarnemerslijst te implementeren. Mochten we besluiten dit te veranderen, dan kunnen onze testen breken. We moeten dus beslissen: herformuleren we de test om geen oproepvolgorde aan te nemen, of voegen we gewoon een test toe die een oproeporder verwacht - waardoor de gespreksvolgorde als een functie wordt gedocumenteerd? Oproepvolgorde lijkt een verstandige functie, dus onze volgende test zorgt ervoor dat Observable dit gedrag behoudt.

 "test zou waarnemers moeten bellen in de volgorde waarin ze zijn toegevoegd": function () var observeable = new tddjs.Observable (); var calls = []; var observer1 = function () calls.push (waarnemer1); ; var observer2 = function () calls.push (waarnemer2); ; observable.addObserver (observer1); observable.addObserver (observer2); observable.notify (); assertEquals (observer1, calls [0]); assertEquals (observer2, calls [1]); 

Omdat de implementatie al een array gebruikt voor de waarnemers, slaagt deze test meteen.


Obstakel-objecten observeren

In statische talen met klassieke overerving worden willekeurige objecten waarneembaar gemaakt door subclassing de klasse Observable. De motivatie voor klassieke overerving in deze gevallen komt voort uit de wens om de mechanica van het patroon op één plaats te definiëren en de logica te hergebruiken over enorme hoeveelheden niet-verwante objecten. In JavaScript hebben we verschillende opties voor hergebruik van code tussen objecten, dus we hoeven ons niet te beperken tot een emulatie van het klassieke overervingsmodel.

In het belang van het loskomen van de klassieke emulatie die constructeurs bieden, overweeg de volgende voorbeelden die ervan uitgaan dat tddjs.observable een object is in plaats van een constructor:

Merk op tddjs.extend methode wordt elders in het boek geïntroduceerd en kopieert eigenschappen eenvoudig van het ene object naar het andere.

 // Een enkel waarneembaar object maken var observable = Object.create (tddjs.util.observable); // Uitbreiding van een enkel object tddjs.extend (krant, tddjs.util.observable); // Een constructor die waarneembare objecten maakt functie Newspaper () / * ... * / Newspaper.prototype = Object.create (tddjs.util.observable); // Uitbreiding van een bestaand prototype tddjs.extend (Newspaper.prototype, tddjs.util.observable);

Het eenvoudig uitvoeren van het waarneembare als een enkel object biedt een grote mate van flexibiliteit. Om daar te komen, moeten we de bestaande oplossing refactoren om de constructor kwijt te raken.


De aannemer overbodig maken

Om van de constructor af te komen, moeten we eerst Observable refactoren zodat de constructor geen werk doet. Gelukkig initialiseert de constructor alleen de array van waarnemers, die niet te moeilijk moet zijn om te verwijderen. Alle methoden op Observable.prototype hebben toegang tot de array, dus we moeten ervoor zorgen dat ze allemaal kunnen omgaan met de case waarin deze niet is geïnitialiseerd. Om dit te testen hoeven we slechts één test per methode te schrijven die de betreffende methode oproept voordat iets anders wordt gedaan.

Omdat we al testen hebben die bellen addObserver en hasObserver Voordat we iets anders gaan doen, zullen we ons concentreren op de kennisgevingsmethode. Deze methode wordt alleen daarna getest addObserver is genoemd. Onze volgende tests verwachten dat het mogelijk is om deze methode te bellen voordat er waarnemers aan toegevoegd worden.

 "test mag niet falen als er geen waarnemers zijn": function () var observeable = new tddjs.Observable (); assertNoException (function () observable.notify ();); 

Met deze test kunnen we de constructor legen:

 functie Waarneembaar () 

Het uitvoeren van de tests toont aan dat op één na alle nu faalt, allemaal met dezelfde boodschap: "this.observers is not defined". We zullen één methode tegelijkertijd behandelen. De eerste is addObserver methode:

function addObserver (observer)
if (! this.observers)
this.observers = [];

/ * ... * /

Als de tests opnieuw worden uitgevoerd, wordt zichtbaar dat de update is uitgevoerd addObserver methode repareert alle behalve de twee tests die niet beginnen met het te noemen. Vervolgens zorgen we ervoor dat false direct wordt geretourneerd hasObserver als de array niet bestaat.

 function hasObserver (waarnemer) if (! this.observers) return false;  / * ... * /

We kunnen exact dezelfde fix toepassen om te melden:

 function notify (observer) if (! this.observers) terug;  / * ... * /

De constructor vervangen door een object

Nu dat het bouwer doet niets dat veilig kan worden verwijderd. We voegen dan alle methoden direct toe aan de tddjs.observable voorwerp, welke vervolgens kan worden gebruikt met b.v. Object.create of tddjs.extend waarneembare objecten maken. Merk op dat de naam niet langer wordt geactiveerd omdat het niet langer een constructor is. De bijgewerkte implementatie volgt:

 (functie () function addObserver (waarnemer) / * ... * / functie hasObserver (waarnemer) / * ... * / functie notify () / * ... * / tddjs.observable = addObserver: addObserver, hasObserver : hasObserver, notificeer: notify; ());

Zeker, het verwijderen van de constructor zorgt ervoor dat alle tests tot nu toe zijn afgebroken. De oplossing is echter eenvoudig. Alles wat we moeten doen is de nieuwe verklaring vervangen door een oproep naar Object.create. De meeste browsers ondersteunen dit echter niet Object.create maar toch, we kunnen het opvullen. Omdat de methode niet mogelijk is om perfect te emuleren, zullen we onze eigen versie op de tddjs voorwerp:

 (function () function F ()  tddjs.create = function (object) F.prototype = object; retourneer nieuwe F ();; / * Waarneembare implementatie gaat hier ... * / ());

Met de shim op zijn plaats kunnen we de tests bijwerken in een kwestie die zelfs in oude browsers werkt. De laatste testsuite volgt:

 TestCase ("ObservableAddObserverTest", setUp: function () this.observable = tddjs.create (tddjs.observable);, "test zou functies moeten opslaan": function () var observers = [function () , function () ]; this.observable.addObserver (waarnemers [0]); this.observable.addObserver (waarnemers [1]); assertTrue (this.observable.hasObserver (observers [0])); assertTrue (this.observable .hasObserver (waarnemers [1]));); TestCase ("ObservableHasObserverTest", setUp: function () this.observable = tddjs.create (tddjs.observable);, "test moet false opleveren als er geen waarnemers zijn": function () assertFalse (this.observable.hasObserver ( function () ));); TestCase ("ObservableNotifyTest", setUp: function () this.observable = tddjs.create (tddjs.observable);, "test moet alle waarnemers bellen": function () var observer1 = function () observer1.called = true;; var observer2 = function () observer2.called = true;; this.observable.addO