Een testgestuurde ontwikkelingscyclus vereenvoudigt het denkproces van het schrijven van code, maakt het gemakkelijker en sneller op de lange termijn. Maar alleen het schrijven van tests is op zichzelf niet genoeg, wetende wat voor soort tests moeten worden geschreven en hoe code moet worden gestructureerd om aan dit patroon te voldoen, daar gaat het allemaal om. In dit artikel zullen we een kijkje nemen bij het bouwen van een kleine app in Node.js volgens een TDD-patroon.
Naast eenvoudige 'unit'-tests, waarmee we allemaal vertrouwd zijn; We kunnen ook de Async-code van Node.js laten lopen, wat een extra toevoegt dimensie in dat weten we niet altijd de volgorde waarin de functies worden uitgevoerd of proberen we iets te testen tijdens een callback of controleren om te zien hoe een asynchroonfunctie werkt.
In dit artikel zullen we een Node-app bouwen die kan zoeken naar bestanden die overeenkomen met een bepaalde vraag. Ik weet dat er al dingen voor zijn (ack) maar omwille van het demonstreren van TDD denk ik dat het een goed afgerond project kan zijn.
De eerste stap is natuurlijk om enkele tests te schrijven, maar zelfs daarvoor moeten we een testraamwerk kiezen. Je kunt Vanilla Node gebruiken, omdat er een is beweren
ingebouwde bibliotheek, maar het is niet veel in termen van een test runner, en is vrijwel de naakte essentials.
Een andere optie en waarschijnlijk mijn favoriet voor algemeen gebruik is Jasmine. Het is vrij op zichzelf staand, je hebt geen andere afhankelijkheden om toe te voegen aan je scripts en de syntaxis is erg schoon en gemakkelijk te lezen. De enige reden dat ik dit vandaag niet ga gebruiken, is omdat ik denk dat Jack Franklin het uitstekend deed in zijn recente Tuts + -serie hier, en het is goed om je opties te kennen, zodat je de beste tool voor je situatie kunt kiezen.
In dit artikel gebruiken we de flexibele 'Mocha' testrunner samen met de Chai-bibliotheek.
In tegenstelling tot Jasmine, dat meer lijkt op een volledige testsuite in één pakket, zorgt Mocha alleen voor de algehele structuur, maar heeft niets te maken met de werkelijke beweringen. Hierdoor kunt u een consistent uiterlijk en gevoel behouden bij het uitvoeren van uw tests, maar kunt u ook de best bewakingsbibliotheek gebruiken die het best bij uw situatie past..
Als u bijvoorbeeld de vanille 'assert' -bibliotheek zou gaan gebruiken, zou u deze met Mocha kunnen koppelen om wat structuur aan uw tests toe te voegen.
Chai is een redelijk populaire optie en gaat ook over opties en modulariteit. Zelfs zonder plug-ins, gewoon met behulp van de standaard API, hebt u drie verschillende syntaxis die u kunt gebruiken, afhankelijk van of u een meer klassieke TDD-stijl of een meer uitgebreide BDD-syntaxis wilt gebruiken.
Dus nu we weten wat we gaan gebruiken, gaan we naar de installatie.
Laten we om te beginnen Mocha wereldwijd installeren door het volgende uit te voeren:
npm install -g mokka
Wanneer dat is voltooid, maakt u een nieuwe map voor ons project en voert u het volgende daarin uit:
npm chai installeren
Dit installeert een lokale kopie van Chai voor ons project. Maak vervolgens een map met de naam test
in de map van ons project, omdat dit de standaardlocatie is waar Mocha naar zoekt.
Dat is vrijwel alles voor de installatie, de volgende stap is om te praten over hoe je je apps kunt structureren als je een testgestuurd ontwikkelproces volgt.
Het is belangrijk om te weten wanneer een TDD-benadering wordt gevolgd, wat tests moet hebben en wat niet. Een vuistregel is om geen tests te schrijven voor geteste code van andere mensen. Wat ik hiermee bedoel is het volgende: laten we zeggen dat uw code een bestand opent, u hoeft het individu niet te testen fs
functie, het is onderdeel van de taal en is vermoedelijk al goed getest. Hetzelfde geldt voor het gebruik van bibliotheken van derden, u moet geen functies structureren die in de eerste plaats dit soort functies noemen. Je schrijft hier niet echt tests voor en daardoor heb je hiaten in de TDD-cyclus.
Nu zijn er natuurlijk bij elke programmeerstijl veel verschillende meningen en zullen mensen verschillende opvattingen hebben over TDD. Maar de benadering die ik gebruik, is dat je individuele componenten maakt om te gebruiken in je app, die elk een uniek functioneel probleem oplossen. Deze componenten zijn gebouwd met behulp van TDD en zorgen ervoor dat ze werken zoals verwacht en dat je hun API niet breekt. Vervolgens schrijft u uw hoofdscript, dat in feite alle lijmcode is, en hoeft niet te worden getest / kan niet worden getest, in bepaalde situaties.
Dit betekent ook dat de meeste van uw componenten in de toekomst opnieuw kunnen worden gebruikt, omdat ze niet echt veel te doen hebben, rechtstreeks, met het hoofdscript.
In navolging van wat ik net zei, is het gebruikelijk om een map met de naam 'lib
'waar je alle individuele componenten neerzet. Dus tot nu toe zou je Mocha en Chai moeten hebben geïnstalleerd, en dan een projectdirectory met twee mappen: 'lib
'en'test
'.
Voor het geval je nog niet bekend bent met TDD, dacht ik dat het een goed idee zou zijn om snel het proces te bespreken. De basisregel is dat je geen enkele code kunt schrijven tenzij de test loper je dat zegt.
In wezen schrijf je wat je code moet doen voordat je het echt doet. Je hebt een echt gericht doel tijdens het coderen en je compromitteert nooit je idee door nevenactiviteiten te volgen of te ver vooruit te denken. Daarnaast kun je er zeker van zijn dat je in de toekomst nooit meer je app zult breken, omdat al je code aan een test is gekoppeld.
Een test is in werkelijkheid slechts een verklaring van wat een functie verwacht te doen tijdens het uitvoeren, dan voert u uw testrunner uit, die uiteraard mislukt (aangezien u de code nog niet hebt geschreven) en vervolgens schrijft u het minimumbedrag van code nodig om de mislukte test te halen. Het is belangrijk om deze stap nooit over te slaan, omdat een test soms zelfs voorbij gaat voordat u een code toevoegt, vanwege andere code die u in dezelfde klas of functie hebt. Wanneer dit gebeurt, heb je ofwel meer code geschreven dan je zou moeten doen voor een andere test of dit is gewoon een slechte test (meestal niet specifiek genoeg).
Nogmaals volgens onze bovenstaande regel, als de test meteen slaagt, kun je geen code schrijven, omdat het je dat niet heeft verteld. Door voortdurend tests te schrijven en vervolgens de functies te implementeren, bouwt u solide modules waarop u kunt vertrouwen.
Zodra u klaar bent met het implementeren en testen van uw component, kunt u vervolgens teruggaan en de code herfactureren om deze te optimaliseren en op te ruimen, maar ervoor zorgen dat de refactoring de tests die u op zijn plaats heeft niet afbreekt en, nog belangrijker, niet doet ' t Voeg functies toe die niet getest zijn.
Elke testbibliotheek heeft zijn eigen syntaxis, maar ze volgen meestal hetzelfde patroon van beweringen maken en vervolgens controleren of ze slagen. Omdat we Mocha en Chai gebruiken, laten we beide syntaxes eens bekijken, te beginnen met Chai.
Ik zal de BDD syntax 'Expect' gebruiken, omdat zoals ik al zei Chai met een paar opties uit de doos komt. De manier waarop deze syntaxis werkt, is dat je begint met het aanroepen van de verwachtingsfunctie, het doorgeeft aan het object waarop je een bewering wilt doen, en dan keten je het met een specifieke test. Een voorbeeld van wat ik bedoel kan als volgt zijn:
verwachten (4 + 5) .equal (9);
Dat is de basissyntaxis, we zeggen dat we de toevoeging van verwachten 4
en 5
gelijk maken 9
. Dit is geen geweldige test, omdat het 4
en 5
wordt toegevoegd door Node.js voordat de functie zelfs wordt gebeld, dus we testen in essentie mijn wiskundige vaardigheden, maar ik hoop dat je het algemene idee krijgt. Het andere ding dat je moet opmerken, is dat deze syntaxis niet erg leesbaar is, in termen van de stroom van een normale Engelse zin. Dit wetende, voegde Chai de volgende kettingvangers toe die niets doen, maar je kunt ze toevoegen om het meer uitgebreid en leesbaar te maken. De kettingvangers zijn als volgt:
Met behulp van het bovenstaande kunnen we onze vorige test naar iets als dit herschrijven:
verwachten (4 + 5) .to.equal (9);
Ik vind het gevoel van de hele bibliotheek erg goed, die je kunt bekijken in hun API. Eenvoudige dingen zoals het ontkennen van de werking is net zo eenvoudig als schrijven .niet
vóór de test:
verwachten (4 + 5) .to.not.equal (10);
Dus zelfs als u de bibliotheek nog nooit eerder hebt gebruikt, zal het niet moeilijk zijn om erachter te komen wat een test probeert te doen.
Het laatste wat ik zou willen bekijken voordat we onze eerste test beginnen, is hoe we onze code in Mocha structureren
Mocha is de testrunner, dus het maakt niet zoveel uit over de eigenlijke tests, waar het om geeft, is de teststructuur, want zo weet het wat faalt en hoe de resultaten moeten worden opgemaakt. De manier waarop je het opbouwt, is dat je meerdere creëert beschrijven
blokken die de verschillende componenten van uw bibliotheek schetsen en vervolgens toevoegen het
blokken om een specifieke test op te geven.
Laten we bijvoorbeeld zeggen dat we een JSON-klasse hadden en dat die klasse een functie had om JSON te analyseren en we wilden ervoor zorgen dat de parse-functie een slecht geformatteerde JSON-tekenreeks kon detecteren, we konden dit als volgt structureren:
beschrijven ("JSON", functie () beschrijven (". parse ()", function () it ("zou misvormde JSON-strings moeten detecteren", function () // Test Goes Here););) ;
Het is niet ingewikkeld, en het is ongeveer 80% persoonlijke voorkeur, maar als je dit soort formaat behoudt, moeten de testresultaten in een zeer leesbaar formaat verschijnen.
We zijn nu klaar om onze eerste bibliotheek te schrijven, laten we beginnen met een eenvoudige synchrone module, om onszelf beter bekend te maken met het systeem. Onze app moet in staat zijn om opdrachtregelopties te accepteren voor het instellen van dingen zoals het aantal niveaus van mappen die onze app moet doorzoeken en de query zelf.
Om voor dit alles te zorgen, zullen we een module maken die de opdrachtstring accepteert en alle opgenomen opties samen met hun waarden parseert.
Dit is een geweldig voorbeeld van een module die u kunt hergebruiken in al uw opdrachtregel-apps, aangezien dit probleem veel voorkomt. Dit zal een vereenvoudigde versie zijn van een echt pakket dat ik heb op npm met de naam ClTags. Dus om te beginnen, maak een bestand met de naam tags.js
in de map lib en vervolgens een ander bestand met de naam tagsSpec.js
binnenkant van de testmap.
We moeten de Chai-verwachtingsfunctie inhalen, omdat dat de assertionsyntaxis is die we zullen gebruiken en we het daadwerkelijke tagsbestand moeten intypen, zodat we het kunnen testen. Alles bij elkaar met een eerste begin moet het er ongeveer zo uitzien:
var verwachten = vereisen ("chai"). verwachten; var tags = require ("... /lib/tags.js"); beschrijf ("Tags", function () );
Als u nu de opdracht 'mokka' uitvoert vanuit de hoofdmap van ons project, moet alles verlopen zoals verwacht. Laten we nu eens nadenken over wat onze module zal doen; we willen het de array met opdrachtargumenten doorgeven die is gebruikt om de app uit te voeren, en dan willen we dat het een object met alle tags bouwt, en het zou mooi zijn als we het ook een standaard object van instellingen kunnen doorgeven, dus als Niets krijgen wordt genegeerd, we hebben enkele instellingen al opgeslagen.
Bij het omgaan met tags bieden veel apps ook snelkoppelingsopties die slechts één karakter hebben, dus laten we zeggen dat we de diepte van onze zoekopdracht wilden instellen, zodat de gebruiker iets kon specificeren zoals --diepte = 2
of zoiets -d = 2
wat hetzelfde effect zou moeten hebben.
Laten we dus beginnen met de lang gevormde tags (bijvoorbeeld '--depth = 2'). Laten we om te beginnen de eerste test schrijven:
beschrijven ("Tags", functie () beschrijven ("# parse ()", function () it ("moet lang gevormde tags parseren", function () var args = ["--depth = 4", " --hello = world "]; var results = tags.parse (args); expect (results) .to.have.a.property (" depth ", 4); expect (results) .to.have.a.property ("Hallo Wereld"); ); ); );
We hebben een methode toegevoegd aan onze test suite genaamd ontleden
en we hebben een test toegevoegd voor lang gevormde tags. In deze test heb ik een voorbeeldopdracht gemaakt en twee beweringen toegevoegd voor de twee eigenschappen die het moet ophalen.
Mocha loopt nu, je zou een fout moeten krijgen, namelijk dat labels
heeft geen ontleden
functie. Dus om deze fout te verhelpen, voegen we een toe ontleden
functie naar de tags module. Een vrij typische manier om een knooppuntmodule te maken is als volgt:
exports = module.exports = ; exports.parse = function ()
De fout zei dat we een a nodig hadden ontleden
methode dus we hebben het gemaakt, we hebben geen andere code aan de binnenkant toegevoegd omdat dit ons nog niet verteld is. Door met het absolute minimum vast te houden, bent u ervan verzekerd dat u niet meer zult schrijven dan u zou moeten doen en dat u eindigt met niet-geteste code.
Laten we Mocha opnieuw uitvoeren, deze keer moeten we een foutmelding krijgen waarin staat dat het geen eigenschap met de naam kan lezen diepte
van een ongedefinieerde variabele. Dat komt omdat momenteel onze ontleden
functie retourneert niets, dus laten we wat code toevoegen zodat het een object retourneert:
exports.parse = function () var options = retouropties;
We gaan langzaam vooruit, als je Mocha opnieuw uitvoert, moeten er geen uitzonderingen worden gegooid, maar een schone foutmelding dat ons lege object geen eigenschap heeft genaamd diepte
.
Nu kunnen we in een echte code komen. Voor onze functie om de tag te ontleden en deze aan ons object toe te voegen, moeten we de array arguments doorlopen en de dubbele streepjes aan het begin van de sleutel verwijderen.
exports.parse = function (args) var options = for (var i in args) // Doorloop args var arg = args [i]; // Controleer of Lang gevormde tag if (arg.substr (0, 2) === "-") arg = arg.substr (2); // Check for equals sign if (arg.indexOf ("=")! == -1) arg = arg.split ("="); var-toets = arg.shift (); opties [key] = arg.join ("="); terugkeeropties;
Deze code doorloopt de lijst met argumenten, zorgt ervoor dat we te maken hebben met een langgevormde tag en splitst deze vervolgens op het eerste gelijkteken om de sleutel en het waardepaar te maken voor het optieobject.
Nu lost dit ons probleem bijna op, maar als we Mocha opnieuw uitvoeren, zul je zien dat we nu een sleutel voor diepte hebben, maar deze is ingesteld op een string in plaats van een cijfer. Nummers zijn een beetje makkelijker om mee te werken in onze app, dus het volgende stukje code dat we moeten toevoegen, is waarden waar mogelijk in getallen omzetten. Dit kan worden bereikt met een aantal RegEx en de parseInt
functioneer als volgt:
if (arg.indexOf ("=")! == -1) arg = arg.split ("="); var-toets = arg.shift (); var value = arg.join ("="); if (/^[0-9]+$/.test(value)) value = parseInt (waarde, 10); opties [key] = waarde;
Mocha loopt nu, je zou een pas moeten krijgen met één test. De nummerconversie moet misschien in zijn eigen test zijn, of op zijn minst genoemd in de testverklaring, zodat u niet per ongeluk de aantekening van de getalomzetting verwijdert; dus gewoon add-on "nummers toevoegen en converteren" naar de het
verklaring voor deze test of scheid deze in een nieuwe het
blok. Het hangt er echt van af of u dit 'duidelijk standaardgedrag' of een afzonderlijke functie in overweging neemt.
Zoals ik in dit hele artikel probeer te benadrukken, als je een voorbijgaande specificatie ziet, is het tijd om meer tests te schrijven. Het volgende dat ik wilde toevoegen was de standaardarray, dus binnen de tagsSpec
bestand laten we het volgende toevoegen het
blok rechts na de vorige:
it ("moet lang gevormde tags analyseren en getallen converteren", function () var args = ["--depth = 4", "--hello = world"]; var results = tags.parse (args); expect ( resultaten) .to.have.a.property ("depth", 4); expect (results) .to.have.a.property ("hallo", "world");); it ("should fallback to defaults", function () var args = ["--depth = 4", "--hello = world"]; var defaults = depth: 2, foo: "bar"; var results = tags.parse (args, default); var expected = depth: 4, foo: "bar", hallo: "world"; expect (results) .to.deep.equal (expected););
Hier gebruiken we een nieuwe test, de diepe gelijkheid die goed is voor het matchen van twee objecten voor gelijke waarden. Als alternatief kunt u de EQL
test die een kortere weg is, maar ik denk dat dit duidelijker is. Deze test geeft twee argumenten door als de opdrachtreeks en geeft twee standaardwaarden door met één overlap, zodat we een goede spreiding over de testcases krijgen.
Als je nu Mocha draait, zou je een soort diff moeten krijgen, met de verschillen tussen wat er verwacht wordt en wat het werkelijk heeft.
Laten we nu verder gaan met de tags.js
module, en laten we deze functionaliteit toevoegen. Het is een vrij eenvoudige oplossing om toe te voegen, we hoeven alleen de tweede parameter te accepteren en wanneer deze op een object is ingesteld, kunnen we het standaard lege object aan het begin vervangen door dit object:
exports.parse = function (args, default) var options = ; if (typeof defaults === "object" &&! (standaardinstantie van Array)) options = standaardwaarden
Dit zal ons terugbrengen naar een groene staat. Het volgende dat ik wil toevoegen, is de mogelijkheid om een tag zonder waarde te specificeren en het als een booleaanse waarde te laten werken. Bijvoorbeeld, als we zojuist hebben ingesteld --searchContents
of iets dergelijks, het zal dat alleen toevoegen aan onze array met opties met een waarde van waar
.
De test hiervoor zou er ongeveer zo uitzien:
it ("moet tags zonder waarden als bool accepteren", function () var args = ["--searchContents"]; var results = tags.parse (args); expect (results) .to.have.a.property ("searchContents", true););
Als u dit uitvoert, krijgt u de volgende fout, net als eerder:
Binnenkant van de voor
lus, toen we een match kregen voor een lang gevormd label, controleerden we of het een gelijkteken bevatte; we kunnen de code voor deze test snel schrijven door een anders
clausule daarover als
statement en alleen de waarde instellen waar
:
if (arg.indexOf ("=")! == -1) arg = arg.split ("="); var-toets = arg.shift (); var value = arg.join ("="); if (/^[0-9]+$/.test(value)) value = parseInt (waarde, 10); opties [key] = waarde; else options [arg] = true;
Het volgende dat ik wil toevoegen is de vervanging van de korte-handslabels. Dit is de derde parameter voor de ontleden
functie en zal in principe een object met letters zijn en de bijbehorende vervangingen. Hier is de specificatie voor deze toevoeging:
it ("moet snel gevormde tags accepteren", function () var args = ["-sd = 4", "-h"]; var replacements = s: "searchContents", d: "depth", h: " hallo "; var results = tags.parse (args, , replacements); var expected = searchContents: true, depth: 4, hello: true; expect (results) .to.deep.equal (expected); );
Het probleem met stenografische tags is dat ze op een rij kunnen worden gecombineerd. Wat ik hiermee bedoel is in tegenstelling tot de lang gevormde tags waar elke afzonderlijk is, met korte hand-tags - omdat ze elk slechts een letter lang zijn - kun je drie verschillende typen typen door te typen -VGH
. Dit maakt het parseren een beetje moeilijker omdat we nog steeds moeten toestaan dat de operator voor gelijken een waarde toevoegt aan de laatst genoemde tag, terwijl je tegelijkertijd de andere tags moet registreren. Maar maak je geen zorgen, het is niets dat niet opgelost kan worden met genoeg knallen en verschuiven.
Hier is de hele oplossing, vanaf het begin van de ontleden
functie:
exports.parse = function (args, default, replacements) var options = ; if (typeof defaults === "object" &&! (defaults instanceof Array)) options = defaults if (typeof replacements === "object" &&! (standaardinstellingen van Array)) for (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 (key)) key = replacements [key]; args.push ("-" + toets + "=" + waarde); else arg = arg.split (""); arg.forEach (functie (sleutel) if (replacements.hasOwnProperty (key)) key = replacements [key]; args.push ("-" + key););
Het is veel code (ter vergelijking), maar het enige wat we echt doen is het argument splitsen door een gelijkteken, en dan die sleutel in de individuele letters splitsen. Dus bijvoorbeeld als we geslaagd zijn -gj = asd
we zouden het splitsen asd
in een variabele genaamd waarde
, en dan zouden we het splitsen gj
sectie in afzonderlijke karakters. Het laatste teken (j
in ons voorbeeld) wordt de sleutel voor de waarde (asd
) overwegende dat eventuele andere letters ervoor gewoon worden toegevoegd als reguliere booleaanse tags. Ik wilde deze tags nu niet gewoon verwerken, voor het geval we de implementatie later veranderden. Dus wat we aan het doen zijn, is gewoon deze korte hand-tags omzetten in de langgevormde versie en ons later door ons script laten verwerken.
Als Mocha opnieuw wordt uitgevoerd, komen we terug op onze illustere groene resultaten van vier tests die voor deze module zijn geslaagd.
Nu zijn er nog een paar dingen die we aan deze module met tags kunnen toevoegen om dichter bij het npm-pakket te komen, zoals de mogelijkheid om ook gewone tekstargumenten op te slaan voor dingen zoals opdrachten of de mogelijkheid om alle tekst aan het einde te verzamelen voor een queryeigenschap. Maar dit artikel wordt al lang en ik wil graag verder gaan met het implementeren van de zoekfunctionaliteit.
We zijn net begonnen met het stap voor stap creëren van een module volgens een TDD-aanpak en ik hoop dat je het idee en het gevoel hebt om zo te schrijven. Maar omwille van het in beweging houden van dit artikel, voor de rest van het artikel, zal ik het testproces versnellen door dingen bij elkaar te voegen en u alleen de definitieve versies van tests te laten zien. Het is meer een gids voor verschillende situaties die zich kunnen voordoen en hoe tests voor hen kunnen worden geschreven.
Dus maak gewoon een bestand met de naam search.js
in de map lib en a searchSpec.js
bestand in de testmap.
Open vervolgens het spec-bestand en laat ons onze eerste test instellen, die voor de functie kan zijn om een lijst met bestanden op basis van a te krijgen diepte
parameter, dit is ook een geweldig voorbeeld voor tests die een beetje externe instellingen vereisen om te kunnen werken. Wanneer u te maken heeft met object-achtige gegevens of in onze casusbestanden, wilt u een vooraf gedefinieerde set-up waarvan u weet dat deze zal werken met uw tests, maar u wilt ook geen nep-info aan uw systeem toevoegen.
Er zijn in principe twee opties om dit probleem op te lossen, je kunt ofwel de gegevens bespotten, zoals hierboven vermeld als je te maken hebt met de eigen commando's voor het laden van gegevens, je hoeft ze niet per se te testen. In dergelijke gevallen kunt u eenvoudig de 'opgehaalde' gegevens opgeven en doorgaan met uw testen, vergelijkbaar met wat we hebben gedaan met de opdrachtreeks in de tagsbibliotheek. Maar in dit geval testen we de recursieve functionaliteit die we aan het lezen van talen toevoegen, afhankelijk van de gespecificeerde diepte. In gevallen zoals deze moet je een test schrijven en daarom moeten we een aantal demobestanden maken om de bestandslezing te testen. Het alternatief is om de fs
functies om gewoon uit te voeren maar niets te doen, en dan kunnen we tellen hoe vaak onze nepfunctie liep of zoiets (check spionnen) maar voor ons voorbeeld ga ik gewoon wat bestanden maken.
Mocha biedt functies die zowel voor als na uw tests kunnen worden uitgevoerd, zodat u dit soort externe instellingen en opschoning rond uw tests kunt uitvoeren.
Voor ons voorbeeld zullen we een aantal testbestanden en -mappen op twee verschillende diepten maken, zodat we die functionaliteit kunnen testen:
var verwachten = vereisen ("chai"). verwachten; var search = require ("... /lib/search.js"); var fs = require ("fs"); beschrijven ("Zoeken", functie () beschrijven ("# scan ()", functie () before (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 "););););
Deze worden gebeld op basis van de beschrijven
blok waarin ze zich bevinden, en je kunt zelfs code voor en na elk uitvoeren het
blokkeren met beforeEach
of na elke
in plaats daarvan. De functies zelf gebruiken alleen standaardknooppuntopdrachten om de bestanden respectievelijk te maken en te verwijderen. Vervolgens moeten we de eigenlijke test schrijven. Dit zou direct naast de moeten gaan na
functie, nog steeds binnen de beschrijven
blok:
it ("zou de bestanden uit een directory moeten halen", functie (gereed) 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 ();););
Dit is ons eerste voorbeeld van het testen van een asynchroonfunctie, maar zoals u kunt zien is het net zo eenvoudig als voorheen; alles wat we moeten doen is gebruik maken van de gedaan
functie Mocha biedt in de het
verklaringen om het te melden wanneer we klaar zijn met deze test.
Mocha zal automatisch detecteren of u de gedaan
variabele in de callback en hij zal wachten totdat deze wordt gebeld, zodat u asynchrone code heel gemakkelijk kunt testen. Het is ook de moeite waard om te vermelden dat dit patroon overal in Mocha beschikbaar is, je kunt dit bijvoorbeeld gebruiken in de voor
of na
functies als u iets asynchroon moest instellen.
Vervolgens zou ik graag een test willen schrijven die ervoor zorgt dat de parameter depth werkt als deze is ingesteld:
it ("zou moeten stoppen op een bepaalde diepte", function (done) search.scan (". test_files", 1, function (err, flist) expect (flist) .to.deep.equal ([".test_files / een "," .test_files / b ",]); done ();););
Hier is niets anders, gewoon een gewone test. Als u dit in Mocha uitvoert, krijgt u een foutmelding dat de zoekopdracht geen methoden bevat, voornamelijk omdat we er niets in hebben geschreven. Dus laten we een overzicht toevoegen met de functie:
var fs = require ("fs"); exports = module.exports = ; exports.scan = function (dir, depth, done)
Als u Mocha nu opnieuw uitvoert, zal het pauzeren in afwachting van het terugkeren van deze asynchroonfunctie, maar aangezien we helemaal geen terugroepactie hebben aangeroepen, zal de test slechts een time-out hebben. Standaard zou het na ongeveer twee seconden moeten uitvallen, maar je kunt dit aanpassen met this.timeout (milliseconden)
binnen een beschrijvingsblok of een blok, om hun time-outs respectievelijk aan te passen.
Het is de bedoeling dat deze scanfunctie een pad en diepte neemt en een lijst terugstuurt van alle gevonden bestanden. Dit is eigenlijk een beetje lastig wanneer je begint na te denken over hoe we in feite in één functie twee verschillende functies samen recurseren. We moeten de verschillende mappen opnieuw doorlopen en dan moeten die mappen zichzelf scannen en besluiten verder te gaan.
Dit synchroon doen is prima, omdat je er een voor een doorheen kunt stappen en langzaam een niveau of pad tegelijk kunt voltooien. Als het om een asynchrone versie gaat, wordt het een beetje ingewikkelder omdat je niet zomaar een foreach
loop ofzo, omdat het niet tussen de mappen zal pauzeren, ze zullen allemaal in wezen tegelijkertijd worden uitgevoerd waarbij verschillende waarden worden geretourneerd en ze elkaar zouden overschrijven.
Dus om het te laten werken, moet je een soort stack maken waar je asynchroon één voor één kunt verwerken (of allemaal in één keer als je in plaats daarvan een wachtrij gebruikt) en dan een bepaalde volgorde op die manier behoudt. Het is een heel specifiek algoritme dus ik bewaar een fragment van Christopher Jeffrey dat je kunt vinden op Stack Overflow. Het is niet alleen van toepassing op het laden van bestanden, maar ik heb dit in een aantal toepassingen gebruikt, eigenlijk alles waar je een array van objecten één voor één moet verwerken met behulp van asynchroonfuncties.
We moeten het een beetje aanpassen, omdat we graag een dieptemogelijkheid willen, hoe de optie Diepte werkt, is ingesteld hoeveel niveaus van mappen je wilt controleren, of nul om voor onbepaalde tijd terug te keren.
Hier is de voltooide functie met behulp van het fragment:
exports.scan = function (dir, depth, done) depth--; var resultaten = []; fs.readdir (dir, functie (err, lijst) if (err) return done (err); var i = 0; (functie next () var file = list [i ++]; if (! file) return done ( null, resultaten); bestand = dir + '/' + bestand; fs.stat (bestand, functie (err, stat) if (stat && stat.isDirectory ()) if (depth! == 0) var ndepth = (diepte> 1)? diepte-1: 1; exports.scan (bestand, ndepth, functie (err, res) results = results.concat (res); next ();); else next () ; else results.push (file); next (););) ();); ;
Mocha zou nu beide tests moeten doorstaan. De laatste functie die we moeten implementeren, is degene die een reeks paden en een zoekwoord accepteert en alle overeenkomsten retourneert. Hier is de test voor:
beschrijven ("# match ()", functie () it ("moet zoeken naar en retourneren van overeenkomsten op basis van een query", function () var files = ["hello.txt", "world.js", "another. js "]; var results = search.match (" .js ", bestanden); verwacht (resultaten) .to.deep.equal ([" world.js "," another.js "]); results = search.match ("hallo", bestanden); verwacht (resultaten) .to.deep.equal (["hello.txt"]);););
En las