Ein testgetriebener Entwicklungszyklus vereinfacht den Denkprozess beim Schreiben von Code, macht ihn einfacher und auf lange Sicht schneller. Aber das Schreiben von Tests allein reicht nicht aus, es ist die Frage, welche Art von Tests zu schreiben ist und wie der Code so strukturiert ist, dass er diesem Muster entspricht. In diesem Artikel wird beschrieben, wie Sie eine kleine App in Node.js nach einem TDD-Muster erstellen.
Neben einfachen "Unit" -Tests, die wir alle kennen; Wir können auch den Async-Code von Node.js ausführen, der ein Extra hinzufügt Abmessungen Wir wissen nicht immer, in welcher Reihenfolge Funktionen ausgeführt werden, oder wir versuchen, etwas in einem Rückruf zu testen oder zu überprüfen, wie eine asynchrone Funktion funktioniert.
In diesem Artikel erstellen wir eine Node-App, die nach Dateien suchen kann, die einer bestimmten Abfrage entsprechen. Ich weiß, es gibt schon Dinge dafür (ack) Aber um TDD zu demonstrieren, denke ich, könnte es ein gut abgerundetes Projekt sein.
Der erste Schritt besteht natürlich darin, einige Tests zu schreiben, aber vorher müssen wir ein Test-Framework auswählen. Sie können Vanilla Node verwenden, da es einen gibt behaupten
Bibliothek integriert, aber es ist nicht viel in Bezug auf einen Testläufer und ist so ziemlich das Nötigste.
Eine andere Option und wahrscheinlich mein Favorit für den allgemeinen Gebrauch ist Jasmin. Es ist ziemlich eigenständig, Sie haben keine anderen Abhängigkeiten, die Sie Ihren Skripten hinzufügen können, und die Syntax ist sehr sauber und einfach zu lesen. Der einzige Grund, warum ich das heute nicht verwenden werde, ist, weil ich denke, dass Frank Franklin in seiner letzten Tuts + -Serie hier hervorragende Arbeit geleistet hat. Es ist gut, Ihre Optionen zu kennen, damit Sie das beste Werkzeug für Ihre Situation auswählen können.
In diesem Artikel verwenden wir den flexiblen Testläufer „Mocha“ zusammen mit der Chai-Assertionsbibliothek.
Im Gegensatz zu Jasmine, die eher einer ganzen Testsuite in einem Paket ähnelt, kümmert sich Mocha nur um die Gesamtstruktur, hat aber nichts mit den tatsächlichen Assertions zu tun. Dies ermöglicht Ihnen ein konsistentes Aussehen und Verhalten bei der Durchführung Ihrer Tests, ermöglicht jedoch auch die Ausführung der Assertionsbibliothek, die am besten zu Ihrer Situation passt.
Wenn Sie beispielsweise die Vanilla-Assert-Library verwenden möchten, können Sie sie mit Mocha koppeln, um Ihren Tests Struktur zu verleihen.
Chai ist eine ziemlich beliebte Option, und es geht auch um Optionen und Modularität. Selbst ohne Plugins gibt es nur drei Standard-Syntaxen, die Sie verwenden können, je nachdem, ob Sie einen klassischeren TDD-Stil oder eine ausführlichere BDD-Syntax verwenden möchten.
Nun, da wir wissen, was wir verwenden werden, gehen wir jetzt in die Installation.
Als ersten Schritt installieren wir Mocha global, indem Sie Folgendes ausführen:
npm install -g Mokka
Wenn dies abgeschlossen ist, erstellen Sie einen neuen Ordner für unser Projekt und führen Sie den folgenden Ordner aus:
npm install chai
Dadurch wird eine lokale Kopie von Chai für unser Projekt installiert. Erstellen Sie als Nächstes einen Ordner mit dem Namen Prüfung
in unserem Projektverzeichnis, da dies der Standardspeicherort ist, in dem Mocha nach Tests sucht.
Das ist so ziemlich alles fürs Setup. Der nächste Schritt ist, wie Sie Ihre Apps strukturieren, wenn Sie einem testgetriebenen Entwicklungsprozess folgen.
Wenn Sie einem TDD-Ansatz folgen, ist es wichtig zu wissen, was getestet werden muss und was nicht. Als Faustregel gilt, dass Sie keine Tests für andere Personen schreiben, die bereits Code getestet haben. Was ich damit meine, ist folgendes: Angenommen, Ihr Code öffnet eine Datei, Sie müssen die Person nicht testen fs
Funktion, es ist Teil der Sprache und ist angeblich bereits gut getestet. Das Gleiche gilt für die Verwendung von Bibliotheken von Drittanbietern. Sie sollten Funktionen nicht strukturieren, die hauptsächlich diese Arten von Funktionen aufrufen. Sie schreiben nicht wirklich Tests für diese und deshalb haben Sie Lücken im TDD-Zyklus.
Natürlich gibt es bei jedem Programmierstil viele unterschiedliche Meinungen, und die Leute werden unterschiedliche Ansichten darüber haben, wie sie TDD nutzen sollen. Der Ansatz, den ich verwende, ist, dass Sie einzelne Komponenten erstellen, die in Ihrer App verwendet werden sollen. Jede dieser Komponenten löst ein einzigartiges Funktionsproblem. Diese Komponenten werden mit TDD erstellt, um sicherzustellen, dass sie wie erwartet funktionieren und die API nicht beschädigt wird. Dann schreiben Sie Ihr Hauptskript, bei dem es sich im Wesentlichen nur um Klebe-Code handelt, der in bestimmten Situationen nicht getestet werden muss / nicht getestet werden kann.
Dies bedeutet auch, dass die meisten Ihrer Komponenten in der Zukunft wiederverwendet werden können, da sie nicht direkt mit dem Hauptskript zu tun haben.
Nach dem, was ich gerade gesagt habe, ist es üblich, einen Ordner mit dem Namen 'lib
'wo Sie alle einzelnen Komponenten ablegen. Bis zu diesem Punkt sollten Sie also Mocha und Chai installiert haben und dann ein Projektverzeichnis mit zwei Ordnern: 'lib
' und 'Prüfung
'.
Nur für den Fall, dass Sie TDD noch nicht kennen, dachte ich, es wäre eine gute Idee, den Prozess schnell abzudecken. Die Grundregel lautet, dass Sie keinen Code schreiben können, wenn Sie nicht vom Testläufer dazu aufgefordert werden.
Im Wesentlichen schreiben Sie, was Ihr Code tun soll, bevor Sie es tatsächlich tun. Sie haben ein wirklich fokussiertes Ziel beim Programmieren und gehen keine Kompromisse ein, wenn Sie auf der Spur bleiben oder zu weit nach vorne denken. Da der gesamte Code mit einem Test verknüpft ist, können Sie außerdem sicher sein, dass Sie Ihre App in Zukunft niemals brechen werden.
Ein Test ist in Wirklichkeit nur eine Erklärung darüber, was eine Funktion beim Ausführen erwartet, Sie führen dann Ihren Testläufer aus, der offensichtlich fehlschlägt (da Sie den Code noch nicht geschrieben haben), und dann schreiben Sie die Mindestmenge Code benötigt, um den fehlerhaften Test zu bestehen. Es ist wichtig, diesen Schritt niemals zu überspringen, da ein Test manchmal sogar vor dem Hinzufügen von Code durchläuft, da der Code in derselben Klasse oder Funktion enthalten ist. In diesem Fall haben Sie entweder mehr Code geschrieben, als für einen anderen Test vorgesehen war. Dies ist jedoch nur ein schlechter Test (normalerweise nicht spezifisch genug)..
Gemäß unserer obigen Regel können Sie, wenn der Test sofort bestanden wird, keinen Code schreiben, da er Sie nicht dazu aufgefordert hat. Durch kontinuierliches Schreiben von Tests und anschließendes Implementieren der Funktionen erstellen Sie solide Module, auf die Sie sich verlassen können.
Sobald Sie mit der Implementierung und dem Testen Ihrer Komponente fertig sind, können Sie den Code umgestalten, um ihn zu optimieren und zu bereinigen. Stellen Sie jedoch sicher, dass beim Refactoring keine der von Ihnen durchgeführten Tests fehlschlägt und was noch wichtiger ist. t Fügen Sie alle Funktionen hinzu, die nicht getestet wurden.
Jede Testbibliothek hat ihre eigene Syntax, aber sie folgt normalerweise dem gleichen Muster, indem sie Assertionen durchführt und dann prüft, ob sie bestanden haben. Da wir Mocha und Chai verwenden, werfen wir einen Blick auf beide ihre Syntax, beginnend mit Chai.
Ich werde die "Expect" -DDD-Syntax verwenden, da Chai, wie gesagt, mit ein paar Optionen aus der Box kommt. Diese Syntax funktioniert so, dass Sie zunächst die Expect-Funktion aufrufen, das Objekt übergeben, für das Sie eine Assertion durchführen möchten, und dann die Verkettung mit einem bestimmten Test durchführen. Ein Beispiel dessen, was ich meine, könnte wie folgt aussehen:
erwarten (4 + 5) .gleich (9);
Das ist die Grundsyntax, wir erwarten den Zusatz von 4
und 5
gleich sein 9
. Nun, das ist kein guter Test, weil die 4
und 5
wird von Node.js hinzugefügt, noch bevor die Funktion aufgerufen wird. Wir testen also im Grunde meine mathematischen Fähigkeiten, aber ich hoffe, Sie bekommen die allgemeine Idee. Die andere Sache, die Sie beachten sollten, ist, dass diese Syntax im Hinblick auf den Fluss eines normalen englischen Satzes nicht sehr gut lesbar ist. Da Chai dies wusste, fügte er folgende Kettenbeißer hinzu, die nichts tun, aber Sie können sie hinzufügen, um sie ausführlicher und lesbarer zu machen. Die Ketteneinsätze lauten wie folgt:
Mit dem obigen können wir unseren vorherigen Test in etwa wie folgt umschreiben:
erwarten (4 + 5) .zu.gleich (9);
Ich mag das Gefühl der gesamten Bibliothek sehr, das Sie in der API nachlesen können. Einfache Dinge wie das Negieren der Operation sind so einfach wie das Schreiben .nicht
vor dem Test:
erwarten (4 + 5) .nicht gleich (10);
Selbst wenn Sie die Bibliothek noch nie zuvor verwendet haben, ist es nicht schwer herauszufinden, was ein Test versucht.
Das letzte, was ich noch vor dem ersten Test durchgehen möchte, ist die Strukturierung unseres Codes in Mocha
Mocha ist der Testläufer, also kümmert es nicht wirklich um die eigentlichen Tests. Was sie interessiert, ist die Teststruktur, denn so weiß sie, was fehlschlägt und wie die Ergebnisse angeordnet werden. Die Art, wie Sie es aufbauen, besteht darin, mehrere zu erstellen beschreiben
Blöcke, die die verschiedenen Komponenten Ihrer Bibliothek umreißen und dann hinzufügen es
um einen bestimmten Test festzulegen.
Ein kurzes Beispiel: Angenommen, wir hatten eine JSON-Klasse, und diese Klasse hatte eine Funktion zum Parsen von JSON. Wir wollten sicherstellen, dass die Parse-Funktion einen schlecht formatierten JSON-String erkennt. Wir könnten dies folgendermaßen strukturieren:
beschreiben ("JSON", function () beschreiben (". parse ()"), function () it ("sollte fehlerhafte JSON-Zeichenfolgen erkennen", function () // Test Goes Here););) ;
Es ist nicht kompliziert und liegt zu etwa 80% in der persönlichen Präferenz, aber wenn Sie dieses Format beibehalten, sollten die Testergebnisse in einem sehr lesbaren Format erscheinen.
Wir sind jetzt bereit, unsere erste Bibliothek zu schreiben. Beginnen wir mit einem einfachen synchronen Modul, um uns mit dem System vertraut zu machen. Unsere App muss Befehlszeilenoptionen akzeptieren können, um festzulegen, wie viele Ordnerebenen unsere App durchsuchen soll, und die Abfrage selbst.
Um all dies zu erledigen, erstellen wir ein Modul, das die Befehlszeichenfolge akzeptiert und alle enthaltenen Optionen zusammen mit ihren Werten analysiert.
Dies ist ein hervorragendes Beispiel für ein Modul, das Sie in allen Befehlszeilen-Apps wiederverwenden können, da dieses Problem sehr häufig auftritt. Dies ist eine vereinfachte Version eines tatsächlichen Pakets, das ich auf npm habe und als ClTags bezeichnet wird. Erstellen Sie zunächst eine Datei mit dem Namen tags.js
innerhalb des lib-Ordners und dann eine andere Datei mit dem Namen tagsSpec.js
innerhalb des Testordners.
Wir müssen die Chai Expect-Funktion verwenden, da dies die Assertions-Syntax sein wird, die wir verwenden werden, und wir müssen die tatsächliche Tag-Datei einlesen, damit wir sie testen können. Zusammen mit dem ersten Setup sollte es ungefähr so aussehen:
var erwartet = erfordern ("chai"). var tags = required ("… /lib/tags.js"); beschreiben ("Tags", function () );
Wenn Sie den Befehl 'mocha' jetzt von der Wurzel unseres Projekts aus ausführen, sollte alles wie erwartet passieren. Lassen Sie uns nun überlegen, was unser Modul tun wird. Wir wollen das Befehlsargumenten-Array übergeben, das zum Ausführen der App verwendet wurde, und dann möchten, dass ein Objekt mit allen Tags erstellt wird. Es wäre schön, wenn wir auch ein Standardobjekt mit Einstellungen übergeben könnten nichts wird außer Kraft gesetzt, einige Einstellungen sind bereits gespeichert.
Im Umgang mit Tags bieten viele Apps auch Verknüpfungsoptionen, die nur aus einem Zeichen bestehen. Nehmen wir also an, wir wollten die Tiefe unserer Suche festlegen. So könnten wir dem Benutzer die Möglichkeit geben, etwas zu definieren --Tiefe = 2
oder so ähnlich -d = 2
was sollte die gleiche Wirkung haben.
Beginnen wir also mit den langgeformten Tags (zum Beispiel '--depth = 2'). Zunächst schreiben wir den ersten Test:
beschreiben ("Tags", function () beschreiben ("# parse ()"), function () it ("sollte lang geformte Tags analysieren", function () var args = ["--depth = 4", " --hello = world "]; var results = tags.parse (args); erwarten (Ergebnisse) .to.have.a.property (" depth ", 4); erwarten (results) .to.have.a.property ("Hallo Welt"); ); ); );
Wir haben eine Methode zu unserer Testsuite hinzugefügt analysieren
und wir haben einen Test für lange geformte Tags hinzugefügt. In diesem Test habe ich einen Beispielbefehl erstellt und zwei Zusicherungen für die beiden Eigenschaften hinzugefügt, die er übernehmen sollte.
Wenn Sie Mocha jetzt ausführen, sollten Sie einen Fehler erhalten, nämlich den Stichworte
hat keine analysieren
Funktion. Um diesen Fehler zu beheben, fügen wir ein analysieren
Funktion zum Tags-Modul. Ein ziemlich typischer Weg zum Erstellen eines Knotenmoduls ist wie folgt:
exports = module.exports = ; exports.parse = function ()
Der Fehler sagte, wir brauchten eine analysieren
Wir haben es so erstellt, dass wir keinen anderen Code hinzugefügt haben, weil er uns noch nicht gesagt hat. Wenn Sie sich an das Nötigste halten, können Sie sicher sein, dass Sie nicht mehr schreiben werden, als Sie sollen, und ungeprüften Code erhalten.
Lassen Sie uns jetzt Mocha erneut ausführen. Diesmal sollten wir einen Fehler erhalten, der uns mitteilt, dass eine Eigenschaft namens nicht gelesen werden kann Tiefe
von einer undefinierten Variable. Das liegt momentan an unserem analysieren
Funktion gibt nichts zurück, also fügen wir etwas Code hinzu, damit ein Objekt zurückgegeben wird:
exports.parse = function () var options = return-Optionen;
Wir bewegen uns langsam weiter, wenn Sie Mocha erneut ausführen, sollten dies keine Ausnahmen sein, sondern nur eine saubere Fehlermeldung, dass unser leeres Objekt keine Eigenschaft hat Tiefe
.
Jetzt können wir in echten Code einsteigen. Damit unsere Funktion das Tag analysieren und zu unserem Objekt hinzufügen kann, müssen Sie das Arguments-Array durchlaufen und die doppelten Striche am Anfang des Schlüssels entfernen.
exports.parse = function (args) var options = für (var i in args) // Durch args wechseln var arg = args [i]; // Überprüfen Sie, ob das lange geformte Tag if (arg.substr (0, 2) === "-") arg = arg.substr (2); // Auf Gleichheitszeichen prüfen if (arg.indexOf ("=")! == -1) arg = arg.split ("="); var key = arg.shift (); Optionen [Schlüssel] = arg.join ("="); Rückgabeoptionen;
Dieser Code durchläuft die Liste der Argumente, stellt sicher, dass es sich um ein langes Tag handelt, und teilt es dann durch das erste Gleichheitszeichen auf, um das Schlüssel- und Wertepaar für das Optionsobjekt zu erstellen.
Jetzt ist das Problem fast gelöst, aber wenn wir Mocha erneut ausführen, werden Sie feststellen, dass wir jetzt einen Schlüssel für die Tiefe haben, der jedoch auf eine Zeichenfolge anstelle einer Zahl gesetzt ist. Zahlen lassen sich später in unserer App etwas einfacher bearbeiten. Der nächste Code, den wir hinzufügen müssen, ist das Konvertieren von Werten in Zahlen, wann immer dies möglich ist. Dies kann mit einigen RegEx und dem erreicht werden parseInt
Funktion wie folgt:
if (arg.indexOf ("=")! == -1) arg = arg.split ("="); var key = arg.shift (); var value = arg.join ("="); if (/^[0-9++$/.test(value)) value = parseInt (value, 10); Optionen [Schlüssel] = Wert;
Wenn Sie Mocha jetzt ausführen, sollten Sie mit einem Test einen Pass bestehen. Die Nummernkonvertierung sollte sich wahrscheinlich in einem eigenen Test befinden oder zumindest in der Testdeklaration erwähnt werden, damit Sie nicht versehentlich die Assertion der Nummernkonvertierung entfernen. Also einfach Add-On "Add und Convert Numbers" zum es
Deklaration für diesen Test oder teilen Sie es in eine neue es
Block. Es hängt wirklich davon ab, ob Sie dieses offensichtliche Standardverhalten oder eine separate Funktion in Betracht ziehen.
Nun, da ich in diesem ganzen Artikel versucht habe zu betonen, wenn Sie eine vorübergehende Spezifikation sehen, ist es an der Zeit, weitere Tests zu schreiben. Das nächste, was ich hinzufügen wollte, war das Standardarray, also im tagsSpec
Datei Lassen Sie uns Folgendes hinzufügen es
Block direkt nach dem vorherigen:
it ("sollte lang geformte Tags analysieren und Zahlen konvertieren", function () var args = ["--depth = 4", "--hello = world"]); var results = tags.parse (args); results) .have.a.property ("Tiefe", 4); erwarten (Ergebnisse) .to.have.a.property ("hallo", "Welt");); it ("sollte auf Standard zurückfallen", function () var args = ["--depth = 4", "--hello = world"]); var default = tiefe: 2, foo: "bar"; var results = tags.parse (args, default); var erwartet = tiefe: 4, foo: "bar", hallo: "world"; erwartet (results) .to.ep.equal (erwartet););
Hier verwenden wir einen neuen Test, den Deep Equal, der gut ist, um zwei Objekte auf gleiche Werte abzustimmen. Alternativ können Sie auch die Gl
test ist eine Abkürzung, aber ich denke, das ist klarer. Dieser Test übergibt zwei Argumente als Befehlszeichenfolge und übergibt zwei Standardwerte mit einer Überlappung, sodass wir eine gute Verteilung der Testfälle erzielen können.
Wenn Sie jetzt Mocha ausführen, sollten Sie eine Art Diff erhalten, der die Unterschiede zwischen dem, was erwartet wird, und dem, was es tatsächlich hat, enthält.
Lass uns jetzt zurück zum gehen tags.js
modul, und fügen wir diese Funktionalität hinzu. Es ist ein recht einfaches Update, wir müssen nur den zweiten Parameter akzeptieren, und wenn es auf ein Objekt gesetzt ist, können wir das leere Standardobjekt am Anfang durch dieses Objekt ersetzen:
exports.parse = Funktion (Argumente, Standardeinstellungen) var options = ; if (typof defaults === "object" &&! (standardmäßige Instanz von Array)) options = defaults
Dies bringt uns wieder in einen grünen Zustand. Das nächste, was ich hinzufügen möchte, ist die Möglichkeit, ein Tag ohne Wert anzugeben und es wie ein Boolean arbeiten zu lassen. Zum Beispiel, wenn wir nur einstellen --searchContents
oder so ähnlich, es fügt dies einfach zu unserem Options-Array mit dem Wert von hinzu wahr
.
Der Test dafür würde ungefähr so aussehen:
it ("Tags ohne Werte als bool akzeptieren"), function () var args = ["--searchContents"]; var results = tags.parse (args); erwarten (results) .to.have.a.property ("searchContents", true););
Wenn Sie dies ausführen, erhalten Sie wie zuvor folgende Fehlermeldung:
Innerhalb der zum
Wenn wir eine Übereinstimmung für ein langes Tag fanden, haben wir überprüft, ob es ein Gleichheitszeichen enthält. Wir können den Code für diesen Test schnell schreiben, indem Sie eine sonst
Klausel dazu ob
Anweisung und setzen Sie einfach den Wert auf wahr
:
if (arg.indexOf ("=")! == -1) arg = arg.split ("="); var key = arg.shift (); var value = arg.join ("="); if (/^[0-9 ×++//test (value)) value = parseInt (value, 10); Optionen [Schlüssel] = Wert; else Optionen [arg] = wahr;
Das nächste, was ich hinzufügen möchte, ist die Ersetzung der Short-Hand-Tags. Dies ist der dritte Parameter des analysieren
Funktion und wird im Wesentlichen ein Objekt mit Buchstaben und den entsprechenden Ersetzungen sein. Hier ist die Spezifikation für diesen Zusatz:
it ("sollte kurz geformte Tags akzeptieren", function () var args = ["-sd = 4", "-h"]); var ersetzungen = s: "searchContents", d: "depth", h: " hallo "; var results = tags.parse (args, , Ersetzungen); var erwartet = searchContents: true, Tiefe: 4, hallo: true; erwartet (results) .to.deep.equal (erwartet); );
Das Problem bei Kurzschrift-Tags ist, dass sie in einer Reihe kombiniert werden können. Was ich damit verstehe, unterscheidet sich von den lang geformten Tags, bei denen jeder einzeln ist, mit kurzen Hand-Tags - da sie jeweils nur einen Buchstaben lang sind - können Sie drei verschiedene durch Tippen aufrufen -vgh
. Dies macht das Parsen etwas schwieriger, da der Gleichheitsoperator immer noch zulassen muss, damit Sie dem zuletzt genannten Tag einen Wert hinzufügen können, während Sie gleichzeitig die anderen Tags registrieren müssen. Aber keine Sorge, es ist nichts, was nicht durch genug Poppen und Schalten gelöst werden kann.
Hier ist der gesamte Fix vom Anfang des analysieren
Funktion:
exports.parse = Funktion (Argumente, Standardwerte, Ersetzungen) var options = ; if (Typ der Standardwerte === "Objekt" &&! (Standardwerte für Instanz des Arrays)) options = Standardwerte if (Ersatztyp === "Objekt" &&! (Standard für Instanz des Arrays)) für (var i in args) var arg = args [i]; if (arg.charAt (0) === "-" && arg.charAt (1)! = "-") arg = arg.substr (1); if (arg.indexOf ("=")! == -1) arg = arg.split ("="); var keys = arg.shift (); var value = arg.join ("="); arg = keys.split (""); var key = arg.pop (); if (replacements.hasOwnProperty (Schlüssel)) Schlüssel = Ersetzungen [Schlüssel]; args.push ("-" + key + "=" + value); else arg = arg.split (""); arg.forEach (function (key) if (replacements.hasOwnProperty (key)) key = Ersetzungen [key]; args.push ("-" + key););
Es ist viel Code (im Vergleich), aber alles, was wir wirklich tun, ist, das Argument durch ein Gleichheitszeichen aufzuteilen und dann den Schlüssel in die einzelnen Buchstaben zu teilen. Also zum Beispiel, wenn wir bestanden haben -gj = asd
wir würden das teilen asd
in eine Variable namens Wert
, und dann würden wir das teilen gj
Abschnitt in einzelne Zeichen. Das letzte Zeichen (j
in unserem Beispiel) wird der Schlüssel für den Wert (asd
) während alle anderen Buchstaben davor als reguläre boolesche Tags hinzugefügt werden. Ich wollte diese Tags jetzt nicht einfach verarbeiten, nur für den Fall, dass wir die Implementierung später geändert haben. Wir konvertieren also nur diese kurzen Hand-Tags in die langgeformte Version und lassen sie dann später von unserem Skript erledigen.
Wenn Sie Mocha erneut ausführen, werden wir zu unseren illustren grünen Ergebnissen von vier Tests zurückgeführt, die für dieses Modul bestanden wurden.
Nun gibt es noch ein paar weitere Dinge, die wir zu diesem Tag-Modul hinzufügen können, um es näher an das npm-Paket zu bringen, beispielsweise die Möglichkeit, Klartextargumente für Befehle wie Befehle zu speichern oder den gesamten Text am Ende zu sammeln Abfrageeigenschaft Aber dieser Artikel wird schon länger und ich möchte die Suchfunktion implementieren.
Wir haben gerade Schritt für Schritt ein Modul nach einem TDD-Ansatz erstellt und ich hoffe, Sie haben die Idee und das Gefühl bekommen, wie man so schreibt. Aber um diesen Artikel in Bewegung zu halten, werde ich für den Rest des Artikels den Testprozess beschleunigen, indem ich die Dinge in Gruppen zusammenfasse und Ihnen nur die endgültigen Versionen der Tests zeige. Es ist mehr ein Leitfaden für verschiedene Situationen, die auftauchen können und wie Tests für sie geschrieben werden.
Erstellen Sie also einfach eine Datei mit dem Namen search.js
im lib-Ordner und a searchSpec.js
Datei im Testordner.
Als nächstes öffnen Sie die Spezifikationsdatei und lassen Sie uns unseren ersten Test einrichten, bei dem die Funktion eine Liste von Dateien abrufen kann Tiefe
Dies ist auch ein hervorragendes Beispiel für Tests, für die einige externe Einstellungen erforderlich sind. Wenn Sie mit externen objektähnlichen Daten oder in unseren Falldateien arbeiten, sollten Sie ein vordefiniertes Setup haben, von dem Sie wissen, dass es mit Ihren Tests funktioniert. Sie möchten jedoch auch keine gefälschten Informationen zu Ihrem System hinzufügen.
Es gibt im Wesentlichen zwei Optionen, um dieses Problem zu lösen. Sie können entweder die Daten nachahmen, wie oben bereits erwähnt. Wenn Sie sich mit den eigenen Befehlen der Sprache zum Laden von Daten befassen, müssen Sie sie nicht unbedingt testen. In solchen Fällen können Sie einfach die "abgerufenen" Daten bereitstellen und mit dem Testen fortfahren, ähnlich wie bei der Befehlszeichenfolge in der Tag-Bibliothek. In diesem Fall testen wir jedoch die rekursive Funktionalität, die wir je nach angegebener Tiefe zu den Dateilesefunktionen der Sprachen hinzufügen. In solchen Fällen müssen Sie einen Test schreiben. Daher müssen Sie einige Demo-Dateien erstellen, um das Lesen der Datei zu testen. Die Alternative ist vielleicht das Stubben fs
Funktionen laufen einfach, aber nichts, und dann können wir zählen, wie oft unsere gefälschte Funktion ausgeführt wurde oder ähnliches (Auschecken von Spionen), aber für unser Beispiel werde ich nur einige Dateien erstellen.
Mocha bietet Funktionen, die sowohl vor als auch nach Ihren Tests ausgeführt werden können, so dass Sie diese externen Einstellungen und Bereinigungen rund um Ihre Tests durchführen können.
In unserem Beispiel erstellen wir ein paar Testdateien und Ordner in zwei verschiedenen Tiefen, damit wir diese Funktionalität testen können:
var erwartet = erfordern ("chai"). var search = required ("… /lib/search.js"); var fs = required ("fs"); beschreiben ("Suchen", function () beschreiben ("# scan ()"), function () vor (function () if (! fs.existsSync (". test_files")) fs.mkdirSync (". test_files.) "); fs.writeFileSync (". test_files / a "," "); fs.writeFileSync (". test_files / b "," "); fs.mkdirSync (". test_files / dir "); fs.writeFileSync (" .test_files / dir / c "," "); fs.mkdirSync (". test_files / dir2 "); fs.writeFileSync (". test_files / dir2 / d "," ");;); after (function () fs.unlinkSync (". test_files / dir / c"); fs.rmdirSync (". test_files / dir"); fs.unlinkSync (". test_files / dir2 / d"); fs.rmdirSync (". test_files / dir2.) "); fs.unlinkSync (". test_files / a "); fs.unlinkSync (". test_files / b "); fs.rmdirSync (". test_files "););););
Diese werden auf der Basis von aufgerufen beschreiben
blockieren sie sind in, und Sie können sogar Code vor und nach jedem ausführen es
blockieren mit vor jedem
oder nach jedem
stattdessen. Die Funktionen selbst verwenden lediglich Standardknotenbefehle zum Erstellen bzw. Entfernen der Dateien. Als nächstes müssen wir den eigentlichen Test schreiben. Dies sollte direkt neben dem gehen nach dem
Funktion, noch im Inneren beschreiben
Block:
it ("soll die Dateien aus einem Verzeichnis abrufen", function (done) search.scan (". test_files"), 0, function (err, flist) expect (flist) .to.deep.equal ([".test_files / a "," .test_files / b "," .test_files / dir / c "," .test_files / dir2 / d "]); done ();););
Dies ist unser erstes Beispiel für das Testen einer asynchronen Funktion, aber wie Sie sehen, ist es genauso einfach wie zuvor; Wir brauchen nur die erledigt
Funktion Mokka bietet in der es
Erklärungen darüber, wenn wir mit diesem Test fertig sind.
Mocha erkennt automatisch, ob Sie das angegeben haben erledigt
Variable im Callback, und es wartet, bis es aufgerufen wird, sodass Sie asynchronen Code sehr einfach testen können. Es ist auch erwähnenswert, dass dieses Muster im gesamten Mocha verfügbar ist. Sie können dieses zum Beispiel im verwenden Vor
oder nach dem
Funktionen, wenn Sie etwas asynchron einrichten müssen.
Als nächstes möchte ich einen Test schreiben, der sicherstellt, dass der Tiefenparameter funktioniert, wenn gesetzt:
it ("sollte bei einer angegebenen Tiefe anhalten", Funktion (erledigt) search.scan (". test_files"), 1, Funktion (err, flist) expect (flist) .to.deep.equal ([".test_files / a "," .test_files / b "]]); done ();););
Nichts anderes hier, nur ein weiterer Test. Wenn Sie dies in Mocha ausführen, erhalten Sie eine Fehlermeldung, dass die Suche keine Methoden hat, im Wesentlichen, weil wir nichts geschrieben haben. So fügen wir eine Gliederung mit der Funktion hinzu:
var fs = required ("fs"); exports = module.exports = ; exports.scan = Funktion (dir, Tiefe, erledigt)
Wenn Sie jetzt Mocha erneut ausführen, wird das Warten auf die Rückkehr der asynchronen Funktion unterbrochen. Da wir den Callback jedoch überhaupt nicht aufgerufen haben, führt der Test nur zu einer Zeitüberschreitung. Standardmäßig sollte nach ca. zwei Sekunden eine Zeitüberschreitung auftreten. Sie können dies jedoch mit anpassen this.timeout (Millisekunden)
innerhalb eines Beschreibungs- oder Es-Blocks, um deren Zeitüberschreitung anzupassen.
Diese Scanfunktion soll einen Pfad und eine Tiefe annehmen und eine Liste aller gefundenen Dateien zurückgeben. Das ist ziemlich schwierig, wenn Sie darüber nachdenken, wie wir im Wesentlichen zwei verschiedene Funktionen zusammen in einer einzigen Funktion rekursieren. Wir müssen die verschiedenen Ordner erneut durchlaufen und dann müssen sie sich selbst scannen und entscheiden, ob sie weiter gehen möchten.
Es ist in Ordnung, dies synchron zu tun, da Sie Schritt für Schritt schrittweise durchlaufen werden können und langsam jeweils einen Level oder einen Pfad abschließen. Bei einer asynchronen Version wird es etwas komplizierter, da Sie nicht einfach eine für jeden
Schleife oder etwas, da es nicht zwischen Ordnern pausiert, werden alle im Wesentlichen zur gleichen Zeit ausgeführt, wobei jeweils andere Werte zurückgegeben werden, und sie überschreiben sich.
Damit dies funktioniert, müssen Sie eine Art Stapel erstellen, in dem Sie einen asynchronen Prozess (oder alle gleichzeitig, wenn Sie stattdessen eine Warteschlange verwenden) verarbeiten können und dann die Reihenfolge beibehalten. Es ist ein sehr spezieller Algorithmus, also halte ich einen Ausschnitt von Christopher Jeffrey, den Sie in Stack Overflow finden können. Dies gilt nicht nur für das Laden von Dateien, sondern ich habe dies in einer Reihe von Anwendungen verwendet, im Grunde überall dort, wo Sie ein Array von Objekten einzeln mit asynchronen Funktionen bearbeiten müssen.
Wir müssen es ein wenig ändern, da wir gerne eine Tiefenoption haben möchten. Wie die Tiefenoption funktioniert, ist, wie viele Ordnerebenen Sie überprüfen möchten, oder Nullwert auf unbestimmte Zeit.
Hier ist die vollständige Funktion, die das Snippet verwendet:
exports.scan = Funktion (dir, Tiefe, erledigt) Tiefe--; var Ergebnisse = []; fs.readdir (dir, function (err, list) if (err) return done (err); var i = 0; (Funktion next () var file = list [i ++]; if (! file) return done ( null, Ergebnisse); file = dir + '/' + file; fs.stat (Datei, Funktion (err, stat) if (stat && stat.isDirectory ()) if (Tiefe! == 0)