Testgetriebene JavaScript-Entwicklung in der Praxis

TDD ist ein iterativer Entwicklungsprozess, bei dem jede Iteration mit dem Schreiben eines Tests beginnt, der Teil der von uns implementierten Spezifikation ist. Die kurzen Iterationen ermöglichen eine sofortige Rückmeldung über den Code, den wir schreiben, und schlechte Designentscheidungen sind leichter zu erkennen. Durch das Schreiben der Tests vor einem Produktionscode kommt eine gute Einheitstestabdeckung mit sich, aber das ist nur ein willkommener Nebeneffekt.

Erneut veröffentlichtes Tutorial

Alle paar Wochen besuchen wir einige der Lieblingsbeiträge unserer Leser aus der gesamten Geschichte der Website. Dieses Tutorial wurde erstmals im November 2010 veröffentlicht.


Entwicklung auf den Kopf stellen

Bei der herkömmlichen Programmierung werden Probleme durch Programmierung gelöst, bis ein Konzept vollständig im Code dargestellt ist. Im Idealfall folgt der Code einigen allgemeinen Überlegungen zur Architektur, obwohl dies in vielen Fällen, insbesondere in der JavaScript-Welt, nicht der Fall ist. Diese Art der Programmierung löst Probleme, indem er erraten kann, welcher Code zu deren Lösung erforderlich ist. Diese Strategie kann leicht zu aufgeblähten und eng gekoppelten Lösungen führen. Wenn auch keine Komponententests vorhanden sind, können mit diesem Ansatz erstellte Lösungen sogar Code enthalten, der niemals ausgeführt wird, z. B. Fehlerbehandlungslogik und "flexible" Argumentbehandlung, oder er kann Randfälle enthalten, die nicht gründlich getestet wurden überhaupt.

Testgetriebene Entwicklung stellt den Entwicklungszyklus auf den Kopf. Anstatt sich auf den zur Lösung eines Problems erforderlichen Code zu konzentrieren, beginnt die testgetriebene Entwicklung mit der Definition des Ziels. Unit-Tests bilden sowohl die Spezifikation als auch die Dokumentation, welche Aktionen unterstützt und abgerechnet werden. Zugegeben, das Ziel von TDD ist nicht das Testen, und daher gibt es keine Garantie, dass es z. Randfälle besser. Da jedoch jede Codezeile von einem repräsentativen Beispielcode getestet wird, produziert TDD wahrscheinlich weniger überschüssigen Code, und die Funktionalität, die berücksichtigt wird, ist wahrscheinlich robuster. Eine korrekte testgetriebene Entwicklung stellt sicher, dass ein System niemals Code enthält, der nicht ausgeführt wird.


Der Prozess

Der testgetriebene Entwicklungsprozess ist ein iterativer Prozess, bei dem jede Iteration aus den folgenden vier Schritten besteht:

  • Schreibe einen Test
  • Führen Sie Tests aus und beobachten Sie, wie der neue Test fehlschlägt
  • Machen Sie den Test bestanden
  • Umgestaltung, um Duplizierungen zu entfernen

In jeder Iteration ist der Test die Spezifikation. Sobald genügend Produktionscode (und nicht mehr) für den Test geschrieben wurde, sind wir fertig, und wir können den Code umgestalten, um Duplizierungen zu entfernen und / oder das Design zu verbessern, solange die Tests noch bestehen.


Praktische TDD: Das Beobachtermuster

Das Observer-Muster (auch bekannt als Publish / Subscribe oder einfach) Pubsub) ist ein Entwurfsmuster, mit dem wir den Status eines Objekts beobachten und bei Änderungen benachrichtigt werden können. Das Muster kann Objekte mit leistungsstarken Erweiterungspunkten versehen, während die lose Kopplung erhalten bleibt.

In The Observer gibt es zwei Rollen - Observable und Observer. Der Beobachter ist ein Objekt oder eine Funktion, die benachrichtigt wird, wenn sich der Zustand des Beobachtbaren ändert. Das Observable entscheidet, wann seine Beobachter aktualisiert werden und welche Daten ihnen zur Verfügung gestellt werden. Das Observable bietet normalerweise mindestens zwei öffentliche Methoden: Pubsub, die seine Beobachter über neue Daten informiert, und Pubsub das abonniert Beobachter zu Ereignissen.


Die beobachtbare Bibliothek

Die testgetriebene Entwicklung ermöglicht es uns, bei Bedarf in sehr kleinen Schritten vorzugehen. In diesem ersten realen Beispiel werden wir mit den kleinsten Schritten beginnen. Wenn wir Vertrauen in unseren Code und den Prozess gewinnen, werden wir schrittweise die Größe unserer Schritte erhöhen, wenn die Umstände es erlauben (d. H. Der umzusetzende Code ist trivial genug). Durch das Schreiben von Code in kleinen, häufigen Iterationen können wir unsere API Stück für Stück entwerfen und weniger Fehler machen. Wenn Fehler auftreten, können wir diese schnell beheben, da Fehler leicht zu finden sind, wenn wir jedes Mal Tests durchführen, wenn wir eine Handvoll Codezeilen hinzufügen.


Umgebung einrichten

In diesem Beispiel wird JsTestDriver zum Ausführen von Tests verwendet. Eine Installationsanleitung ist auf der offiziellen Website verfügbar.

Das anfängliche Projektlayout sieht wie folgt aus:

 chris @ laptop: ~ / projects / beobachtbarer $ baum. | - jsTestDriver.conf | - src | '- observable.js' - test '- observable_test.js

Die Konfigurationsdatei ist nur das Minimum JsTestDriver Aufbau:

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

Beobachter hinzufügen

Wir beginnen das Projekt, indem wir ein Mittel implementieren, um Beobachter zu einem Objekt hinzuzufügen. Dies führt uns dazu, den ersten Test zu schreiben, zu beobachten, wie er versagt, auf die schmutzigste Weise bestanden wird und schließlich in etwas Vernünftigeres umgewandelt wird.


Der erste Test

Beim ersten Test wird versucht, einen Beobachter hinzuzufügen, indem Sie die addObserver Methode. Um zu überprüfen, ob dies funktioniert, werden wir stumpf sein und davon ausgehen, dass observable seine Beobachter in einem Array speichert, und überprüfen, ob der Observer das einzige Element in diesem Array ist. Der Test gehört in test / observable_test.js und sieht wie folgt aus:

 TestCase ("ObservableAddObserverTest", "Test sollte Funktion speichern"): function () var observable = neue tddjs.Observable (); var observer = function () ; observable.addObserver (observer); assertEquals (beobachter, observable). Beobachter [0]););

Den Test ausführen und beobachten, dass er fehlschlägt

Auf den ersten Blick ist das Ergebnis unseres ersten Tests verheerend:

 Insgesamt 1 Tests (Bestanden: 0; Fehler: 0; Fehler: 1) (0,00 ms) Firefox 3.6.12 Linux: Führen Sie 1 Tests (bestanden: 0; Fehler: 0; Fehler 1) (0,00 ms) aus. ObservableAddObserverTest.test sollte speichern Funktionsfehler (0,00 ms): \ tddjs ist nicht definiert /test/observable_test.js:3 Tests fehlgeschlagen.

Den Test bestehen

Keine Angst! Versagen ist eigentlich eine gute Sache: Sie sagt uns, wo wir unsere Anstrengungen konzentrieren müssen. Das erste ernsthafte Problem ist, dass tddjs nicht existiert. Fügen wir das Namespace-Objekt in hinzu src / observable.js:

 var tddjs = ;

Das erneute Ausführen der Tests führt zu einem neuen Fehler:

 E Insgesamt 1 Tests (Bestanden: 0; Fehler: 0; Fehler: 1) (0,00 ms) Firefox 3.6.12 Linux: Führen Sie 1 Tests (bestanden: 0; Fehler: 0; Fehler 1) (0,00 ms) aus. ObservableAddObserverTest.test sollte Speicherfunktionsfehler (0,00 ms): \ tddjs.Observable ist kein Konstruktor /test/observable_test.js:3 Tests fehlgeschlagen.

Wir können dieses neue Problem beheben, indem Sie einen leeren Observable-Konstruktor hinzufügen:

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

Wenn Sie den Test erneut ausführen, gelangen Sie direkt zum nächsten Problem:

 E Insgesamt 1 Tests (Bestanden: 0; Fehler: 0; Fehler: 1) (0,00 ms) Firefox 3.6.12 Linux: Führen Sie 1 Tests (bestanden: 0; Fehler: 0; Fehler 1) (0,00 ms) aus. ObservableAddObserverTest.test sollte Speicherfunktionsfehler (0,00 ms): \ observable.addObserver ist keine Funktion /test/observable_test.js:6 Tests fehlgeschlagen.

Fügen wir die fehlende Methode hinzu.

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

Wenn die Methode installiert ist, schlägt der Test anstelle eines fehlenden Observers-Arrays fehl.

 E Insgesamt 1 Tests (Bestanden: 0; Fehler: 0; Fehler: 1) (0,00 ms) Firefox 3.6.12 Linux: Führen Sie 1 Tests (bestanden: 0; Fehler: 0; Fehler 1) (0,00 ms) aus. ObservableAddObserverTest.test sollte Speicherfunktionsfehler (0,00 ms): \ observable.observers ist undefined /test/observable_test.js:8 Tests fehlgeschlagen.

So seltsam es auch erscheinen mag, ich definiere jetzt das Array der Beobachter im Pubsub Methode. Wenn ein Test fehlschlägt, weist TDD uns an, das einfachste zu tun, was möglicherweise funktioniert, egal wie schmutzig es sich anfühlt. Wir werden die Gelegenheit haben, unsere Arbeit zu überprüfen, sobald der Test bestanden ist.

 Funktion addObserver (Beobachter) this.observers = [Beobachter];  Erfolg! Der Test ist nun bestanden:. Insgesamt 1 Tests (Bestanden: 1; Fehler: 0; Fehler: 0) (1,00 ms) Firefox 3.6.12 Linux: 1 Test ausführen (Bestanden: 1; Fehler: 0; Fehler 0) (1,00 ms)

Umgestaltung

Bei der Entwicklung der aktuellen Lösung haben wir den schnellstmöglichen Weg zu einem Bestehenstest gemacht. Nachdem der Balken grün ist, können wir die Lösung überprüfen und alle erforderlichen Änderungen durchführen. Die einzige Regel in diesem letzten Schritt ist, den Balken grün zu halten. Dies bedeutet, dass wir auch in kleinen Schritten umgestalten müssen, um sicherzustellen, dass wir nicht versehentlich etwas kaputt machen.

Die aktuelle Implementierung hat zwei Probleme, mit denen wir uns befassen sollten. Der Test macht detaillierte Annahmen über die Implementierung von Observable und der addObserver Die Implementierung ist zu unserem Test hartcodiert.

Wir werden zuerst die Hard-Codierung ansprechen. Um die hartcodierte Lösung freizulegen, werden wir den Test so erweitern, dass er zwei Beobachter statt einen hinzufügt.

 "test sollte Funktion speichern": function () var observable = new tddjs.Observable (); Var-Beobachter = [Funktion () , Funktion () ]; observable.addObserver (beobachter [0]); observable.addObserver (beobachter [1]); assertEquals (Beobachter, beobachtbare Beobachter); 

Wie erwartet schlägt der Test jetzt fehl. Der Test erwartet, dass als Beobachter hinzugefügte Funktionen wie jedes zu einem Element hinzugefügte Element gestapelt werden sollten Pubsub. Um dies zu erreichen, werden wir die Array-Instantiierung in den Konstruktor verschieben und einfach delegieren addObserver zum Array Methode Push:

 Funktion Observable () this.observers = [];  function addObserver (observer) this.observers.push (observer); 

Mit dieser Implementierung ist der Test erneut bestanden, was zeigt, dass wir uns um die hartcodierte Lösung gekümmert haben. Das Problem, auf ein öffentliches Eigentum zuzugreifen und wilde Annahmen über die Implementierung von Observable zu treffen, ist jedoch immer noch ein Problem. Ein beobachtbarer Pubsub sollte von einer beliebigen Anzahl von Objekten beobachtet werden können, aber es ist für Außenstehende nicht interessant, wie oder wo das Beobachtbare sie speichert. Im Idealfall möchten wir mit dem Beobachtbaren überprüfen, ob ein bestimmter Beobachter registriert ist, ohne um sein Inneres herumzumachen. Wir notieren den Geruch und machen weiter. Später werden wir zurückkommen, um diesen Test zu verbessern.


Nach Beobachtern suchen

Wir werden Observable eine weitere Methode hinzufügen, hasObserver, und verwenden Sie es, um etwas Unordnung zu entfernen, das wir bei der Implementierung hinzugefügt haben addObserver.


Der Test

Eine neue Methode beginnt mit einem neuen Test und dem nächsten gewünschten Verhalten für den hasObserver Methode.

 TestCase ("ObservableHasObserverTest", "Test sollte true zurückgeben, wenn der Observer hat"): function () var observable = neue tddjs.Observable (); var observer = function () ; observable.addObserver (observer); .hasObserver (Beobachter)););

Wir gehen davon aus, dass dieser Test angesichts eines Fehlschlags versagen wird hasObserver, was es tut.


Den Test bestehen

Wieder verwenden wir die einfachste Lösung, die den aktuellen Test möglicherweise bestehen könnte:

 Funktion hasObserver (observer) return true;  Observable.prototype.hasObserver = hasObserver;

Obwohl wir wissen, dass dies unsere Probleme auf lange Sicht nicht lösen wird, bleiben die Tests grün. Der Versuch zu überprüfen und zu überarbeiten, lässt uns mit leeren Händen zurück, da es keine offensichtlichen Punkte gibt, an denen wir uns verbessern können. Die Tests sind unsere Anforderungen und derzeit nur erforderlich hasObserver wahr zurückkehren. Um dies zu beheben, werden wir einen weiteren Test vorstellen, der erwartet hasObserver zu falsch zurückgeben für einen nicht existierenden Beobachter, der die wirkliche Lösung erzwingen kann.

 "test sollte false zurückgeben, wenn keine Beobachter vorhanden sind": function () var observable = new tddjs.Observable (); assertFalse (observable.hasObserver (function () )); 

Dieser Test scheitert kläglich hasObserver immer gibt wahr zurück, Wir zwingen uns, die reale Implementierung zu produzieren. Um zu überprüfen, ob ein Beobachter registriert ist, müssen Sie einfach überprüfen, ob das this.observers-Array das Objekt enthält, an das ursprünglich ein Objekt übergeben wurde addObserver:

 Funktion hasObserver (Observer) return this.observers.indexOf (Observer)> = 0; 

Das Array.prototype.indexOf Methode gibt eine Zahl zurück, die kleiner als ist 0 wenn das Element nicht im vorhanden ist Array, so überprüfen, dass es eine Zahl zurückgibt, die größer oder gleich ist 0 wird uns sagen, ob der Beobachter existiert.


Browser-Inkompatibilitäten beheben

Das Ausführen des Tests in mehreren Browsern führt zu überraschenden Ergebnissen:

 chris @ laptop: ~ / projects / observable $ jstestdriver - testet alle… E Insgesamt 4 Tests (Bestanden: 3; Fehler: 0; Fehler: 1) (11.00 ms) Firefox 3.6.12 Linux: Führen Sie 2 Tests (Bestanden: 2 ; Fehler: 0; Fehler 0) (2,00 ms) Microsoft Internet Explorer 6.0 Windows: Führen Sie 2 Tests aus (bestanden: 1; Fehler: 0; Fehler 1) (0,00 ms). ObservableHasObserverTest.test sollte true zurückgeben, wenn der Beobachterfehler \ ist ( 0,00 ms): Objekt unterstützt diese Eigenschaft nicht oder Methode fehlgeschlagen.

Die Internet Explorer-Versionen 6 und 7 schlugen den Test mit den allgemeinsten Fehlermeldungen fehl: "Objekt unterstützt diese Eigenschaft oder Methode nicht ". Dies kann auf eine beliebige Anzahl von Problemen hinweisen:

  • Wir rufen eine Methode für ein Objekt auf, das null ist
  • Wir nennen eine Methode, die nicht existiert
  • Wir greifen auf eine Eigenschaft zu, die nicht existiert

Glücklicherweise wissen wir, dass TDD in winzigen Schritten verwendet wird, dass der Fehler sich auf den kürzlich hinzugefügten Aufruf beziehen muss Index von auf unsere Beobachter Array. Wie sich herausstellt, unterstützen IE 6 und 7 die JavaScript 1.6-Methode nicht Array.prototype.indexOf (wofür wir es nicht wirklich beschuldigen können, wurde erst kürzlich mit standardisiert ECMAScript 5, Dezember 2009). Zu diesem Zeitpunkt haben wir drei Möglichkeiten:

  • Umgehen Sie die Verwendung von Array.prototype.indexOf in hasObserver, um die native Funktionalität in unterstützenden Browsern effektiv zu duplizieren.
  • Implementieren Sie Array.prototype.indexOf für nicht unterstützende Browser. Alternativ können Sie eine Hilfsfunktion implementieren, die dieselbe Funktionalität bietet.
  • Verwenden Sie eine Drittanbieter-Bibliothek, die entweder die fehlende Methode oder eine ähnliche Methode bereitstellt.

Welcher dieser Ansätze am besten geeignet ist, um ein bestimmtes Problem zu lösen, hängt von der Situation ab - alle haben ihre Vor- und Nachteile. Um das Observable in sich geschlossen zu halten, werden wir es einfach umsetzen hasObserver in Bezug auf eine Schleife anstelle der Index von Aufruf, effektiv um das Problem zu arbeiten. Dies scheint übrigens auch die einfachste Sache zu sein, die an dieser Stelle funktionieren könnte. Sollten wir später in eine ähnliche Situation geraten, wäre es ratsam, unsere Entscheidung noch einmal zu überdenken. Die aktualisiert hasObserver sieht wie folgt aus:

 Funktion hasObserver (observer) für (var i = 0, l = this.observers.length; i < l; i++)  if (this.observers[i] == observer)  return true;   return false; 

Umgestaltung

Wenn die Leiste wieder grün ist, ist es Zeit, unseren Fortschritt zu überprüfen. Wir haben jetzt drei Tests, aber zwei von ihnen scheinen sich merkwürdig ähnlich zu sein. Der erste Test, den wir geschrieben haben, um die Richtigkeit von zu überprüfen addObserver testet grundsätzlich auf die gleichen Dinge wie der Test, den wir zur Verifizierung geschrieben haben Umgestaltung . Es gibt zwei Hauptunterschiede zwischen den beiden Tests: Der erste Test wurde zuvor für stinkend erklärt, da er direkt auf das Observer-Array innerhalb des beobachtbaren Objekts zugreift. Beim ersten Test werden zwei Beobachter hinzugefügt, um sicherzustellen, dass beide hinzugefügt werden. Wir können jetzt die Tests zu einem Test zusammenfassen, der bestätigt, dass alle Beobachter hinzugefügt werden, die dem Beobachtbaren hinzugefügt werden:

 "test sollte Funktionen speichern": function () var observable = new tddjs.Observable (); Var-Beobachter = [Funktion () , Funktion () ]; observable.addObserver (beobachter [0]); observable.addObserver (beobachter [1]); assertTrue (observable.hasObserver (beobachter [0]))); assertTrue (observable.hasObserver (beobachter [1]))); 

Beobachter benachrichtigen

Das Hinzufügen von Beobachtern und das Überprüfen auf ihre Existenz ist schön, aber ohne die Möglichkeit, sie über interessante Änderungen zu informieren, ist Observable nicht sehr nützlich. Es ist Zeit, die Benachrichtigungsmethode zu implementieren.


Sicherstellen, dass Beobachter aufgerufen werden

Die wichtigste Aufgabe, die notify durchführt, ist das Aufrufen aller Beobachter. Um dies zu erreichen, benötigen wir eine Möglichkeit, um zu überprüfen, ob ein Beobachter nachträglich angerufen wurde. Um zu überprüfen, ob eine Funktion aufgerufen wurde, können Sie beim Aufrufen der Funktion eine Eigenschaft festlegen. Um den Test zu überprüfen, können wir prüfen, ob die Eigenschaft festgelegt ist. Der folgende Test verwendet dieses Konzept im ersten Test zum Benachrichtigen.

 Testfall ("ObservableNotifyTest", "Test sollte alle Beobachter aufrufen"): function () var observable = neue 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);;

Um den Test zu bestehen, müssen wir das Observer-Array durchlaufen und jede Funktion aufrufen:

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

Argumente übergeben

Momentan werden die Beobachter gerufen, aber es werden keine Daten eingespeist. Sie wissen, dass etwas passiert ist - aber nicht unbedingt was. Wir machen notify eine beliebige Anzahl von Argumenten und geben sie einfach an jeden Beobachter weiter:

 "test soll Argumente durchlassen": function () var observable = new tddjs.Observable (); var actual; observable.addObserver (function () actual = argumente;); observable.notify ("String", 1, 32); assertEquals (["String", 1, 32], actual); 

Der Test vergleicht die erhaltenen und übergebenen Argumente, indem er die empfangenen Argumente einer lokalen Variablen des Tests zuweist. Der Beobachter, den wir gerade erstellt haben, ist in der Tat ein sehr einfacher manueller Testspion. Das Ausführen des Tests bestätigt, dass der Test fehlschlägt. Dies ist nicht weiter überraschend, da wir derzeit die Argumente innerhalb von notify nicht berühren.

Um den Test zu bestehen, können Sie beim Aufruf des Beobachters den Befehl apply verwenden:

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

Mit diesem einfachen Fixtest gehen die Tests wieder auf grün zurück. Beachten Sie, dass wir dies als erstes Argument eingereicht haben, das heißt, dass Beobachter mit dem Beobachtbaren als diesem aufgerufen werden.


Fehlerbehandlung

Zu diesem Zeitpunkt ist Observable funktionsfähig und es gibt Tests, die sein Verhalten überprüfen. Die Tests überprüfen jedoch nur, dass sich die Observables als Reaktion auf die erwarteten Eingaben korrekt verhalten. Was passiert, wenn jemand versucht, ein Objekt anstelle einer Funktion als Beobachter zu registrieren? Was passiert, wenn einer der Beobachter in die Luft geht? Dies sind Fragen, die wir zur Beantwortung unserer Tests benötigen. Es ist wichtig, ein korrektes Verhalten in erwarteten Situationen sicherzustellen - das ist es, was unsere Objekte meistens tun werden. Zumindest könnten wir hoffen. Ein korrektes Verhalten, auch wenn sich der Client nicht korrekt verhält, ist jedoch ebenso wichtig, um ein stabiles und vorhersehbares System zu gewährleisten.


Betrüger hinzufügen

Die aktuelle Implementierung akzeptiert blind jegliche Art von Argumenten addObserver. Obwohl unsere Implementierung jede Funktion als Beobachter verwenden kann, kann sie keinen Wert verarbeiten. Der folgende Test erwartet, dass das Observable eine Ausnahme auslöst, wenn versucht wird, einen nicht aufrufbaren Observer hinzuzufügen.

 "Test sollte für einen unwiderruflichen Beobachter werfen": function () var observable = new tddjs.Observable (); assertException (function () observable.addObserver ();, "TypeError"); 

Wenn Sie bereits beim Hinzufügen der Beobachter eine Ausnahme auslösen, müssen Sie sich später keine Gedanken über ungültige Daten machen, wenn wir die Beobachter benachrichtigen. Hätten wir vertraglich programmiert, könnten wir sagen, dass dies eine Voraussetzung für die addObserver Methode ist, dass die Eingabe aufrufbar sein muss. Das Nachbedingung ist, dass der Beobachter zum Beobachtbaren hinzugefügt wird und garantiert garantiert wird, sobald die beobachtbaren Aufrufe benachrichtigt werden.

Der Test schlägt fehl, daher konzentrieren wir uns darauf, den Balken so schnell wie möglich wieder grün zu machen. Leider gibt es keine Möglichkeit, die Implementierung so zu simulieren, dass bei jedem Aufruf eine Ausnahme ausgelöst wird addObserver Alle anderen Tests werden fehlschlagen. Zum Glück ist die Implementierung ziemlich trivial:

 function addObserver (observer) if (Beobachtertyp! = "Funktion") Neuen TypeError werfen ("Beobachter ist keine Funktion");  this.observers.push (Beobachter); 

addObserver prüft nun, ob der Beobachter tatsächlich eine Funktion ist, bevor er der Liste hinzugefügt wird. Die Tests führen zu dem süßen Gefühl des Erfolgs: Alles grün.


Beobachter falsch benehmen

Das Observable garantiert nun, dass sich jeder Beobachter durchgesetzt hat addObserver ist anrufbar. Dennoch kann die Benachrichtigung immer noch schrecklich fehlschlagen, wenn ein Beobachter eine Ausnahme auslöst. Der nächste Test erwartet, dass alle Beobachter gerufen werden, selbst wenn einer von ihnen eine Ausnahme auslöst.

 "test sollte alle benachrichtigen, auch wenn einige fehlschlagen": function () var observable = new tddjs.Observable (); var observer1 = function () werfen neuen Fehler ("Oops"); ; var observer2 = function () observer2.called = true; ; observable.addObserver (observer1); observable.addObserver (observer2); observable.notify (); assertTrue (observer2.called); 

Der Test zeigt, dass die aktuelle Implementierung zusammen mit dem ersten Beobachter explodiert und der zweite Beobachter nicht aufgerufen wird. In der Tat verstößt notify gegen die Garantie, dass alle Beobachter aufgerufen werden, sobald sie erfolgreich hinzugefügt wurden. Um die Situation zu korrigieren, muss die Methode auf das Schlimmste vorbereitet sein:

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

Die Ausnahme wird stillschweigend verworfen. Es liegt in der Verantwortung des Beobachters, sicherzustellen, dass Fehler richtig gehandhabt werden. Das Beobachtbare ist nur, um sich schlecht verhaltene Beobachter abzuwehren.


Anrufreihenfolge dokumentieren

Wir haben die Robustheit des Observable-Moduls durch eine korrekte Fehlerbehandlung verbessert. Das Modul ist jetzt in der Lage, Betriebsgarantien zu geben, solange es gute Eingaben erhält und sich erholen kann, falls ein Beobachter seine Anforderungen nicht erfüllt. Der letzte von uns hinzugefügte Test geht jedoch von nicht dokumentierten Merkmalen des Beobachtbaren aus: Es wird davon ausgegangen, dass Beobachter in der Reihenfolge ihres Hinzufügens aufgerufen werden. Derzeit funktioniert diese Lösung, weil wir zur Implementierung der Beobachterliste ein Array verwendet haben. Sollten wir uns dazu entschließen, dies zu ändern, können unsere Tests jedoch brechen. Wir müssen also entscheiden: Müssen wir den Test so umgestalten, dass er die Anrufreihenfolge nicht annimmt, oder fügen wir einfach einen Test hinzu, der die Anrufreihenfolge erwartet - und damit die Anrufreihenfolge als Funktion dokumentiert? Die Anrufreihenfolge scheint eine vernünftige Funktion zu sein, sodass der nächste Test sicherstellen wird, dass Observable dieses Verhalten beibehält.

 "test sollte Beobachter in der Reihenfolge aufrufen, in der sie hinzugefügt wurden": function () var observable = new tddjs.Observable (); var ruft = [] auf; var observer1 = function () ruft.push (observer1) auf; ; var observer2 = function () calls.push (observer2); ; observable.addObserver (observer1); observable.addObserver (observer2); observable.notify (); assertEquals (observer1, ruft [0]) auf; assertEquals (observer2, ruft [1]) auf; 

Da die Implementierung bereits ein Array für die Beobachter verwendet, ist dieser Test sofort erfolgreich.


Beliebige Objekte beobachten

In statischen Sprachen mit klassischer Vererbung werden beliebige Objekte durch sichtbar gemacht Unterklasse die beobachtbare Klasse. Die Motivation für die klassische Vererbung in diesen Fällen beruht auf dem Wunsch, die Mechanik des Musters an einem Ort zu definieren und die Logik über eine große Anzahl nicht zusammenhängender Objekte hinweg wiederzuverwenden. In JavaScript haben wir mehrere Möglichkeiten zur Wiederverwendung von Code zwischen Objekten, sodass wir uns nicht auf eine Emulation des klassischen Vererbungsmodells beschränken müssen.

Um die klassische Emulation, die Konstruktoren bieten, zu durchbrechen, sollten Sie die folgenden Beispiele betrachten, die davon ausgehen, dass tddjs.observable ein Objekt und kein Konstruktor ist:

Beachten Sie das tddjs.extend Die Methode wird an anderer Stelle im Buch eingeführt und kopiert einfach Eigenschaften von einem Objekt zu einem anderen.

 // Erstellen eines einzelnen beobachtbaren Objekts var observable = Object.create (tddjs.util.observable); // Ein einzelnes Objekt erweitern tddjs.extend (newspaper, tddjs.util.observable); // Ein Konstruktor, der beobachtbare Objekte erstellt. Funktion Newspaper () / *… * / Newspaper.prototype = Object.create (tddjs.util.observable); // Erweitern eines vorhandenen Prototyps tddjs.extend (Newspaper.prototype, tddjs.util.observable);

Die einfache Implementierung des Beobachtbaren als einzelnes Objekt bietet viel Flexibilität. Um dorthin zu gelangen, müssen wir die vorhandene Lösung umgestalten, um den Konstruktor loszuwerden.


Den Konstruktor überflüssig machen

Um den Konstruktor loszuwerden, sollten Sie Observable zuerst so umgestalten, dass der Konstruktor keine Arbeit ausführt. Glücklicherweise initialisiert der Konstruktor nur das Observers-Array, das nicht zu schwer zu entfernen sein sollte. Alle Methoden in Observable.prototype greifen auf das Array zu. Wir müssen also sicherstellen, dass alle den Fall behandeln können, bei dem es nicht initialisiert wurde. Um dies zu testen, müssen Sie lediglich einen Test pro Methode schreiben, der die betreffende Methode aufruft, bevor Sie etwas anderes tun.

Da haben wir schon Tests, die diesen Aufruf machen addObserver und hasObserver Bevor wir etwas anderes tun, konzentrieren wir uns auf die Benachrichtigungsmethode. Diese Methode wird erst danach getestet addObserver genannt worden. Bei den nächsten Tests wird erwartet, dass diese Methode vor dem Hinzufügen von Beobachtern aufgerufen werden kann.

 "Test sollte nicht fehlschlagen, wenn keine Beobachter vorhanden sind": function () var observable = new tddjs.Observable (); assertNoException (function () observable.notify ();); 

Mit diesem Test können wir den Konstruktor leeren:

 Funktion Observable () 

Das Ausführen der Tests zeigt, dass bis auf einen jetzt alle fehlschlagen, und zwar mit der gleichen Meldung: "this.observers ist nicht definiert". Wir werden jeweils eine Methode behandeln. Zuerst ist es addObserver Methode:

Funktion addObserver (Beobachter)
if (! this.observers)
this.observers = [];

/ *… * /

Wenn Sie die Tests erneut ausführen, wird angezeigt, dass sie aktualisiert wurden addObserver Diese Methode behebt alle außer den beiden Tests, die nicht mit dem Aufruf beginnen. Als nächstes stellen wir sicher, dass Sie false direkt von zurückgeben hasObserver wenn das Array nicht existiert.

 Funktion hasObserver (observer) if (! this.observers) return false;  / *… * /

Wir können genau das gleiche Update anwenden, um zu benachrichtigen:

 Funktion benachrichtigen (Beobachter) if (! this.observers) return;  / *… * /

Ersetzen des Konstruktors durch ein Objekt

Nun das das Konstrukteur tut nichts, es kann sicher entfernt werden. Wir werden dann alle Methoden direkt zum hinzufügen tddjs.observable Objekt, die dann mit z. Object.create oder tddjs.extend beobachtbare Objekte erstellen. Beachten Sie, dass der Name nicht mehr groß geschrieben wird, da er kein Konstruktor mehr ist. Die aktualisierte Implementierung folgt:

 (Funktion () Funktion addObserver (Beobachter) / *… * / Funktion hasObserver (Beobachter) / *… * / Funktion notify () / *… * / tddjs.observable = addObserver: addObserver, hasObserver : hasObserver, benachrichtigen: benachrichtigen; ());

Durch das Entfernen des Konstruktors werden alle bisherigen Tests abgebrochen. Sie zu reparieren ist jedoch einfach. Alles, was wir tun müssen, ist, die neue Anweisung durch einen Aufruf an zu ersetzen Object.create. Die meisten Browser unterstützen jedoch nicht Object.create aber so können wir es abschneiden. Da die Methode nicht perfekt emuliert werden kann, stellen wir Ihnen eine eigene Version zur Verfügung tddjs Objekt:

 (Funktion () Funktion F ()  tddjs.create = Funktion (Objekt) F.prototype = Objekt; neues F () zurückgeben;; / * Die beobachtbare Implementierung ist hier… * / ());

Mit dem Shim können wir die Tests in einer Angelegenheit