TDD è un processo di sviluppo iterativo in cui ogni iterazione inizia scrivendo un test che fa parte delle specifiche che stiamo implementando. Le brevi iterazioni consentono un feedback più immediato sul codice che stiamo scrivendo e le cattive decisioni di progettazione sono più facili da individuare. Scrivendo i test prima di qualsiasi codice di produzione, una buona copertura del test unitario arriva con il territorio, ma questo è solo un effetto collaterale positivo.
Tutorial ripubblicatoOgni poche settimane, rivisitiamo alcuni dei post preferiti del nostro lettore da tutta la cronologia del sito. Questo tutorial è stato pubblicato per la prima volta nel novembre del 2010.
Nella programmazione tradizionale, i problemi vengono risolti programmando finché un concetto non è completamente rappresentato nel codice. Idealmente, il codice segue alcune considerazioni generali sulla progettazione architettonica, anche se in molti casi, forse soprattutto nel mondo di JavaScript, questo non è il caso. Questo stile di programmazione risolve i problemi indovinando a quale codice è richiesto per risolverli, una strategia che può facilmente portare a soluzioni gonfiate e strettamente accoppiate. Se non ci sono anche test unitari, le soluzioni prodotte con questo approccio possono anche contenere codice che non viene mai eseguito, come la logica di gestione degli errori e la gestione degli argomenti "flessibile", oppure può contenere casi limite che non sono stati testati a fondo, se testati affatto.
Lo sviluppo guidato dai test trasforma il ciclo di sviluppo a testa in giù. Piuttosto che concentrarsi su quale codice è necessario per risolvere un problema, lo sviluppo basato sui test inizia definendo l'obiettivo. I test unitari costituiscono sia la specifica che la documentazione per le azioni supportate e contabilizzate. Certo, l'obiettivo di TDD non è il test e quindi non vi è alcuna garanzia che gestisca ad es. casi limite Tuttavia, poiché ogni riga di codice viene testata da un pezzo rappresentativo di codice di esempio, è probabile che TDD produca meno codice in eccesso e la funzionalità che viene considerata è probabilmente più robusta. Lo sviluppo corretto basato sui test assicura che un sistema non contenga mai codice che non viene eseguito.
Il processo di sviluppo basato su test è un processo iterativo in cui ogni iterazione consiste dei seguenti quattro passaggi:
In ogni iterazione, il test è la specifica. Una volta che è stato scritto un codice di produzione sufficiente (e non più) per fare passare il test, abbiamo finito, e potremmo refactoring il codice per rimuovere la duplicazione e / o migliorare il design, purché i test continuino a passare.
Il pattern Observer (noto anche come Pubblica / Iscriviti o semplicemente PubSub
) è un modello di progettazione che ci consente di osservare lo stato di un oggetto e di essere avvisati quando cambia. Il modello può fornire oggetti con punti di estensione potenti pur mantenendo un accoppiamento lento.
Ci sono due ruoli in The Observer: osservabile e osservatore. L'osservatore è un oggetto o una funzione che verrà notificato quando cambia lo stato dell'osservabile. L'osservabile decide quando aggiornare i suoi osservatori e quali dati fornirli. L'osservabile fornisce in genere almeno due metodi pubblici: PubSub
, che notifica i suoi osservatori di nuovi dati, e PubSub
che sottoscrive gli osservatori agli eventi.
Lo sviluppo basato sui test ci consente di muoverci in piccoli passi quando necessario. In questo primo esempio del mondo reale inizieremo con il più piccolo dei passi. Man mano che acquisiamo sicurezza nel nostro codice e nel processo, aumenteremo gradualmente la dimensione dei nostri passaggi quando le circostanze lo consentiranno (ad esempio, il codice da implementare è abbastanza banale). Scrivere codice in piccole iterazioni frequenti ci aiuterà a progettare le nostre API pezzo per pezzo e ci aiuterà a commettere meno errori. Quando si verificano degli errori, saremo in grado di risolverli rapidamente poiché gli errori saranno facili da rintracciare quando eseguiremo i test ogni volta che aggiungiamo una manciata di righe di codice.
Questo esempio utilizza JsTestDriver per eseguire i test. Una guida di installazione è disponibile dal sito web ufficiale.
Il layout del progetto iniziale ha il seguente aspetto:
chris @ laptop: ~ / projects / osservabile $ tree. | - jsTestDriver.conf | - src | '- observable.js' - test '- observable_test.js
Il file di configurazione è solo il minimo JsTestDriver
configurazione:
server: http: // localhost: 4224 carico: - lib / *. js - test / *. js
Daremo il via al progetto implementando un mezzo per aggiungere osservatori a un oggetto. Fare così ci porterà a scrivere il primo test, a guardarlo fallire, a passarlo nel modo più sporco possibile e infine a rifarlo in qualcosa di più sensato.
Il primo test tenterà di aggiungere un osservatore chiamando il addObserver
metodo. Per verificare che funzioni, saremo schietti e supponiamo che osservabile memorizzi i suoi osservatori in una matrice e controlli che l'osservatore sia l'unico elemento in quella matrice. Il test appartiene a test / observable_test.js
e assomiglia al seguente:
TestCase ("ObservableAddObserverTest", "test dovrebbe memorizzare la funzione": function () var observable = new tddjs.Observable (); var observer = function () ; observable.addObserver (observer); assertEquals (observer, observable. osservatori [0]););
A prima vista, il risultato dell'esecuzione del nostro primo test è devastante:
Totale 1 test (superato: 0; errore: 0; errori: 1) (0,00 ms) Firefox 3.6.12 Linux: eseguire 1 test (superato: 0; errore: 0; errori 1) (0,00 ms) ObservableAddObserverTest.test deve memorizzare errore di funzione (0,00 ms): \ tddjs non è definito /test/observable_test.js:3 Test non riusciti.
Non aver paura! Il fallimento è in realtà una buona cosa: ci dice dove concentrare i nostri sforzi. Il primo problema serio è che tddjs non esiste. Aggiungiamo l'oggetto namespace in src / observable.js
:
var tddjs = ;
L'esecuzione dei test genera nuovamente un nuovo errore:
E Totale 1 test (superato: 0; errore: 0; errori: 1) (0,00 ms) Firefox 3.6.12 Linux: eseguire 1 test (superato: 0; errore: 0; errori 1) (0,00 ms) ObservableAddObserverTest.test dovrebbe Errore funzione archivio (0,00 ms): \ tddjs.Observable non è un costruttore /test/observable_test.js:3 Test non riusciti.
Possiamo risolvere questo nuovo problema aggiungendo un costruttore Observable vuoto:
var tddjs = ; (function () function Observable () tddjs.Observable = Observable; ());
Eseguire il test ancora una volta ci porta direttamente al problema successivo:
E Totale 1 test (superato: 0; errore: 0; errori: 1) (0,00 ms) Firefox 3.6.12 Linux: eseguire 1 test (superato: 0; errore: 0; errori 1) (0,00 ms) ObservableAddObserverTest.test dovrebbe Errore funzione archivio (0,00 ms): \ observable.addObserver non è una funzione /test/observable_test.js:6 Test non riusciti.
Aggiungiamo il metodo mancante.
function addObserver () Observable.prototype.addObserver = addObserver;
Con il metodo in atto, il test ora fallisce al posto di un array di osservatori mancante.
E Totale 1 test (superato: 0; errore: 0; errori: 1) (0,00 ms) Firefox 3.6.12 Linux: eseguire 1 test (superato: 0; errore: 0; errori 1) (0,00 ms) ObservableAddObserverTest.test dovrebbe Errore funzione archivio (0,00 ms): \ observable.observers è indefinito /test/observable_test.js:8 Test non riusciti.
Per quanto possa sembrare strano, definirò l'array di osservatori all'interno di PubSub
metodo. Quando un test fallisce, TDD ci istruisce a fare la cosa più semplice che possa funzionare, non importa quanto sia sporco. Avremo la possibilità di rivedere il nostro lavoro una volta superato il test.
function addObserver (observer) this.observers = [observer]; Successo! Il test ora passa:. Totale 1 test (superato: 1; errore: 0; errori: 0) (1,00 ms) Firefox 3.6.12 Linux: eseguire 1 test (superato: 1; errore: 0; errori 0) (1,00 ms)
Durante lo sviluppo della soluzione attuale abbiamo intrapreso il percorso più veloce possibile per superare un test. Ora che la barra è verde, possiamo esaminare la soluzione ed eseguire qualsiasi refactoring che riteniamo necessario. L'unica regola in questo ultimo passo è mantenere la barra verde. Ciò significa che dovremo effettuare il refactoring anche in piccoli passaggi, assicurandoci di non rompere accidentalmente nulla.
L'attuale implementazione ha due problemi che dovremmo affrontare. Il test formula ipotesi dettagliate sull'implementazione di Observable e il addObserver
l'implementazione è codificata per il nostro test.
Affronteremo prima l'hard-coding. Per esporre la soluzione codificata, aumenteremo il test per aggiungere due osservatori invece di uno.
"test dovrebbe memorizzare funzione": function () var observable = new tddjs.Observable (); var observers = [function () , function () ]; observable.addObserver (osservatori [0]); observable.addObserver (osservatori [1]); assertEquals (observers, observable.observers);
Come previsto, il test ora fallisce. Il test si aspetta che le funzioni aggiunte come osservatori si impilino come qualsiasi elemento aggiunto ad un PubSub
. Per ottenere ciò, sposteremo l'istanziazione dell'array nel costruttore e semplicemente delegheremo addObserver
al schieramento
metodo push:
function Observable () this.observers = []; function addObserver (observer) this.observers.push (observer);
Con questa implementazione in atto, il test passa nuovamente, dimostrando che ci siamo occupati della soluzione codificata. Tuttavia, il problema dell'accesso a una proprietà pubblica e di formulare ipotesi selvagge sull'implementazione di Observable è ancora un problema. Un osservabile PubSub
dovrebbe essere osservabile da un numero qualsiasi di oggetti, ma non interessa agli estranei come o dove li memorizza l'osservabile. Idealmente, vorremmo essere in grado di verificare con l'osservabile se un determinato osservatore è registrato senza brancolare intorno al suo interno. Prendiamo nota dell'odore e andiamo avanti. Più tardi, torneremo per migliorare questo test.
Aggiungeremo un altro metodo a Observable, hasObserver
, e usarlo per rimuovere parte della confusione che abbiamo aggiunto durante l'implementazione addObserver
.
Un nuovo metodo inizia con un nuovo test e il successivo comportamento desiderato per hasObserver
metodo.
TestCase ("ObservableHasObserverTest", "test dovrebbe restituire true quando ha observer": function () var observable = new tddjs.Observable (); var observer = function () ; observable.addObserver (observer); assertTrue (osservabile .hasObserver (observer)););
Ci aspettiamo che questo test fallisca di fronte a una mancanza hasObserver
, che fa.
Ancora una volta, utilizziamo la soluzione più semplice che potrebbe superare il test corrente:
function hasObserver (observer) return true; Observable.prototype.hasObserver = hasObserver;
Anche se sappiamo che questo non risolverà i nostri problemi a lungo termine, mantiene i test verdi. Cercare di fare una revisione e un refactoring ci lascia a mani vuote perché non ci sono punti ovvi in cui possiamo migliorare. I test sono i nostri requisiti e al momento richiedono solo hasObserver
per restituire vero Per risolvere il problema, introdurremo un altro test che si aspetta hasObserver
a restituisce falso
per un osservatore inesistente, che può aiutare a forzare la vera soluzione.
"test dovrebbe restituire false quando non ci sono osservatori": function () var observable = new tddjs.Observable (); assertFalse (observable.hasObserver (function () ));
Questo test fallisce miseramente, dato che hasObserver
sempre ritorna vero,
costringendoci a produrre la vera implementazione. Controllare se un osservatore è registrato è una semplice questione di controllare che l'array this.observers contenga l'oggetto originariamente passato addObserver
:
function hasObserver (observer) return this.observers.indexOf (observer)> = 0;
Il Array.prototype.indexOf
metodo restituisce un numero inferiore a 0
se l'elemento non è presente nel schieramento
, quindi controllando che restituisca un numero uguale o maggiore di 0
ci dirà se l'osservatore esiste.
Eseguire il test in più di un browser produce risultati un po 'sorprendenti:
chris @ laptop: ~ / projects / observable $ jstestdriver --tests all ... E Total 4 test (superato: 3; Fails: 0; Errors: 1) (11,00 ms) Firefox 3.6.12 Linux: Esegui 2 test (superato: 2 ; Errore: 0; Errori 0) (2,00 ms) Microsoft Internet Explorer 6.0 Windows: Esegui 2 test \ (Passato: 1; Errore: 0; Errori 1) (0,00 ms) ObservableHasObserverTest.test dovrebbe restituire true quando ha errore observer \ ( 0,00 ms): l'oggetto non supporta questa proprietà o metodo Test non riusciti.
Le versioni di Internet Explorer 6 e 7 non hanno superato il test con i loro messaggi di errore più generici: "L'oggetto non supporta questa proprietà o metodo ".
Questo può indicare un numero qualsiasi di problemi:
Fortunatamente, TDD-in pochi passi, sappiamo che l'errore deve riguardare la chiamata aggiunta di recente a indice di
sui nostri osservatori schieramento
. Come risulta, IE 6 e 7 non supportano il metodo JavaScript 1.6 Array.prototype.indexOf
(per il quale non possiamo davvero biasimarlo, solo di recente è stato standardizzato con ECMAScript 5, dicembre 2009). A questo punto, abbiamo tre opzioni:
Quale di questi approcci è più adatto a risolvere un determinato problema dipenderà dalla situazione: tutti hanno i loro pro e contro. Nell'interesse di mantenere l'osservabile autosufficiente, implementeremo semplicemente hasObserver
in termini di un ciclo al posto del indice di
chiama, efficacemente aggirando il problema. Per inciso, anche questa sembra essere la cosa più semplice che potrebbe funzionare a questo punto. Se dovessimo imbattersi in una situazione simile più avanti, saremmo invitati a riconsiderare la nostra decisione. L'aggiornato hasObserver
sembra come segue:
function hasObserver (observer) for (var i = 0, l = this.observers.length; i < l; i++) if (this.observers[i] == observer) return true; return false;
Con la barra di nuovo verde, è tempo di rivedere i nostri progressi. Ora abbiamo tre test, ma due sembrano stranamente simili. Il primo test che abbiamo scritto per verificare la correttezza di addObserver
fondamentalmente prova per le stesse cose del test che abbiamo scritto per verificare refactoring
. Ci sono due differenze chiave tra i due test: il primo test è stato precedentemente dichiarato maleodorante, in quanto accede direttamente alla matrice di osservatori all'interno dell'oggetto osservabile. Il primo test aggiunge due osservatori, assicurando che siano entrambi aggiunti. Ora possiamo partecipare ai test in uno che verifica che tutti gli osservatori aggiunti all'osservabile siano effettivamente aggiunti:
"test dovrebbe memorizzare funzioni": function () var observable = new tddjs.Observable (); var observers = [function () , function () ]; observable.addObserver (osservatori [0]); observable.addObserver (osservatori [1]); assertTrue (observable.hasObserver (osservatori [0])); assertTrue (observable.hasObserver (osservatori [1]));
Aggiungere osservatori e verificare la loro esistenza è bello, ma senza la possibilità di notificarli di cambiamenti interessanti, Observable non è molto utile. È ora di implementare il metodo di notifica.
L'attività più importante notificata esegue chiama tutti gli osservatori. Per fare ciò, abbiamo bisogno di un modo per verificare che un osservatore sia stato chiamato dopo il fatto. Per verificare che una funzione sia stata chiamata, possiamo impostare una proprietà sulla funzione quando viene chiamata. Per verificare il test possiamo verificare se la proprietà è impostata. Il seguente test utilizza questo concetto nel primo test per la notifica.
TestCase ("ObservableNotifyTest", "test dovrebbe chiamare tutti gli osservatori": function () var observable = new tddjs.Observable (); var observer1 = function () observer1.called = true;; var observer2 = function () observer2.called = true;; observable.addObserver (observer1); observable.addObserver (observer2); observable.notify (); assertTrue (observer1.called); assertTrue (observer2.called););
Per superare il test, è necessario eseguire il loop della matrice di osservatori e chiamare ciascuna funzione:
function notify () for (var i = 0, l = this.observers.length; i < l; i++) this.observers[i](); Observable.prototype.notify = notify;
Attualmente vengono chiamati gli osservatori, ma non vengono dati loro dati. Sanno che qualcosa è successo - ma non necessariamente cosa. Faremo notifica prendere un numero qualsiasi di argomenti, semplicemente passandoli insieme a ciascun osservatore:
"test dovrebbe passare attraverso argomenti": function () var observable = new tddjs.Observable (); var effettivo; observable.addObserver (function () actual = arguments;); observable.notify ("String", 1, 32); assertEquals (["String", 1, 32], actual);
Il test confronta gli argomenti ricevuti e passati assegnando gli argomenti ricevuti ad una variabile locale al test. L'osservatore che abbiamo appena creato è in realtà una spia di test manuale molto semplice. Esecuzione del test conferma che fallisce, il che non è sorprendente dato che attualmente non stiamo toccando gli argomenti all'interno di notify.
Per superare il test possiamo usare apply quando si chiama l'osservatore:
function notify () for (var i = 0, l = this.observers.length; i < l; i++) this.observers[i].apply(this, arguments);
Con questa semplice correzione, i test tornano al verde. Nota che abbiamo inviato questo come primo argomento da applicare, nel senso che gli osservatori saranno chiamati con l'osservabile come questo.
A questo punto Osservabile è funzionale e abbiamo test che ne verificano il comportamento. Tuttavia, i test verificano solo che gli osservabili si comportino correttamente in risposta all'input previsto. Cosa succede se qualcuno tenta di registrare un oggetto come osservatore al posto di una funzione? Cosa succede se uno degli osservatori esplode? Queste sono domande a cui abbiamo bisogno che i nostri test rispondano. Assicurare un comportamento corretto nelle situazioni previste è importante - questo è ciò che i nostri oggetti faranno la maggior parte del tempo. Almeno così potremmo sperare. Tuttavia, un comportamento corretto anche quando il cliente si comporta male è altrettanto importante per garantire un sistema stabile e prevedibile.
L'attuale implementazione accetta ciecamente qualsiasi tipo di argomento per addObserver
. Sebbene la nostra implementazione possa utilizzare qualsiasi funzione come osservatore, non può gestire alcun valore. Il seguente test si aspetta che l'osservabile lanci un'eccezione quando si tenta di aggiungere un osservatore che non è richiamabile.
"il test deve essere lanciato per l'osservatore non controllabile": function () var observable = new tddjs.Observable (); assertException (function () observable.addObserver ();, "TypeError");
Lanciando un'eccezione già quando si aggiungono gli osservatori non è necessario preoccuparsi di dati non validi in seguito quando si notificano gli osservatori. Se avessimo programmato per contratto, potremmo dire che una precondizione per il addObserver
il metodo è che l'input deve essere chiamabile. Il postcondizione
è che l'osservatore è aggiunto all'osservabile ed è garantito per essere chiamato una volta che le chiamate osservabili notificano.
Il test fallisce, quindi spostiamo l'attenzione per riportare la barra verde il più rapidamente possibile. Sfortunatamente, non c'è modo di simulare l'implementazione: lanciare un'eccezione su ogni chiamata a addObserver
fallirà tutti gli altri test. Fortunatamente, l'implementazione è abbastanza banale:
function addObserver (observer) if (typeof observer! = "function") lanciare un nuovo TypeError ("observer is not function"); this.observers.push (observer);
addObserver
ora controlla che l'osservatore sia effettivamente una funzione prima di aggiungerla alla lista. Eseguendo i test si ottiene quella dolce sensazione di successo: tutto verde.
L'osservabile ora garantisce che ogni osservatore aggiunto addObserver
è chiamabile Tuttavia, la notifica può ancora fallire in modo orribile se un osservatore lancia un'eccezione. Il prossimo test si aspetta che tutti gli osservatori vengano chiamati anche se uno di loro lancia un'eccezione.
"il test dovrebbe notificare tutto anche quando alcuni falliscono": function () var observable = 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);
L'esecuzione del test rivela che l'attuale implementazione esplode insieme al primo osservatore, causando il non richiamo del secondo osservatore. In effetti, la notifica sta infrangendo la sua garanzia che chiamerà sempre tutti gli osservatori una volta che sono stati aggiunti con successo. Per rettificare la situazione, il metodo deve essere preparato al peggio:
function notify () for (var i = 0, l = this.observers.length; i < l; i++) try this.observers[i].apply(this, arguments); catch (e)
L'eccezione viene silenziosamente scartata. È responsabilità dell'osservatore assicurarsi che ogni errore sia gestito correttamente, l'osservatore stia semplicemente respingendo gli osservatori che si comportano male..
Abbiamo migliorato la robustezza del modulo Osservabile dandogli la corretta gestione degli errori. Il modulo è ora in grado di fornire garanzie di funzionamento finché riceve un buon input ed è in grado di recuperare se un osservatore non riesce a soddisfare i suoi requisiti. Tuttavia, l'ultimo test che abbiamo aggiunto fa un'ipotesi sulle caratteristiche non documentate dell'osservabile: presuppone che gli osservatori siano chiamati nell'ordine in cui sono stati aggiunti. Attualmente questa soluzione funziona perché abbiamo utilizzato un array per implementare l'elenco degli osservatori. Se dovessimo decidere di cambiare questo, tuttavia, i nostri test potrebbero interrompersi. Quindi dobbiamo decidere: facciamo il refactoring del test per non assumere l'ordine delle chiamate, o semplicemente aggiungiamo un test che si aspetta l'ordine delle chiamate - documentando così l'ordine delle chiamate come una caratteristica? L'ordine di chiamata sembra una caratteristica sensata, quindi il nostro prossimo test farà in modo che Observable mantenga questo comportamento.
"test dovrebbe chiamare osservatori nell'ordine in cui sono stati aggiunti": function () var observable = new tddjs.Observable (); var calls = []; var observer1 = function () calls.push (observer1); ; var observer2 = function () calls.push (observer2); ; observable.addObserver (Observer1); observable.addObserver (observer2); observable.notify (); assertEquals (observer1, chiama [0]); assertEquals (observer2, chiama [1]);
Poiché l'implementazione utilizza già un array per gli osservatori, questo test ha esito positivo.
Nei linguaggi statici con ereditarietà classica, gli oggetti arbitrari sono resi osservabili da sottoclassi la classe osservabile. La motivazione dell'ereditarietà classica in questi casi deriva dal desiderio di definire la meccanica del modello in un posto e di riutilizzare la logica attraverso vaste quantità di oggetti non correlati. In JavaScript, abbiamo diverse opzioni per il riutilizzo del codice tra gli oggetti, quindi non dobbiamo limitarci a un'emulazione del modello di ereditarietà classica.
Nell'interesse di liberarsi dall'emulazione classica fornita dai costruttori, si consideri i seguenti esempi che assumono che tddjs.observable sia un oggetto piuttosto che un costruttore:
Notare la tddjs.extend
il metodo viene introdotto altrove nel libro e copia semplicemente le proprietà da un oggetto a un altro.
// Creazione di un singolo oggetto osservabile var observable = Object.create (tddjs.util.observable); // Estensione di un singolo oggetto tddjs.extend (giornale, tddjs.util.observable); // Un costruttore che crea oggetti osservabili funzione Newspaper () / * ... * / Newspaper.prototype = Object.create (tddjs.util.observable); // Estensione di un prototipo esistente tddjs.extend (Newspaper.prototype, tddjs.util.observable);
Semplicemente implementando l'osservabile come un singolo oggetto offre una grande flessibilità. Per arrivarci abbiamo bisogno di rifattorizzare la soluzione esistente per sbarazzarsi del costruttore.
Per eliminare il costruttore, dovremmo prima effettuare il refactoring Observable in modo tale che il costruttore non lavori. Fortunatamente, il costruttore inizializza solo l'array di osservatori, che non dovrebbe essere troppo difficile da rimuovere. Tutti i metodi su Observable.prototype accedono all'array, quindi dobbiamo assicurarci che tutti possano gestire il caso in cui non è stato inizializzato. Per testare questo, abbiamo semplicemente bisogno di scrivere un test per metodo che chiama il metodo in questione prima di fare qualsiasi altra cosa.
Dato che abbiamo già test che chiamano addObserver
e hasObserver
prima di fare qualsiasi altra cosa, ci concentreremo sul metodo di notifica. Questo metodo viene testato solo dopo addObserver
è stato chiamato. I nostri prossimi test si aspettano che sia possibile chiamare questo metodo prima di aggiungere qualsiasi osservatore.
"test non dovrebbe fallire se non ci sono osservatori": function () var observable = new tddjs.Observable (); assertNoException (function () observable.notify (););
Con questo test in atto possiamo svuotare il costruttore:
funzione Observable ()
L'esecuzione dei test mostra che tutti tranne uno ora stanno fallendo, tutti con lo stesso messaggio: "this.observers non è definito". Ci occuperemo di un metodo alla volta. Il primo è addObserver
metodo:
function addObserver (observer)
se (! this.observers)
this.observers = [];
/ * ... * /
Eseguendo nuovamente i test si rivela che l'aggiornamento addObserver
il metodo corregge tutti tranne i due test che non iniziano chiamandolo. In seguito, ci assicureremo di restituire il falso direttamente da hasObserver
se la matrice non esiste.
function hasObserver (observer) if (! this.observers) return false; / * ... * /
Possiamo applicare la stessa esatta soluzione per notificare:
function notify (observer) if (! this.observers) return; / * ... * /
Ora che il costruttore
non fa nulla che possa essere rimosso in sicurezza. Quindi aggiungeremo tutti i metodi direttamente a tddjs.observable
oggetto
, che può quindi essere usato con ad es. Object.create o tddjs.extend
per creare oggetti osservabili. Nota che il nome non è più in maiuscolo poiché non è più un costruttore. L'implementazione aggiornata segue:
(function () function addObserver (observer) / * ... * / function hasObserver (observer) / * ... * / function notify () / * ... * / tddjs.observable = addObserver: addObserver, hasObserver : hasObserver, notify: notify; ());
Sicuramente, la rimozione del costruttore causa la rottura di tutti i test. Ripararli è facile, tuttavia. Tutto quello che dobbiamo fare è sostituire la nuova affermazione con una chiamata a Object.create
. Tuttavia, la maggior parte dei browser non supporta Object.create
ancora, così possiamo farlo a pezzi. Poiché non è possibile emulare perfettamente il metodo, forniremo la nostra versione su tddjs
oggetto
:
(function () function F () tddjs.create = function (object) F.prototype = object; return new F ();; / * L'implementazione osservabile va qui ... * / ());
Con lo shim sul posto, possiamo aggiornare i test in una questione che funzionerà anche nei vecchi browser. La suite di test finale segue:
TestCase ("ObservableAddObserverTest", setUp: function () this.observable = tddjs.create (tddjs.observable);, "test dovrebbe memorizzare funzioni": function () var observers = [function () , function () ]; this.observable.addObserver (observers [0]); this.observable.addObserver (observers [1]); assertTrue (this.observable.hasObserver (observers [0])); assertTrue (this.observable .hasObserver (observers [1]));); TestCase ("ObservableHasObserverTest", setUp: function () this.observable = tddjs.create (tddjs.observable);, "test dovrebbe restituire false quando nessun osservatore": function () assertFalse (this.observable.hasObserver ( funzione () )); ); TestCase ("ObservableNotifyTest", setUp: function () this.observable = tddjs.create (tddjs.observable);, "test dovrebbe chiamare tutti gli osservatori": function () var observer1 = function () observer1.called = true;; var observer2 = function () observer2.called = true;; this.observable.addObserver (observer1); this.observable.addObserver (observer2); this.observable.notify (); assertTrue (observer1. chiamato); assertTrue (observer2.called);, "il test deve passare attraverso gli argomenti": function () var actual; this.observable.addObserver (function () actual = arguments;); this.observab