Testarea în Node.js

Un ciclu de dezvoltare bazat pe test simplifică procesul de gândire de scriere a codului, îl face mai ușor și mai rapid pe termen lung. Dar scrierea testelor nu este suficientă singură, cunoașterea tipurilor de teste pentru a scrie și modul de structurare a codului pentru a se conforma acestui model este ceea ce este vorba. În acest articol vom analiza construirea unei aplicații mici în Node.js urmând un model TDD.

Pe lângă testele "unice" simple, cu care suntem cu toții familiarizați; De asemenea, putem avea codul Async al Node.js, care adaugă un extra dimensiune în care nu cunoaștem întotdeauna ordinea în care vor funcționa funcțiile sau s-ar putea să încercăm să testați ceva într-un apel invers sau să verificăm dacă funcționează o funcție de asincracție.

În acest articol vom construi o aplicație Nod care poate căuta fișiere care se potrivesc cu o interogare dată. Știu că există deja lucruri pentru acest lucru (ack), dar pentru a demonstra TDD cred că ar putea fi un proiect bine rotunjit.

Primul pas este, evident, de a scrie câteva teste, dar chiar înainte de aceasta, trebuie să alegem un cadru de testare. Puteți folosi nodul de vanilie, deoarece există un afirma biblioteca încorporată, dar nu este prea mult în ceea ce privește un alergător de testare și este destul de mult esențial.

O altă opțiune și, probabil, preferatul meu pentru uz general este Jasmine. Este destul de autonom, nu aveți alte dependențe de adăugat la scenariile dvs., iar sintaxa este foarte curată și ușor de citit. Singurul motiv pentru care nu voi folosi acest lucru astăzi este că cred că Jack Franklin a făcut o treabă excelentă care să acopere acest lucru în seria recentă de Tuts + și este bine să știți opțiunile dvs. pentru a putea alege cel mai bun instrument pentru situația dvs..


Ce vom construi

În acest articol vom folosi alerta flexibilă "Mocha" împreună cu biblioteca de afirmare Chai.

Spre deosebire de Jasmine, care este mai mult ca o întreagă suită de testare într-un singur pachet, Mocha se ocupă doar de structura generală, dar nu are nimic de-a face cu afirmațiile reale. Acest lucru vă permite să păstrați un aspect consistent și să vă simțiți atunci când executați testele dvs., dar vă permite de asemenea să rulați oricare dintre bibliotecile de afirmații care se potrivesc cel mai bine situației dvs..

De exemplu, dacă ați folosi biblioteca vanilie "assert", puteți să o asociați cu Mocha pentru a adăuga o structură la testele dvs..

Chai este o opțiune destul de populară, și este, de asemenea, totul despre opțiuni și modularitate. Chiar și fără pluginuri, folosind doar API-ul implicit, aveți trei sintaxe diferite pe care le puteți utiliza în funcție de dacă doriți să utilizați un stil TDD mai clasic sau o sintaxă BDD mai detaliată.

Deci, acum că știm ce vom folosi, să intrăm în instalație.


Pregatirea

Pentru a începe, să instalați Mocha la nivel global, executând:

npm install -g mocha

Când se finalizează, creați un nou dosar pentru proiectul nostru și executați următoarele în interiorul acestuia:

npm install chai

Aceasta va instala o copie locală a Chai pentru proiectul nostru. Apoi, creați un folder numit Test în directorul proiectului nostru, deoarece aceasta este locația implicită Mocha va căuta teste.

Asta este destul de mult pentru configurare, următorul pas este să vorbiți despre modul de structurare a aplicațiilor atunci când urmați un proces testat de dezvoltare.


Structurarea aplicației dvs.

Este important să știți, atunci când urmează o abordare TDD, ce trebuie să aibă teste și ce nu. O regulă de bază este de a nu scrie teste pentru alte persoane deja testate de cod. Ce vreau sa spun prin aceasta este urmatoarea: sa spunem ca codul dvs. deschide un fisier, nu este nevoie sa testati persoana fs funcția, face parte din limbă și se presupune că este deja bine testată. Același lucru este valabil și atunci când folosiți biblioteci terțe, nu trebuie să structurați funcții care numesc în primul rând aceste tipuri de funcții. Nu prea scrieți teste pentru acestea și din acest motiv aveți lacune în ciclul TDD.

Acum, desigur, cu fiecare stil de programare există o mulțime de opinii diferite și oamenii vor avea opinii diferite cu privire la modul de a TDD. Dar abordarea pe care o folosesc este aceea că creați componente individuale de utilizat în aplicația dvs., fiecare dintre acestea rezolvând o problemă funcțională unică. Aceste componente sunt construite folosind TDD, asigurându-se că funcționează conform așteptărilor și că nu le veți rupe API-ul. Apoi scrieți scriptul principal, care este, în esență, tot codul de clei și nu trebuie să fie testat / nu poate fi testat, în anumite situații.

Acest lucru înseamnă, de asemenea, că majoritatea componentelor dvs. pot fi refolosite în viitor, deoarece nu au prea multe de făcut, direct, cu scenariul principal.

După ce am spus, este o practică obișnuită să creezi un dosar numit "libunde puneți toate componentele individuale. Deci, până în acest moment ar trebui să aveți instalat Mocha și Chai și apoi un director de proiect cu două dosare:lib' și 'Test'.


Noțiuni de bază cu TDD

Doar în cazul în care sunteți nou la TDD, am crezut că ar fi o idee bună să acoperiți rapid procesul. Regula principală este că nu puteți scrie niciun cod decât dacă vă indică alergătorul de test.

În esență, scrii ceea ce trebuie să faci codul înainte de ao face. Aveți un obiectiv cu adevărat concentrat în timp ce codificați și niciodată nu compromiteți ideea dvs. prin a vă urmări sau a vă gândi prea departe. În afară de aceasta, deoarece tot codul dvs. va avea un test afiliat cu acesta, puteți fi siguri că nu veți rupe aplicația în viitor.

Un test, în realitate, este doar o declarație despre ceea ce se așteaptă să facă o funcție atunci când alergi, apoi alergi alergătorul, care în mod evident va eșua (din moment ce nu ai scris încă codul) și apoi scrii suma minimă de cod necesar pentru a trece testul de eșec. Este important să nu treceți niciodată de acest pas, deoarece uneori un test va trece chiar înainte de a adăuga orice cod, din cauza altui cod pe care îl aveți în aceeași clasă sau funcție. Când se întâmplă acest lucru, ați scris mai multe coduri, atunci când trebuia să faceți un test diferit sau este doar un test prost (de obicei nu este suficient de specific).

Din nou, conform regulii noastre de mai sus, dacă testul trece imediat, nu puteți scrie nici un cod, pentru că nu ți-a spus. Prin scrierea continuă a testelor și apoi prin implementarea caracteristicilor pe care le construiți module solide pe care vă puteți baza.

Odată ce ați terminat implementarea și testarea componentei dvs., puteți să vă întoarceți din nou și să refaceți codul pentru ao optimiza și ao curăța, dar asigurați-vă că refactorizarea nu va eșua niciunul dintre testele pe care le aveți și, mai important, adăugați funcții care nu sunt testate.

Fiecare bibliotecă de testare va avea propria sintaxă, dar de obicei urmează același model de a face afirmații și apoi verifică dacă trece. Deoarece folosim Mocha și Chai, să aruncăm o privire la ambele sintaxe, începând cu Chai.


Mocha & Chai

Voi folosi sintaxa "Expect" BDD, pentru că, după cum am menționat, Chai vine cu câteva opțiuni din cutie. Modul în care funcționează această sintaxă începe prin apelarea funcției așteptare, transmiterea obiectului la care doriți să faceți o afirmație, apoi lanțați-o cu un test specific. Un exemplu de ceea ce vreau să spun ar putea fi după cum urmează:

se așteaptă (4 + 5) .equal (9);

Aceasta este sintaxa de bază, spunem că așteptăm adăugarea 4 și 5 la egal 9. Acum, acest lucru nu este un test de mare, deoarece 4 și 5 va fi adăugată de Node.js înainte ca funcția să fie chiar numită, așa că testam în esență abilitățile mele de matematică, dar sper că veți obține ideea generală. Celălalt lucru pe care ar trebui să îl rețineți, este că această sintaxă nu este foarte lizibilă, din punctul de vedere al fluxului unei sentințe normale în limba engleză. Cunoscand acest lucru, Chai a adaugat urmatorii lingasi care nu fac nimic, dar le puteti adauga pentru a le face mai verbose si mai lizibile. Obținătorii lanțului sunt după cum urmează:

  • la
  • fi
  • fost
  • este
  • acea
  • și
  • avea
  • cu
  • la
  • de
  • la fel
  • A
  • un

Folosind cele de mai sus, putem rescrie testul nostru anterior la ceva de genul:

se așteaptă (4 + 5) .to.equal (9);

Îmi place foarte mult simțurile întregii biblioteci, pe care le puteți verifica în API-ul lor. Lucruri simple cum ar fi negarea operațiunii sunt la fel de ușor ca și scrierea .nu înainte de test:

se așteaptă (4 + 5) .to.not.equal (10);

Deci, chiar dacă nu ai mai folosit niciodată biblioteca, nu va fi greu să îți dai seama ce încercare încearcă să faci.

Ultimul lucru pe care aș vrea să îl privesc înainte să intrăm în primul nostru test este cum ne structurăm codul în Mocha

Cafea

Mocha este alergătorul de test, așa că nu prea are grijă prea mult de testele reale, ceea ce îi interesează este structura testelor, pentru că așa știe ce eșuează și cum să modeleze rezultatele. Modul în care îl construiți, creați mai multe descrie blocuri care conturează diferitele componente ale bibliotecii dvs. și apoi adăugați aceasta blocuri pentru a specifica un test specific.

Pentru un exemplu rapid, să presupunem că am avut o clasă JSON și că această clasă a avut o funcție de analiză a JSON și am vrut să ne asigurăm că funcția de analiză poate detecta un șir JSON formatat prost, am putea structura acest lucru astfel:

descrie ("JSON", funcția () descrie (". parse ()", funcția () it (ar trebui să detecteze șiruri JSON malformate ", funcția // Test Goes Here ;

Nu este complicat și este vorba de aproximativ 80% preferință personală, dar dacă păstrați acest tip de format, rezultatele testului ar trebui să apară într-un format foarte lizibil.

Suntem gata să scriem prima bibliotecă, să începem cu un simplu modul sincron, să ne cunoaștem mai bine sistemul. Aplicația noastră va trebui să poată accepta opțiunile din linia de comandă pentru a seta lucruri precum numărul de dosare pe care aplicația noastră ar trebui să le caute și interogarea însăși.

Pentru a avea grijă de toate acestea, vom crea un modul care acceptă șirul de comandă și analizează toate opțiunile incluse împreună cu valorile acestora.

Modulul Tag

Acesta este un exemplu excelent al unui modul pe care îl puteți reutiliza în toate aplicațiile din linia de comandă, deoarece acest aspect apare foarte mult. Aceasta va fi o versiune simplificată a unui pachet actual pe care îl am numit ClTags. Deci, pentru a începe, creați un fișier numit tags.js în interiorul directorului lib, apoi un alt fișier numit tagsSpec.js în interiorul dosarului de testare.

Trebuie să tragem funcția de așteptare Chai, deoarece aceasta va fi sintaxa de afirmație pe care o vom folosi și trebuie să tragem în fișierul cu tag-uri reale, astfel încât să îl putem testa. Împreună cu o configurație inițială ar trebui să arate ceva de genul:

var aștepta = cere ("chai"). var tags = solicită ("... /lib/tags.js"); descrie ("Etichete", funcția () );

Dacă rulați comanda "mocha" acum de la rădăcina proiectului nostru, totul ar trebui să treacă așa cum era de așteptat. Acum, să ne gândim la ceea ce va face modul nostru; dorim să-i transmitem argumentul array array care a fost folosit pentru a rula aplicația și apoi vrem să construim un obiect cu toate etichetele și ar fi drăguț dacă i-am putea transmite și un obiect implicit al setărilor, nimic nu este suprasolicitat, vom avea deja unele setări deja stocate.

Când se ocupă de etichete, o mulțime de aplicații oferă, de asemenea, opțiuni de comenzi rapide, care sunt doar un caracter, deci să spunem că am vrut să setăm adâncimea căutării noastre, am putea permite utilizatorului să specificăm ceva de genul --adâncimea = 2 sau ceva asemănător -d = 2 care ar trebui să aibă același efect.

Deci, să începem cu etichetele lungi formate (de exemplu, '--depth = 2'). În primul rând, să scriem primul test:

descrie ("Etichete", functie () descrie ("# parse ()", (Rezultate) .to.have.a.property ("depth", 4); așteptați (rezultate) .to.have.a.property ("Salut Lume"); ); ); );

Am adăugat o metodă la testul nostru numit analiza și am adăugat un test pentru etichetele formate de mult timp. În cadrul acestui test am creat o comandă de exemplu și am adăugat două afirmații pentru cele două proprietăți pe care ar trebui să le iau.

Acum, alergând la Mocha, ar trebui să obțineți o singură eroare, și anume Etichete nu are a analiza funcţie. Deci, pentru a rezolva această eroare, să adăugăm o analiza funcție de modulul de etichete. Un mod destul de tipic de a crea un modul de noduri este așa:

export = modul.exports = ; exports.parse = function () 

Eroarea a spus că avem nevoie de a analiza așa că am creat-o, nu am adăugat niciun alt cod înăuntru, pentru că nu ne-a spus încă. Prin lipirea cu minimul liber sunteți siguri că nu veți scrie mai mult decât ar trebui să faceți și veți sfârși cu codul netestat.

Acum să conducem din nou Mocha, de data aceasta ar trebui să primim o eroare spunându-ne că nu poate citi o proprietate numită adâncime de la o variabilă nedefinită. Asta pentru că în prezent analiza funcția nu întoarce nimic, deci să adăugăm un cod astfel încât să returneze un obiect:

exports.parse = function () var opțiuni =  opțiuni de retur; 

Ne mișcăm încet, dacă reușim din nou Mocha, nu ar trebui să fie excepții care ar fi aruncate, doar un mesaj de eroare curat care spune că obiectul gol nu are proprietatea numită adâncime.


Acum putem intra într-un cod real. Pentru ca funcția noastră să analizăm eticheta și să o adăugăm la obiectul nostru trebuie să trecem prin matricea argumentelor și să eliminăm liniuțele duble la începutul cheii.

exports.parse = funcția (args) var opțiuni =  pentru (var i în args) // Ciclul prin args var arg = args [i]; // Verificați dacă tagul formatat lung (arg.substr (0, 2) === "-") arg = arg.substr (2); // Verificați pentru semnul egal dacă (arg.indexOf ("=")! == -1) arg = arg.split ("="); var cheie = arg.shift (); opțiuni [key] = arg.join ("=");  retur; 

Acest cod cicluri prin lista de argumente, ne asigură că avem de-a face cu o etichetă lungă formată și apoi o împărțim cu primul caracter egal pentru a crea perechea de chei și valoare pentru obiectul de opțiuni.

Acum, acest lucru ne rezolvă aproape problema, dar dacă conducem din nou Mocha, veți vedea că acum avem o cheie pentru adâncime, dar este setat la un șir în loc de un număr. Numerele sunt mai ușor de operat mai târziu în aplicația noastră, deci următoarea bucată de cod pe care trebuie să o adăugăm este de a converti valorile la numere ori de câte ori este posibil. Acest lucru poate fi realizat cu unele RegEx și parseInt funcționează după cum urmează:

 dacă (arg.indexOf ("=")! == -1) arg = arg.split ("="); var cheie = arg.shift (); var valoare = arg.join ("="); dacă /\[0-9]+$/.test(value)) value = parseInt (valoare, 10);  opțiuni [cheie] = valoare; 

Acum, alergând la Mocha, ar trebui să obțineți un pas cu un test. Conversia numerelor ar trebui să fie în mod propriu testată sau cel puțin menționată în declarația de testare, astfel încât, din greșeală, să nu eliminați afirmația de conversie a numărului; așa că doar add-on "adăuga și convertească numere" la aceasta declarație pentru acest test sau o separați într-o nouă aceasta bloc. Depinde într-adevăr dacă considerați acest "comportament evident implicit" sau o caracteristică separată.


Acum, așa cum am încercat să subliniez în tot acest articol, când vedeți o spec. De trecere, este timpul să scrieți mai multe teste. Următorul lucru pe care am vrut să-l adaug este matricea implicită, deci înăuntru tagsSpec fișierul să adăugăm următoarele aceasta blocați imediat după cel precedent:

 ("ar trebui să analizăm etichetele lungi formate și să convertim numerele", funcția () var args = ["--depth = 4", "--hello = lume"]; rezultatele) .to.have.a.property ("adâncime", 4); așteptați (rezultate) .to.have.a.property ("salut", "lume");); ("ar trebui să se întoarcă la valorile implicite", funcția () var args = ["--depth = 4", "--hello = lume"]; var defaults = adâncimea: 2, foo: bar "; rezultatele = tags.parse (args, implicit); var expected = adâncime: 4, foo: "bar", hello: "world"; așteptați (rezultate) .de.deep.equal (așteptat););

Aici folosim un test nou, egal adânc, care este bun pentru potrivirea a două obiecte pentru valori egale. Alternativ, puteți utiliza funcția EQL care este o scurtătură, dar cred că este mai clar. Acest test trece două argumente ca șirul de comandă și trece două defaults cu o singură suprapunere, doar ca să putem obține o bună răspândire pe casetele de testare.

Fugind acum Mocha, ar trebui să obțineți un fel de diferență, care să conțină diferențele dintre ceea ce este de așteptat și ceea ce a obținut.


Acum continuăm să revenim la tags.js modul și să adăugăm această funcționalitate. Este o soluție destul de simplă de adăugat, trebuie doar să acceptăm al doilea parametru și atunci când este setat la un obiect putem înlocui obiectul obișnuit gol la început cu acest obiect:

exports.parse = funcție (args, implicit) var opțiuni = ; dacă (tipul de defaults === "obiect" &&! (default din Array)) options = defaults

Aceasta ne va aduce într-o stare verde. Următorul lucru pe care vreau să îl adaug este abilitatea de a specifica doar o etichetă fără valoare și permiteți-i să funcționeze ca un boolean. De exemplu, dacă am setat --searchContents sau ceva de genul asta, va adăuga doar că la opțiunile noastre matrice cu o valoare de Adevărat.

Testul pentru acest lucru ar arata cam asa:

 ("ar trebui să accepte etichete fără valori ca bool", funcția () var args = ["--searchContents"]; vară = tags.parse (args); așteptați (rezultate) .to.have.a.property ("căutareContents", true););

Rularea acestui lucru ne va da următoarea eroare la fel ca înainte:


În interiorul pentru atunci când am obținut un meci pentru o etichetă lungă, am verificat dacă acesta conține un semn egal; putem scrie rapid codul pentru acest test prin adăugarea unui cod altfel clauza pentru asta dacă declarația și doar setarea valorii la Adevărat:

 dacă (arg.indexOf ("=")! == -1) arg = arg.split ("="); var cheie = arg.shift (); var valoare = arg.join ("="); dacă /\[0-9]+$/.test(value)) value = parseInt (valoare, 10);  opțiuni [cheie] = valoare;  altceva opțiuni [arg] = adevărat; 

Următorul lucru pe care vreau să îl adaug este înlocuirea etichetelor scurte. Acesta va fi al treilea parametru pentru analiza și va fi, în esență, un obiect cu litere și înlocuirile lor corespunzătoare. Iată specificațiile pentru această adăugare:

 ar trebui să accepte etichete scurte formate, funcția () var args = ["-sd = 4", "-h"]; var replacements = s: "searchContents"; d: hello "; var rezultate = tags.parse (args, , înlocuitori); var expected = searchContents: true, depth: 4, hello: true; );

Problema cu etichetele stenografice este că pot fi combinate într-un rând. Ceea ce vreau sa spun prin aceasta este spre deosebire de tag-urile formate de mult timp, fiecare dintre ele fiind separat, cu etichete scurte de mână - din moment ce fiecare are doar o literă lungă - puteți suna trei tipuri diferite prin tastarea -vgh. Acest lucru face ca parsarea să fie un pic mai dificilă, pentru că trebuie să permitem ca operatorul egal să vă adauge o valoare ultimei etichete menționate, în timp ce, în același timp, trebuie să înregistrați încă celelalte etichete. Dar să nu-ți faci griji, nu este nimic care nu poate fi rezolvat cu suficientă lovire și schimbare.

Aici este întreaga remediere, de la începutul analiza funcţie:

exports.parse = funcția (args, implicit, înlocuitor) var options = ; dacă (typeof defaults === "obiect" &&! (default de la Array)) opțiuni = implicite dacă (typeof replacements === "object" &&! var arg = args [i]; dacă (arg.charAt (0) === "-" && arg.charAt (1)! = "-") arg = arg.substr (1); dacă (arg.indexOf ("=")! == -1) arg = arg.split ("="); var cheie = arg.shift (); var valoare = arg.join ("="); arg = chei.split (""); var cheie = arg.pop (); dacă (replacements.hasOwnProperty (cheie)) key = replacements [key];  args.push ("-" + cheie + "=" + valoare);  altceva arg = arg.split ("");  arg.forEach (funcție (cheie) if (replacements.hasOwnProperty (cheie)) key = înlocuire [cheie]; args.push ("-" +); 

Este o mulțime de cod (în comparație), dar tot ceea ce facem cu adevărat este divizarea argumentului printr-un semn egal, apoi împărțirea acelei chei în literele individuale. De exemplu, dacă am trecut -gj = asd am diviza asd într-o variabilă numită valoare, și apoi am împărțit gj secțiune în caractere individuale. Ultimul caracter (j în exemplul nostru) va deveni cheia pentru valoarea (asd), în timp ce orice alte scrisori înaintea ei, vor fi adăugate doar ca tag-uri booleene obișnuite. Nu am vrut doar să procesez aceste etichete acum, doar în cazul în care am schimbat implementarea mai târziu. Deci, ceea ce facem este doar convertirea acestor etichete scurte în versiunea formată de mult timp și apoi lăsarea scriptului nostru să se ocupe mai târziu.

Rularea Mocha din nou ne va duce înapoi la rezultatele noastre ecologice ilustrative din patru teste care trec pentru acest modul.

Acum, există încă câteva lucruri pe care le putem adăuga la acest modul de etichete pentru a fi mai aproape de pachetul npm, cum ar fi capacitatea de a stoca, de asemenea, argumente text simplu pentru lucruri cum ar fi comenzi sau abilitatea de a colecta tot textul la sfârșit, pentru interogare proprietate. Dar acest articol devine deja mult timp și aș dori să trec la punerea în aplicare a funcționalității de căutare.


Modulul de căutare

Am trecut prin crearea unui modul pas cu pas urmând o abordare TDD și sper că ați avut ideea și sentimentul cum să scrieți așa. Dar pentru a păstra acest articol în mișcare, pentru restul articolului, voi accelera procesul de testare prin gruparea lucrurilor și vă arăt doar versiunile finale ale testelor. Este mai mult un ghid pentru diferite situații care pot apărea și cum să scrie teste pentru ei.

Deci, creați doar un fișier numit search.js în interiorul directorului lib și a searchSpec.js fișier din interiorul dosarului de testare.

Apoi deschideți fișierul de spec. Și urmați primul nostru test, care poate fi pentru funcția de a obține o listă de fișiere bazate pe adâncime parametru, acesta este, de asemenea, un exemplu excelent pentru testele care necesită o configurație externă pentru ca acestea să funcționeze. Atunci când se ocupă de date exterioare de tip obiect sau în fișierele noastre de caz, veți dori să aveți o configurație predefinită pe care o cunoașteți că va funcționa cu testele dvs., dar, de asemenea, nu doriți să adăugați informații false la sistemul dvs..

Există în esență două opțiuni pentru a rezolva această problemă, puteți să măriți datele, așa cum am menționat mai sus dacă aveți de-a face cu comenzile proprii ale limbilor pentru încărcarea datelor, nu trebuie neapărat să le testați. În astfel de cazuri, puteți să furnizați datele "recuperate" și să continuați cu testarea dvs., cum ar fi ceea ce am făcut cu șirul de comandă din biblioteca de etichete. Dar, în acest caz, testează funcționalitatea recursivă pe care o adăugăm la capacitățile de citire a fișierelor în limbi, în funcție de adâncimea specificată. În astfel de cazuri, trebuie să scrieți un test și, prin urmare, trebuie să creați câteva fișiere demo pentru a testa citirea fișierului. Alternativa este de a stăpâni probabil fs funcționează doar pentru a rula, dar nu pentru a face nimic, și apoi putem conta de câte ori funcția noastră falsă a fugit sau ceva de genul asta (a verifica spioni), dar pentru exemplul nostru, eu am de gând să creeze unele fișiere.

Mocha oferă funcții care pot funcționa atât înainte cât și după teste, astfel încât să puteți efectua aceste tipuri de configurare și curățare externă în jurul testelor dvs..

Pentru exemplul nostru, vom crea câteva teste și fișiere de testare la două adâncimi diferite, astfel încât să putem testa această funcție:

var aștepta = cere ("chai"). var căutare = solicită ("... /lib/search.js"); var fs = cer ("fs"); descrie ("Search", funcția () descrie ("# scan ()", funcția () prefixul_files "); fs.writeFileSync (" .test_files / a ","); fs.writeFileSync (".test_files / b", ""); fs.mkdirSync (".test_files / dir2 / d", "");); după (funcția () fs.unlinkSync (".test_files / dir / c"); fs.rmdirSync (".test_files / dir"); fs.unlinkSync (".test_files / dir2 / d"); "); fs.unlinkSync (" .test_files / a "); fs.unlinkSync (" .test_files / b "); fs.rmdirSync (" .test_files ");))););

Acestea vor fi chemați pe baza descrie bloc în care sunt în, și puteți rula chiar cod înainte și după fiecare aceasta blocarea folosind beforeEach sau după fiecare in schimb. Funcțiile în sine utilizează doar comenzi standard pentru noduri pentru a crea și a elimina respectiv fișierele. În continuare trebuie să scriem testul real. Acest lucru ar trebui să meargă chiar lângă după funcția, încă în interiorul descrie bloc:

 ("ar trebui să recupereze fișierele dintr-un director", funcția (done) search.scan (".test_files", 0, funcția (err, flist) expect (flist) .to.deep.equal / a "," .test_files / b "," .test_files / dir / c "," .test_files / dir2 / d "];);););

Acesta este primul nostru exemplu de testare a unei funcții de asincracție, dar, după cum puteți vedea, este la fel de simplu ca înainte; tot ce trebuie să facem este să folosim Terminat funcție pe care Mocha o oferă în aceasta declarații pentru a le spune când ne-am încheiat cu acest test.

Mocha va detecta automat dacă ați specificat Terminat variabilă în apelul de apel și va aștepta ca acesta să fie numit permițându-vă să testați codul asincron foarte ușor. De asemenea, merită menționat faptul că acest model este disponibil în întreaga Mocha, de exemplu, puteți folosi acest lucru în inainte de sau după dacă doriți să configurați ceva în mod asincron.

În continuare aș dori să scriu un test care să asigure funcționarea parametrului de adâncime dacă este setat:

 ("ar trebui să se oprească la o anumită adâncime", funcția (făcut) search.scan (".test_files", 1, funcția (err, flist) expect (flist) .to.deep.equal a "," .test_files / b ",]); done ();););

Nimic diferit aici, doar un alt test simplu. Făcând acest lucru în Mocha, veți avea o eroare că căutarea nu are metode, practic pentru că nu am scris nimic în ea. Deci, hai să adăugăm o schiță cu funcția:

var fs = cer ("fs"); export = modul.exports = ; exports.scan = funcție (dir, adâncime, făcut) 

Dacă rulați din nou Mocha, va întrerupe așteptarea revenirii acestei funcții de asincronizare, dar din moment ce nu am sunat deloc la apel, testul va termina. În mod implicit, ar trebui să expire după aproximativ două secunde, dar puteți ajusta acest lucru utilizând this.timeout (milisecunde) în interiorul unui bloc descrie sau bloc, pentru a-și ajusta temporar timpul de expirare.

Această funcție de scanare trebuie să ia o cale și o adâncime și să returneze o listă a tuturor fișierelor pe care le găsește. Acest lucru este de fapt cam greu atunci când începeți să vă gândiți cum suntem în esență recurgând la două funcții diferite într-o singură funcție. Trebuie să recursăm prin diferite foldere și apoi acele foldere trebuie să se scaneze și să decidă să meargă mai departe.

Făcând acest lucru în mod sincron, este bine pentru că puteți să faceți un pas printr-unul câte unul, finalizând lent un nivel sau o cale în același timp. Atunci când se ocupă de o versiune de asincronizare, este ceva mai complicat, deoarece nu puteți face a pentru fiecare bucla sau ceva, pentru că nu va întrerupe între foldere, toate vor executa în mod esențial, în același timp, fiecare revenind diferite valori și ar fi un fel de suprascriere.

Deci, pentru a face să funcționeze, trebuie să creați un tip de stivă în care să puteți procesa în mod asincron unul câte unul (sau toate simultan dacă utilizați o coadă) și apoi păstrați o ordine în acest mod. Este un algoritm foarte specific, așa că păstrez doar un fragment de Christopher Jeffrey pe care îl puteți găsi pe Stack Overflow. Nu se aplică doar la încărcarea fișierelor, dar am folosit acest lucru într-un număr de aplicații, în esență orice în cazul în care aveți nevoie pentru a procesa o serie de obiecte unul câte unul folosind funcții async.

Trebuie să o modificăm puțin, pentru că am dori să avem o opțiune de adâncime, cum funcționează opțiunea de adâncime, dacă setați câte niveluri de foldere doriți să verificați sau zero pentru a reveni la nesfârșit.

Iată funcția completă utilizând fragmentul:

exports.scan = funcție (dir, adâncime, făcut) depth--; var rezultatele = []; fs.readdir (dir, funcția (err, listă) if (err) întoarcere făcută (err); var i = 0; (if (depth = = 0) var ndepth (fișier, funcția (err, stat) = (adâncime> 1)? adâncime-1: 1; exports.scan (fișier, ndepth, funcția (err, res) ; altceva results.push (file); next (););) ();); ;

Mocha ar trebui să treacă acum ambele teste. Ultima funcție pe care trebuie să o implementăm este cea care va accepta o serie de căi și un cuvânt cheie de căutare și va returna toate potrivirile. Aici este testul pentru el:

 descrie ("# match"), funcția () it ("ar trebui să găsească și să returneze potrivirile bazate pe o interogare", funcția () var files = ["hello.txt", "world.js"; ["world.js", "another.js"]); results = search.match ("js.js", rezultatele) .de.deep.equal ("salut", fișiere); așteptați (rezultate) .to.deep.equal (["hello.txt"]);););

Și nu în ultimul rând, să adăugăm funcția search.js:

exports.match = funcție (interogare, fișiere) var matches = []; files.forEach (funcția (nume)) if (nume.indexOf (interogare)! == -1) matches.push (name);); meciuri de întoarcere; 

Doar pentru a vă asigura că conduceți din nou Mocha, trebuie să aveți un număr total de șapte teste care trec.



Punându-le pe toți împreună

Ultimul pas este de a scrie cu adevărat codul lipiciului care trage toate modulele împreună; astfel încât în ​​rădăcina proiectului nostru să adăugăm un fișier numit app.js sau ceva de genul acesta și adăugați următoarele în interiorul:

#! / usr / bin / env nod var tags = cer ("./ lib / tags.js"); var search = necesită ("./ lib / search.js"); var defaults = cale: ".", interogare: "", adâncime: 2 var înlocuiri = p: "cale", q: "interogare"; .parse (process.argv, implicite, înlocuiri); dacă (tags.help) console.log ("Utilizare: ./app.js -q = interogare [-d = adâncime] [-p = cale]");  altceva search.scan (tags.path, tags.depth, funcția (err, fișiere)


Cod