Test de dezvoltare JavaScript în practică

TDD este un proces de dezvoltare iterativ în care fiecare iterație începe prin scrierea unui test care face parte din specificațiile pe care le implementăm. Repetările scurte permit feedback mai rapid asupra codului pe care îl scriem, iar deciziile de proiectare proaste sunt mai ușor de prins. Prin scrierea testelor înaintea oricărui cod de producție, o bună acoperire cu teste unitare vine cu teritoriul, dar acesta este doar un efect secundar binevenit.

Tutorial publicat

La fiecare câteva săptămâni, revizuim câteva postări preferate ale cititorului nostru de-a lungul istoriei site-ului. Acest tutorial a fost publicat pentru prima oară în noiembrie 2010.


Transformarea dezvoltării în sus

În programarea tradițională, problemele sunt rezolvate prin programare până când un concept este reprezentat pe deplin în cod. În mod ideal, codul urmează anumite considerații de proiectare arhitecturală, deși în multe cazuri, probabil mai ales în lumea JavaScript, acest lucru nu este cazul. Acest stil de programare rezolvă problemele prin a ghici ce cod este necesar pentru a le rezolva, o strategie care poate duce cu ușurință la soluții îmbinate și strâns legate. Dacă nu există nici un test de unitate, soluțiile produse cu această abordare pot conține chiar și cod care nu este niciodată executat, cum ar fi logica de manipulare a erorilor și manipularea argumentului "flexibil", sau poate conține cazuri de margine care nu au fost testate temeinic, deloc.

Dezvoltarea bazată pe teste transformă ciclul de dezvoltare cu susul în jos. În loc să se concentreze asupra codului care este necesar pentru a rezolva o problemă, dezvoltarea bazată pe teste începe prin definirea scopului. Testarea unităților formează atât specificația cât și documentația pentru acțiunile care sunt suportate și luate în considerare. Acordat, obiectivul TDD nu este testat și astfel nu există nici o garanție că acesta se ocupă de ex. marginea cazurilor mai bine. Cu toate acestea, deoarece fiecare linie de cod este testată printr-o piesă reprezentativă de cod exemplu, TDD este probabil să producă mai puțin cod în exces și funcționalitatea care este luată în considerare este probabil mai robustă. Dezvoltarea corectă a testului asigură că un sistem nu va conține niciodată cod care nu este executat.


Procesul

Procesul de dezvoltare bazat pe teste este un proces iterativ în care fiecare iterație constă în următoarele patru etape:

  • Scrie un test
  • Executați teste, urmăriți că noul test nu reușește
  • Efectuați testul
  • Refactor pentru a elimina duplicarea

În fiecare iterație, testul este specificația. Odată ce a fost scris suficient cod de producție (și nu mai mult) pentru a face testul, am terminat și putem reface codul pentru a elimina duplicarea și / sau pentru a îmbunătăți designul, atâta timp cât testele persistă.


TDD practic: Modelul de observator

Modelul Observer (cunoscut și sub numele de Publicare / Abonare sau pur și simplu PubSub) este un model de design care ne permite să observăm starea unui obiect și să fim atenți când acesta se schimbă. Modelul poate furniza obiecte cu puncte de extensie puternice, menținând în același timp cuplajul liber.

Există două roluri în Observer - observator și observator. Observatorul este un obiect sau o funcție care va fi anunțată atunci când se schimbă starea observabilă. Observantul decide când să își actualizeze observatorii și ce date să le furnizeze. Observantul oferă de obicei cel puțin două metode publice: PubSub, care își notifică observatorii despre date noi și PubSub care aderă observatorii la evenimente.


Biblioteca observabilă

Dezvoltarea bazată pe dezvoltare ne permite să ne mișcăm în pași foarte mici atunci când este necesar. În acest prim exemplu din lumea reală vom începe cu cele mai mici pași. Pe măsură ce câștigăm încrederea în codul nostru și în proces, vom mări treptat mărimea pașilor noștri atunci când circumstanțele o permit (adică, codul de implementat este destul de trivial). Scrierea codului în iterații mici frecvente ne va ajuta să proiectăm API-ul cu bucăți și să ne ajute să facem mai puține greșeli. Când apar greșeli, vom putea să le reparăm repede, deoarece erorile vor fi ușor de urmărit atunci când executăm teste de fiecare dată când adăugăm o serie de linii de cod.


Configurarea mediului

Acest exemplu folosește JsTestDriver pentru a executa teste. Un ghid de configurare este disponibil pe site-ul oficial.

Schema inițială a proiectului arată după cum urmează:

 chris @ laptop: ~ / proiecte / copac observabil $. | --Descriere.conf | '- observable.js' - test '- observable_test.js

Fișierul de configurare este doar minim JsTestDriver configurare:

 server: http: // localhost: 4224 încărcare: - lib / *. js - test / *. js

Adăugarea observatorilor

Vom lansa proiectul prin implementarea unui mijloc de a adăuga observatori la un obiect. Făcând acest lucru ne va duce prin scrierea primului test, urmărind că eșuează, trecând-o în cel mai murdar mod posibil și refăcând-o într-un sens mai sensibil.


Primul test

Primul test va încerca să adauge un observator apelând addObserver metodă. Pentru a verifica dacă acest lucru funcționează, vom fi blondați și vom presupune că observatorii își stochează observatorii într-o matrice și verifică dacă observatorul este singurul element din acea matrice. Testul face parte din testare / observable_test.js și arată după cum urmează:

 TestCase ("ObservableAddObserverTest", "test test should store function": function () var observable = new tddjs.Observable () var observer = function () ; observatori [0]););

Executarea testului și urmărirea acestuia nu reușesc

La prima vedere, rezultatul executării primului test este devastator:

 Total 1 teste (Passed: 0; Fails: 0; Erori: 1) (0.00 ms) Firefox 3.6.12 Linux: Run 1 teste (Passed: 0; eroare de funcționare (0,00 ms): \ tddjs nu este definită /test/observable_test.js:3 Testele au eșuat.

Efectuarea testului

Nu te teme! Eșecul este de fapt un lucru bun: ne spune unde să ne concentrăm eforturile. Prima problemă serioasă este că tddjs nu există. Să adăugăm obiectul namespace în src / observable.js:

 var tddjs = ;

Rularea din nou a testelor generează o nouă eroare:

 E Total 1 teste (Passed: 0; Fails: 0; Errors: 1) (0.00 ms) Firefox 3.6.12 Linux: Run 1 teste (Passed: 0; eroare de funcționare a magazinului (0,00 ms): \ tddjs.Observable nu este un constructor /test/observable_test.js:3 Testele au eșuat.

Putem rezolva această problemă nouă prin adăugarea unui constructor observabil gol:

 var tddjs = ; (funcția () funcția Observabilă ()  tddjs.Observable = Observabil; ());

Executarea testului ne aduce direct la următoarea problemă:

 E Total 1 teste (Passed: 0; Fails: 0; Errors: 1) (0.00 ms) Firefox 3.6.12 Linux: Run 1 teste (Passed: 0; eroare funcția de stocare (0.00 ms): \ observable.addObserver nu este o funcție /test/observable_test.js:6 Testele au eșuat.

Să adăugăm metoda lipsă.

 funcția addObserver ()  Observable.prototype.addObserver = addObserver;

Prin metoda aplicată, testul nu reușește acum în locul unei matrice de observatori lipsă.

 E Total 1 teste (Passed: 0; Fails: 0; Errors: 1) (0.00 ms) Firefox 3.6.12 Linux: Run 1 teste (Passed: 0; eroare de funcționare a magazinului (0,00 ms): \ observable.observers is undefined /test/observable_test.js:8 Testele au eșuat.

Atât de ciudat cum pare, acum voi defini matricea observatorilor din interiorul PubSub metodă. Când un test eșuează, TDD ne instruiește să facem cel mai simplu lucru care ar putea funcționa, indiferent de cât de murdar se simte. Vom primi șansa de a revizui munca noastră după ce trece testul.

 funcția addObserver (observator) this.observers = [observator];  Succes! Testul trece acum:. Total 1 teste (Passed: 1; Fails: 0; Errors: 0) (1,00 ms) Firefox 3.6.12 Linux: Run 1 teste (Passed: 1;

refactorizare

În timp ce dezvoltam soluția actuală, am parcurs cea mai rapidă cale posibilă pentru un test de trecere. Acum că bara este verde, putem examina soluția și putem efectua orice refactorizare pe care o considerăm necesară. Singura regulă din acest ultim pas este de a menține bara verde. Aceasta înseamnă că va trebui să refacem și pași mici, asigurându-ne că nu vom sparge nimic din greșeală.

Actuala implementare are două probleme pe care ar trebui să le abordăm. Testul face presupuneri detaliate cu privire la implementarea lui addObserver implementarea este codificată greu pentru testul nostru.

Vom aborda mai întâi codul greu. Pentru a expune soluția codificată greu, vom mări testarea pentru a adăuga doi observatori în loc de unul.

 "test should save function": funcția () var observable = new tddjs.Observable (); var observatori = [funcția () , funcția () ]; observable.addObserver (observatori [0]); observable.addObserver (observatori [1]); assertEquals (observatori, observable.observers); 

Așa cum era de așteptat, testul eșuează acum. Testul așteaptă ca funcțiile adăugate în calitate de observatori să se suprapună ca orice element adăugat la un PubSub. Pentru a realiza acest lucru, vom muta instantierea matricei în constructor și pur și simplu vom delega addObserver la mulțime push push:

 funcție observabilă () this.observers = [];  funcția addObserver (observator) this.observers.push (observator); 

Odată cu punerea în aplicare, testul trece din nou, dovedind că ne-am ocupat de soluția codată greu. Cu toate acestea, problema accesării unei proprietăți publice și luarea de presupuneri sălbatice cu privire la implementarea lui Observabil este încă o problemă. O observabilitate PubSub ar trebui să fie observabile de orice număr de obiecte, dar nu este deloc interesat de cei din afară cum sau unde se află stocurile observabile. În mod ideal, am dori să fim capabili să verificăm cu observabil dacă un anumit observator este înregistrat fără a se plimba în jurul lui. Facem o notă de miros și mergem mai departe. Mai târziu, vom reveni pentru a îmbunătăți acest test.


Verificarea observatorilor

Vom adăuga o altă metodă la Observabil, hasObserver, și folosiți-o pentru a elimina o parte din aglomerația pe care am adăugat-o atunci când implementăm addObserver.


Testul

O nouă metodă începe cu un nou test și următorul comportament dorit pentru hasObserver metodă.

 TestCase ("ObservableHasObserverTest", "testul ar trebui să se întoarcă la adevărat atunci când are observator": function () var observable = new tddjs.Observable () var observer = functie () observable.addObserver observer .hasObserver (observator)););

Ne așteptăm ca acest test să nu reușească în fața unei lipsește hasObserver, pe care o face.


Efectuarea testului

Din nou, folosim cea mai simplă soluție care ar putea trece testul curent:

 funcția hasObserver (observator) return true;  Observable.prototype.hasObserver = hasObserver;

Chiar dacă știm că acest lucru nu ne va rezolva problemele pe termen lung, vom păstra testul verde. Încercarea de a revizui și refactorul ne lasă cu mâinile goale, deoarece nu există puncte evidente în care să ne putem îmbunătăți. Testele sunt cerințele noastre și, în prezent, acestea necesită numai hasObserver pentru a reveni la adevărat. Pentru a repara că vom introduce un alt test care se așteaptă hasObserver la return false pentru un observator inexistent, care poate ajuta la forțarea soluției reale.

 "testul ar trebui să returneze false atunci când niciun observator": function () var observable = new tddjs.Observable (); assertFalse (observable.hasObserver (funcția () )); 

Acest test nu reușește în mod neplăcut, având în vedere că hasObserver mereu întoarce adevărat, forțându-ne să realizăm implementarea reală. Verificarea faptului dacă un observator este înregistrat este o chestiune simplă de verificare a faptului că matricea this.observers conține obiectul inițial transferat addObserver:

 funcția hasObserver (observator) return this.observers.indexOf (observator)> = 0; 

Array.prototype.indexOf metoda returnează un număr mai mic de 0 dacă elementul nu este prezent în mulțime, astfel încât verificarea faptului că returnează un număr egal sau mai mare decât 0 ne va spune dacă observatorul există.


Rezolvarea incompatibilităților browserului

Rularea testului în mai multe browsere generează rezultate surprinzătoare:

 chris @ laptop: ~ / proiecte / observabile $ jstestdriver - testeaza tot ... E Total 4 teste (trecute: 3; eșuate: 0; erori: 1) (11.00 ms) Firefox 3.6.12 Linux: (0,00 ms) ObservableHasObserverTest.test ar trebui să se întoarcă la adevărat atunci când are eroarea de observație \ (eșuat: 0; erori 0) (2,00 ms) Microsoft Internet Explorer 6.0 Windows: 0.00 ms): Obiectul nu acceptă această proprietate sau metodă Testele au eșuat.

Internet Explorer versiunile 6 și 7 nu au reușit testul cu cele mai generice mesaje de eroare: "Obiectul nu acceptă această proprietate sau metodă ". Acest lucru poate indica orice număr de probleme:

  • numim o metodă pe un obiect care este nul
  • numim o metodă care nu există
  • accesăm o proprietate care nu există

Din fericire, TDD-ing în pași mici, știm că eroarea trebuie să se refere la apelul recent adăugat la Index de pe observatorii noștri mulțime. După cum reiese, IE 6 și 7 nu acceptă metoda JavaScript 1.6 Array.prototype.indexOf (pentru care nu putem vina cu adevărat, a fost doar recent standardizat cu ECMAScript 5, decembrie 2009). În acest moment, avem trei opțiuni:

  • Oblicați utilizarea Array.prototype.indexOf în hasObserver, duplicând în mod efectiv funcționalitatea nativă în sprijinirea browserelor.
  • Implementați Array.prototype.indexOf pentru browserele care nu sunt compatibile. Alternativ, implementați o funcție de ajutor care oferă aceeași funcționalitate.
  • Utilizați o bibliotecă terță parte care oferă fie metoda lipsă, fie o metodă similară.

Care dintre aceste abordări este cea mai potrivită pentru a rezolva o anumită problemă va depinde de situație - toți au argumente pro și contra. În interesul de a păstra observabilitatea autonomă, vom implementa pur și simplu hasObserver în termeni de buclă în locul Index de sunați, lucrați în mod eficient în jurul problemei. De altfel, acest lucru pare, de asemenea, cel mai simplu lucru care ar putea funcționa în acest moment. Ar trebui să fim într-o situație similară mai târziu, ne-ar fi sfătuit să ne reconsiderăm decizia. Actualizată hasObserver arată după cum urmează:

 funcția hasObserver (observator) pentru (var i = 0, l = this.observers.length; i < l; i++)  if (this.observers[i] == observer)  return true;   return false; 

refactorizare

Cu bara înapoi la verde, este timpul să ne revedem progresul. Acum avem trei teste, dar două dintre ele par asemănătoare. Primul test pe care l-am scris pentru a verifica corectitudinea addObserver practic testează pentru aceleași lucruri ca și testul pe care l-am scris pentru a verifica refactorizare . Există două diferențe cheie între cele două teste: primul test a fost declarat anterior mirositor, deoarece accesează direct matricea observatorilor în interiorul obiectului observat. Primul test adaugă doi observatori, asigurându-i că ambii sunt adăugați. Acum se poate adăuga testele într-una care verifică faptul că toți observatorii adăugați la observabil sunt de fapt adăugați:

 "testul ar trebui să stocheze funcții": funcția () var observable = new tddjs.Observable (); var observatori = [funcția () , funcția () ]; observable.addObserver (observatori [0]); observable.addObserver (observatori [1]); assertTrue (observable.hasObserver (observatori [0])); assertTrue (observable.hasObserver (observatori [1])); 

Notificarea observatorilor

Adăugarea de observatori și verificarea existenței lor este plăcută, dar fără posibilitatea de a le notifica schimbări interesante, Observantul nu este foarte util. Este timpul să implementăm metoda de notificare.


Asigurarea faptului că observatorii sunt numiți

Cea mai importantă sarcină de notificare a performanțelor îi cheamă pe toți observatorii. Pentru a face acest lucru, avem nevoie de o modalitate de a verifica dacă un observator a fost chemat după acest fapt. Pentru a verifica dacă o funcție a fost apelată, putem să setăm o proprietate asupra funcției atunci când este apelată. Pentru a verifica testul putem verifica dacă proprietatea este setată. Următorul test utilizează acest concept în primul test pentru notificare.

 TestCase ("ObservableNotifyTest", "testul trebuie să apeleze toți observatorii": function () var observable = new tddjs.Observable () var observer1 = function () observer1.called = true; obserble2.called = true; observable.addObserver (observer1); observable.addObserver (observer2); observable.notify (); assertTrue (observer1.called); assertTrue (observer2.called););

Pentru a trece testul trebuie să bifăm matricea observatorilor și să apelam fiecare funcție:

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

Trecerea Argumentelor

În prezent sunt chemați observatorii, dar nu li se alimentează date. Ei știu că sa întâmplat ceva - dar nu neapărat ce. Vom face notificări să luăm orice număr de argumente, pur și simplu să le transmitem fiecărui observator:

 "testul ar trebui să treacă prin argumente": function () var observable = new tddjs.Observable (); var actual; observable.addObserver (funcție () actual = argumente;); observable.notify ("String", 1, 32); assertEquals (["String", 1, 32], actual); 

Testarea compară argumentele primite și transmise prin atribuirea argumentelor primite unei variabile locale testate. Observatorul pe care tocmai l-am creat este, de fapt, un simplu spion test manual. Rularea testului confirmă că nu reușește, ceea ce nu este surprinzător, deoarece nu atingem în prezent argumentele din notificare.

Pentru a trece testul pe care îl putem aplica atunci când sunăm la observator:

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

Cu aceste teste de fixare simple, reveniți la verde. Rețineți că am trimis acest lucru ca primul argument pe care l-am aplicat, adică că observatorii vor fi chemați cu observabilul ca acesta.


Eroare de manipulare

În acest moment Observabil este funcțional și avem teste care îi verifică comportamentul. Cu toate acestea, testele verifică numai dacă observatorii se comportă corect ca răspuns la intrarea așteptată. Ce se întâmplă dacă cineva încearcă să înregistreze un obiect ca observator în locul unei funcții? Ce se întâmplă dacă unul dintre observatori aruncă în aer? Acestea sunt întrebările la care avem nevoie de testele noastre pentru a răspunde. Asigurarea unui comportament corect în situațiile așteptate este importantă - adică lucrurile pe care obiectele noastre le vor face de cele mai multe ori. Cel puțin așa am putea spera. Cu toate acestea, comportamentul corect, chiar și atunci când clientul este greșit, este la fel de important pentru a garanta un sistem stabil și previzibil.


Adăugarea observatorilor de Bogus

Actuala implementare acceptă orbește orice tip de argument addObserver. Deși implementarea noastră poate folosi orice funcție ca observator, nu poate face față nici unei valori. Următorul test se așteaptă ca observatorul să arunce o excepție atunci când încearcă să adauge un observator care nu este apelat.

 "test ar trebui să arunce pentru observer necalibrat": funcția () var observable = new tddjs.Observable (); assertException (funcția () observable.addObserver ();, "TypeError"); 

Dacă aruncăm o excepție deja când adăugăm observatorii, nu trebuie să ne facem griji cu privire la datele nevalide mai târziu, atunci când le comunicăm observatorilor. Dacă am fi programat prin contract, am putea spune că o condiție prealabilă pentru addObserver metoda este că intrarea trebuie să fie callabilă. postconditie este că observatorul este adăugat la observabil și este garantat să fie numit odată ce apelurile observabile notifică.

Testul eșuează, așa că ne îndreptăm atenția spre a obține din nou bara verde cât mai repede posibil. Din păcate, nu există nici o modalitate de a falsifica punerea în aplicare a acestui lucru - aruncând o excepție de la orice apel la addObserver va eșua toate celelalte teste. Din fericire, implementarea este destul de banală:

 funcția addObserver (observator) if (tipul observer! = "function") aruncați noi TypeError ("observatorul nu este funcțional");  this.observers.push (observator); 

addObserver verifică acum că observatorul este de fapt o funcție înainte de adăugarea lui în listă. Rularea testelor dă un sentiment de succes dulce: toate verde.


Observatorii necorespunzători

Observatul garantează acum că orice observator va fi adăugat addObserver este callabil. Cu toate acestea, notificarea poate continua să nu funcționeze în mod oribil dacă un observator aruncă o excepție. Următorul test așteaptă ca toți observatorii să fie chemați chiar dacă unul dintre ei aruncă o excepție.

 "testul ar trebui să anunțe toate, chiar și atunci când unele eșuează": function () var observable = new tddjs.Observable (); var observer1 = funcția () arunca o nouă eroare ("Oops"); ; var observer2 = funcția () observer2.called = true; ; observable.addObserver (observer1); observable.addObserver (observer2); observable.notify (); assertTrue (observer2.called); 

Rularea testului arată că implementarea actuală suflă împreună cu primul observator, determinând ca cel de-al doilea observator să nu fie apelat. Într-adevăr, notificarea își pierde garanția că va apela întotdeauna toți observatorii odată ce au fost adăugați cu succes. Pentru a remedia situația, metoda trebuie pregătită pentru cel mai rău caz:

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

Excepția este eliminată în tăcere. Este responsabilitatea observatorului să se asigure că orice eroare este tratată în mod corespunzător, observabilul este pur și simplu împiedicat de observatorii care se comportă prost.


Documentarea comenzii de apel

Am îmbunătățit robustețea modulului Observabil, oferindu-i o manevrare corectă a erorilor. Modulul este acum capabil să ofere garanții de funcționare atâta timp cât acesta obține o bună intrare și este capabil să se recupereze în cazul în care un observator nu reușește să-și îndeplinească cerințele. Totuși, ultimul test pe care l-am adăugat face o presupunere asupra caracteristicilor nedocumentate ale observabilului: presupune că observatorii sunt chemați în ordinea în care au fost adăugați. În prezent, această soluție funcționează deoarece am folosit o matrice pentru a implementa lista de observatori. Dacă vom decide să schimbăm acest lucru, testele noastre se pot rupe. Așadar, trebuie să decidem: refăcem testul pentru a nu prelua ordinea de apel sau pur și simplu adăugăm un test care așteaptă ordinul de apel - prin aceasta documentând ordinea apelurilor ca o caracteristică? Ordinea de apel pare a fi o caracteristică sensibilă, astfel încât următorul nostru test să ne asigurăm că Observabil păstrează acest comportament.

 "testul trebuie să apeleze observatori în ordinea în care au fost adăugați": function () var observable = new tddjs.Observable (); var apeluri = []; var observer1 = funcția () calls.push (observer1); ; var observer2 = funcția () calls.push (observator2); ; observable.addObserver (observer1); observable.addObserver (observer2); observable.notify (); assertEquals (observator1, apeluri [0]); assertEquals (observator2, apeluri [1]); 

Deoarece implementarea utilizează deja o matrice pentru observatori, acest test reușește imediat.


Observarea obiectelor arbitrare

În limbile statice cu moștenire clasică, obiectele arbitrare sunt făcute observabile de către subclasarea clasa Observabilă. Motivația moștenirii clasice în aceste cazuri provine dintr-o dorință de a defini mecanica modelului într-un singur loc și de a reutiliza logica în cantități mari de obiecte care nu au legătură. În JavaScript, avem mai multe opțiuni de reutilizare a codului printre obiecte, deci nu trebuie să ne limităm la o emulație a modelului clasic de moștenire.

În scopul eliminării emulației clasice pe care constructorii o oferă, ia în considerare următoarele exemple care presupun că tddjs.observable este un obiect mai degrabă decât un constructor:

Notă: The tddjs.extend metoda este introdusă în altă parte a cărții și copiază pur și simplu proprietățile de la un obiect la altul.

 // Crearea unui singur obiect observabil var observable = Object.create (tddjs.util.observable); // Extinderea unui singur obiect tddjs.extend (ziar, tddjs.util.observable); // Un constructor care creează obiecte observabile funcționează Ziar () / * ... * / Newspaper.prototype = Object.create (tddjs.util.observable); // Extinderea unui prototip existent tddjs.extend (Newspaper.prototype, tddjs.util.observable);

Pur și simplu implementarea observabilă ca un singur obiect oferă o mare flexibilitate. Pentru a ajunge acolo, trebuie să refacem soluția existentă pentru a scăpa de constructor.


Făcând constructorul învechit

Pentru a scăpa de constructor, ar trebui mai întâi să refactorizabil, astfel încât constructorul să nu facă nici o lucrare. Din fericire, constructorul inițializează numai matricea observatorilor, care nu ar trebui să fie prea greu de eliminat. Toate metodele despre Accessible.prototype accesează matricea, deci trebuie să ne asigurăm că toți se pot ocupa de cazul în care nu a fost inițializat. Pentru a testa acest lucru, trebuie doar să scriem un test pe metodă care solicită metoda în cauză înainte de a face orice altceva.

Așa cum deja avem teste care sună addObserver și hasObserver înainte de a face orice altceva, ne vom concentra pe metoda de notificare. Această metodă este testată numai după addObserver a fost chemat. Următoarele teste așteaptă ca această metodă să poată fi apelată înainte de adăugarea oricărui observator.

 "testul nu trebuie să cedeze dacă nu observatori": function () var observable = new tddjs.Observable (); assertNoException (funcția () observable.notify ();); 

Cu acest test, putem să golim constructorul:

 funcție Observabilă () 

Rularea testelor arată că toți, cu excepția unuia, nu reușesc acum, toate cu același mesaj: "this.observers nu este definită". Vom face o singură metodă la un moment dat. Primul este addObserver metodă:

funcția addObserver (observator)
dacă (! this.observers)
this.observers = [];

/ * ... * /

Rularea din nou a testelor arată că actualizările addObserver metoda stabilește toate încercările care nu pornesc prin apelarea acestora. În continuare, ne asigurăm că vom reveni direct la fals hasObserver dacă matricea nu există.

 funcția hasObserver (observator) if (! this.observers) return false;  / * ... * /

Putem aplica exact aceeași remediere pentru a anunța:

 funcția de notificare (observator) if (! this.observers) return;  / * ... * /

Înlocuirea constructorului cu un obiect

Acum, că constructor nu face nimic care poate fi înlăturat în siguranță. Vom adăuga apoi toate metodele direct la tddjs.observable obiect, care poate fi apoi utilizată cu ex. Object.create sau tddjs.extend pentru a crea obiecte observabile. Rețineți că numele nu mai este capitalizat, deoarece nu mai este un constructor. Implementarea actualizată urmează:

 () functie () function * addObserver (observator) / * ... * / functie hasObserver (observer) / * ... * / function notify () tddjs.observable = addObserver: addObserver, hasObserver : hasObserver, notifică: notifică; ());

Desigur, eliminarea constructorului face ca toate testele să se întrerupă până acum. Fixarea lor este însă ușoară. Tot ce trebuie să facem este să înlocuim noua declarație cu un apel către Object.create. Cu toate acestea, majoritatea browserelor nu acceptă Object.create totuși, ca să putem schimba. Deoarece metoda nu este posibilă pentru a emula perfect, vom oferi versiunea proprie pe tddjs obiect:

 (functie () functie F ()  tddjs.create = functie (obiect) F.prototype = obiect; returnare noi F (); / * implementare observabila merge aici ... * /;

Cu ajutorul șurubului, putem actualiza testele într-o chestiune care va funcționa chiar și în browserele vechi. Suita finală de testare urmează:

 TestCase ("ObservableAddObserverTest", setUp: function () this.observable = tddjs.create (tddjs.observable);, "funcția () () [)]; this.observable.addObserver (observatori [0]); this.observable.addObserver (observatori [1]); assertTrue (this.observable.hasObserver (observatori [0])) assertTrue .hasObserver (observatori [1]));); TestCase ("ObservableHasObserverTest", setUp: function () this.observable = tddjs.create (tddjs.observable);, "testul ar trebui sa returneze false atunci cand nici un observator": function () assertFalse (this.observable.hasObserver funcția () ));); TestCase ("ObservableNotifyTest", setUp: function () this.observable = tddjs.create (tddjs.observable);, "test trebuie să apeleze toți observatorii": function () observer1 = = true; var observer2 = function () observer2.called = true;; this.observable.addObserver (observer1); this.observable.addObserver (observer2); this.observable.notify (); assertTrue (observer1; numit); assertTrue (observer2.called);, "testul ar trebui să treacă prin argumente": function () var actual; this.observable.addObserver (function () actual = arguments; "(" String ", 1, 32], actual);" test ar trebui să arunce pentru un observator necalibrat ": function () var observable = this.observable; assertException ) observable.addObserver ();, "TypeError");, "testul ar trebui să notifice toate, chiar și atunci când unele eșuează": function () var observer1 = function ();  var observer2 = funcția () observer2.called = true; ; this.observable.addObserver (observer1); this.observable.addObserver (observer2); this.observable.notify (); assertTrue (observer2.called); , "testul trebuie să apeleze observatori în ordinea în care au fost adăugați": function () var calls = []; var observer1 = funcția () calls.push (observer1); ; var observer2 = funcția () calls.push (observator2); ; this.observable.addObserver (observer1); this.observable.addObserver (observer2); this.observable.notify (); assertEquals (observator1, apeluri [0]); assertEquals (observator2, apeluri [1]); , "testul nu trebuie să cedeze dacă nu observatori": function () var observable = this.observable; assertNoException (funcția () observable.notify ();); );

Pentru a evita duplicarea tddjs.create apel, fiecare caz test a câștigat o înființat meto

Cod