Test in Node.js

Un ciclo di sviluppo basato su test semplifica il processo di pensiero della scrittura del codice, rende più facile e più veloce nel lungo periodo. Ma solo la scrittura di test non è sufficiente da sola, conoscendo il tipo di test da scrivere e il modo in cui strutturare il codice per conformarsi a questo modello è ciò di cui si tratta. In questo articolo daremo un'occhiata a costruire una piccola app in Node.js seguendo un modello TDD.

Oltre ai semplici test di "unità", che tutti conosciamo; Possiamo anche far funzionare il codice Async di Node.js, che aggiunge un extra dimensione in quanto non sempre conosciamo l'ordine in cui le funzioni verranno eseguite o potremmo provare a testare qualcosa in una richiamata o controllare per vedere come funziona una funzione asincrona.

In questo articolo creeremo un'app Node che può cercare file che corrispondono a una determinata query. So che ci sono già cose per questo (ack) ma per dimostrare TDD penso che potrebbe essere un progetto ben arrotondato.

Il primo passo è ovviamente quello di scrivere alcuni test, ma anche prima, dobbiamo scegliere un framework di test. È possibile utilizzare il nodo vanilla, in quanto vi è un affermare libreria built-in, ma non è molto in termini di test runner, ed è praticamente l'essenziale.

Un'altra opzione e probabilmente la mia preferita per uso generale è Jasmine. È abbastanza autonomo, non hai altre dipendenze da aggiungere agli script e la sintassi è molto pulita e facile da leggere. L'unica ragione per cui non lo userò oggi, è perché penso che Jack Franklin abbia fatto un lavoro eccellente coprendolo nella sua recente serie Tuts + qui, ed è bello conoscere le tue opzioni in modo da poter scegliere lo strumento migliore per la tua situazione.


Cosa costruiremo

In questo articolo useremo il flessibile runner di prova "Mocha" insieme alla libreria di asserzione Chai.

A differenza di Jasmine, che è più simile a un'intera suite di test in un unico pacchetto, Mocha si occupa solo della struttura generale, ma non ha nulla a che fare con le asserzioni effettive. Ciò consente di mantenere un aspetto coerente quando si eseguono i test, ma consente anche di eseguire qualsiasi libreria di asserzioni che si adatti al meglio alla propria situazione.

Ad esempio, se avessi intenzione di utilizzare la libreria "assert" di Vanilla, potresti accoppiarlo con Mocha per aggiungere qualche struttura ai tuoi test.

Chai è un'opzione abbastanza popolare, e riguarda anche opzioni e modularità. Anche senza plugin, usando l'API predefinita hai tre diverse sintassi che puoi usare a seconda se vuoi usare uno stile TDD più classico o una sintassi BDD più dettagliata.

Quindi ora che sappiamo cosa useremo, entriamo nell'installazione.


Il set up

Per iniziare, installiamo Mocha globalmente eseguendo:

npm install -g mocha

Al termine, crea una nuova cartella per il nostro progetto ed esegui quanto segue al suo interno:

npm install chai

Questo installerà una copia locale di Chai per il nostro progetto. Quindi, creare una cartella denominata test all'interno della directory del nostro progetto, poiché questa è la posizione predefinita che Mocha cercherà per i test.

Questo è più che altro per l'installazione, il passo successivo è parlare di come strutturare le tue app quando si segue un processo di sviluppo basato su test.


Strutturare la tua app

È importante sapere, quando si segue un approccio TDD, cosa è necessario fare test e cosa no. Una regola empirica è di non scrivere test per codice di altre persone già testato. Quello che intendo con questo è il seguente: diciamo che il tuo codice apre un file, non è necessario testare l'individuo fs funzione, è parte del langue ed è presumibilmente già ben testato. Lo stesso vale quando si utilizzano librerie di terze parti, non si dovrebbero strutturare funzioni che chiamino principalmente questi tipi di funzioni. In realtà non si scrivono test per questi e per questo si hanno lacune nel ciclo TDD.

Ora, naturalmente, con ogni stile di programmazione ci sono molte opinioni diverse e le persone avranno opinioni diverse su come usare TDD. L'approccio che utilizzo è che crei singoli componenti da utilizzare nella tua app, ognuno dei quali risolve un problema funzionale unico. Questi componenti sono creati utilizzando TDD assicurando che funzionino come previsto e che non interrompano la loro API. Quindi scrivi il tuo script principale, che è essenzialmente tutto il codice della colla, e non ha bisogno di essere testato / non può essere testato, in certe situazioni.

Ciò significa anche che la maggior parte dei tuoi componenti può essere riutilizzata in futuro in quanto non hanno molto da fare, direttamente, con lo script principale.

Seguendo quello che ho appena detto, è prassi comune creare una cartella denominata 'lib'dove metti tutti i singoli componenti. Quindi fino a questo punto dovresti avere installato Mocha e Chai, quindi una directory di progetto con due cartelle: "lib' e 'test'.


Iniziare con TDD

Nel caso in cui sei nuovo di TDD ho pensato che sarebbe una buona idea per coprire rapidamente il processo. La regola di base è che non puoi scrivere alcun codice a meno che il test runner non ti dica di farlo.

In sostanza, stai scrivendo cosa dovrebbe fare il tuo codice prima di farlo davvero. Hai un obiettivo davvero mirato durante la codifica e non hai mai compromesso la tua idea seguendo il percorso o pensando troppo in anticipo. Inoltre, dal momento che tutto il tuo codice avrà un test affiliato, potrai essere certo che non riuscirai mai a rompere la tua app in futuro.

Un test, in realtà, è solo una dichiarazione di cosa si prevede che una funzione esegua durante l'esecuzione, quindi esegui il tuo runner di test, che ovviamente fallirà (dal momento che non hai ancora scritto il codice) e quindi scrivi l'importo minimo del codice necessario per superare il test in errore. È importante non saltare mai questo passaggio, perché a volte un test passerà anche prima di aggiungere qualsiasi codice, a causa dell'altro codice presente nella stessa classe o funzione. Quando ciò accade, o hai scritto più codice allora dovevi fare un test diverso o questo è solo un test negativo (di solito non abbastanza specifico).

Sempre secondo la nostra regola sopra, se il test passa subito non puoi scrivere alcun codice, perché non te lo ha detto. Scrivendo continuamente i test e poi implementando le funzionalità, costruisci moduli solidi su cui puoi fare affidamento.

Una volta che hai finito di implementare e testare il tuo componente, puoi tornare indietro e rifattorare il codice per ottimizzarlo e pulirlo ma assicurandoti che il refactoring non fallisca nessuno dei test che hai in atto e, cosa più importante, non lo fa t aggiungere caratteristiche che non sono state testate.

Ogni libreria di test avrà una propria sintassi, ma di solito seguono lo stesso schema di asserzione e quindi verifica se passano. Dal momento che stiamo usando Mocha e Chai diamo un'occhiata a entrambe le loro sintassi che iniziano con Chai.


Moka e Chai

Userò la sintassi BDD 'Expect', perché come ho detto Chai ha alcune opzioni fuori dalla scatola. Il modo in cui questa sintassi funziona è che si inizia chiamando la funzione expect, passando l'oggetto su cui si desidera eseguire un'asserzione e quindi si concatena con un test specifico. Un esempio di ciò che intendo potrebbe essere il seguente:

si aspettano (4 + 5) .equal (9);

Questa è la sintassi di base, stiamo dicendo aspettatevi l'aggiunta di 4 e 5 uguale 9. Ora questo non è un grande test perché il 4 e 5 verrà aggiunto da Node.js prima che venga chiamata anche la funzione, quindi stiamo essenzialmente testando le mie abilità matematiche, ma spero che tu abbia un'idea generale. L'altra cosa che dovresti notare è che questa sintassi non è molto leggibile, in termini di flusso di una normale frase inglese. Sapendo questo, Chai ha aggiunto i seguenti getter a catena che non fanno nulla ma puoi aggiungerli per renderlo più prolisso e leggibile. I getter della catena sono i seguenti:

  • a
  • essere
  • stato
  • è
  • quello
  • e
  • avere
  • con
  • a
  • di
  • stesso
  • un
  • un

Usando quanto sopra, possiamo riscrivere il nostro test precedente con qualcosa del genere:

si aspettano (4 + 5) .to.equal (9);

Mi piace molto l'aspetto dell'intera libreria, che puoi controllare nella loro API. Cose semplici come la negazione dell'operazione sono facili come scrivere .non prima del test:

si aspettano (4 + 5) .to.not.equal (10);

Quindi, anche se non hai mai usato la libreria in precedenza, non sarà difficile capire cosa sta provando a fare un test.

L'ultima cosa che vorrei esaminare prima di entrare nel nostro primo test è come strutturiamo il nostro codice in Mocha

moca

Mocha è il runner di test, quindi non si preoccupa troppo dei test effettivi, ciò che importa è la struttura dei test, perché è così che sa cosa sta fallendo e come impaginare i risultati. Il modo in cui lo costruisci, ne crei più descrivere blocchi che descrivono i diversi componenti della libreria e quindi aggiungono esso blocchi per specificare un test specifico.

Per un esempio veloce, diciamo che avevamo una classe JSON e che la classe aveva una funzione per analizzare JSON e volevamo assicurarci che la funzione di analisi potesse rilevare una stringa JSON formattata male, potremmo strutturarla in questo modo:

describe ("JSON", function () describe (". parse ()", function () it ("dovrebbe rilevare stringhe JSON malformate", function () // Test Go Here Here););) ;

Non è complicato, e rappresenta circa l'80% delle preferenze personali, ma se si mantiene questo tipo di formato, i risultati del test dovrebbero risultare in un formato molto leggibile.

Ora siamo pronti per scrivere la nostra prima libreria, iniziamo con un semplice modulo sincrono, per conoscere meglio il sistema. La nostra app dovrà essere in grado di accettare le opzioni della riga di comando per impostare cose come quanti livelli di cartelle deve cercare la nostra app e la query stessa.

Per occuparci di tutto ciò, creeremo un modulo che accetta la stringa del comando e analizza tutte le opzioni incluse insieme ai loro valori.

Il modulo tag

Questo è un ottimo esempio di un modulo che puoi riutilizzare in tutte le tue app a linea di comando, poiché questo problema si presenta molto. Questa sarà una versione semplificata di un vero pacchetto che ho su NPM chiamato ClTags. Quindi, per iniziare, crea un file chiamato tags.js all'interno della cartella lib e quindi un altro file chiamato tagsSpec.js all'interno della cartella di test.

Abbiamo bisogno di inserire la funzione Chai expect, poiché questa sarà la sintassi dell'asserzione che useremo e dobbiamo inserire il vero file dei tag per poterlo testare. Complessivamente con alcune impostazioni iniziali dovrebbe assomigliare a questo:

var expect = require ("chai"). expect; var tag = require ("... /lib/tags.js"); descrivere ("Tag", function () );

Se esegui il comando 'mocha' ora dalla radice del nostro progetto, tutto dovrebbe passare come previsto. Ora pensiamo a cosa farà il nostro modulo; vogliamo passargli il comando array di argomenti che è stato usato per eseguire l'app, e poi vogliamo che costruisca un oggetto con tutti i tag, e sarebbe bello se potessimo anche passargli un oggetto predefinito di impostazioni, quindi se niente viene sovrascritto, avremo alcune impostazioni già memorizzate.

Quando si ha a che fare con i tag, molte app forniscono anche opzioni di scelta rapida che sono solo un carattere, quindi diciamo che volevamo impostare la profondità della nostra ricerca potremmo consentire all'utente di specificare qualcosa come --profondità = 2 o qualcosa del genere -d = 2 che dovrebbe avere lo stesso effetto.

Quindi iniziamo con i tag long format (ad esempio, '--depth = 2'), per cominciare, scriviamo il primo test:

describe ("Tags", function () describe ("# parse ()", function () it ("dovrebbe analizzare tag formati lunghi", 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 ("Ciao mondo"); ); ); );

Abbiamo aggiunto un metodo alla nostra suite di test chiamato analizzare e abbiamo aggiunto un test per tag lunghi formati. All'interno di questo test ho creato un comando di esempio e aggiunto due asserzioni per le due proprietà che deve essere prelevata.

In esecuzione Mocha ora, si dovrebbe ottenere un errore, cioè quello tag non ha un analizzare funzione. Quindi per correggere questo errore aggiungiamo un analizzare funzione al modulo tags. Un modo abbastanza tipico per creare un modulo nodo è come questo:

exports = module.exports = ; exports.parse = function () 

L'errore ha detto che avevamo bisogno di un analizzare metodo così l'abbiamo creato, non abbiamo aggiunto altro codice all'interno perché non ci ha ancora detto di farlo. Attaccandoti con il minimo ti assicuri che non scriverai più di quello che dovresti e finirai con codice non testato.

Ora eseguiamo nuovamente Mocha, questa volta dovremmo ricevere un errore che ci dice che non è possibile leggere una proprietà denominata profondità da una variabile indefinita. Questo perché attualmente il nostro analizzare la funzione non restituisce nulla, quindi aggiungiamo del codice in modo che restituisca un oggetto:

exports.parse = function () var options =  opzioni di restituzione; 

Stiamo procedendo lentamente, se esegui nuovamente Mocha, non dovrebbero essere generate eccezioni, solo un messaggio di errore pulito che dice che il nostro oggetto vuoto non ha alcuna proprietà chiamata profondità.


Ora possiamo entrare in qualche codice reale. Per la nostra funzione di analizzare il tag e aggiungerlo al nostro oggetto, è necessario scorrere l'array di argomenti e rimuovere i due trattini all'inizio della chiave.

exports.parse = function (args) var options =  for (var i in args) // Passa attraverso args var arg = args [i]; // Controlla se il tag Long format if (arg.substr (0, 2) === "-") arg = arg.substr (2); // Controlla il segno di uguale se (arg.indexOf ("=")! == -1) arg = arg.split ("="); var key = arg.shift (); opzioni [chiave] = arg.join ("=");  opzioni di restituzione; 

Questo codice scorre l'elenco degli argomenti, assicura che si tratti di un tag formato lungo e quindi lo divide dal primo carattere uguale per creare la coppia chiave e valore per l'oggetto opzioni.

Ora questo quasi risolve il problema, ma se eseguiamo nuovamente Mocha, vedremo che ora abbiamo una chiave per la profondità, ma è impostata su una stringa anziché su un numero. I numeri sono un po 'più facili da utilizzare più avanti nella nostra app, quindi la prossima parte di codice che dobbiamo aggiungere è convertire i valori in numeri quando possibile. Questo può essere ottenuto con alcuni RegEx e il parseInt funzione come segue:

 if (arg.indexOf ("=")! == -1) arg = arg.split ("="); var key = arg.shift (); var value = arg.join ("="); if (/^[0-9]+$/.test(value)) value = parseInt (valore, 10);  opzioni [chiave] = valore; 

Eseguendo Mocha ora, dovresti ottenere un passaggio con un test. La conversione del numero dovrebbe essere indiscutibilmente testata, o almeno menzionata nella dichiarazione di test, in modo da non rimuovere per errore l'asserzione di conversione del numero; quindi basta aggiungere "aggiungi e converti numeri" a esso dichiarazione per questo test o separarlo in un nuovo esso bloccare. Dipende davvero se consideri questo "comportamento predefinito ovvio" o una funzione separata.


Ora, come ho cercato di sottolineare in tutto questo articolo, quando vedi una specifica passante, è il momento di scrivere altri test. La prossima cosa che volevo aggiungere era la matrice di default, quindi dentro tagsSpec file aggiungiamo quanto segue esso bloccare subito dopo il precedente:

 ("dovrebbe analizzare tag formati lunghi e convertire numeri", function () var args = ["--depth = 4", "--hello = world"]; var results = tags.parse (args); risultati) .per.have.a.property ("depth", 4); expect (results) .to.have.a.property ("hello", "world");); ("dovrebbe fallback to default", function () var args = ["--depth = 4", "--hello = world"]; var defaults = depth: 2, foo: "bar"; var results = tags.parse (args, defaults); var expected = depth: 4, foo: "bar", ciao: "world"; expect (results) .to.deep.equal (expected););

Qui stiamo usando un nuovo test, l'equal deep che è buono per la corrispondenza di due oggetti con valori uguali. In alternativa, puoi usare il EQL test che è una scorciatoia ma penso che questo sia più chiaro. Questo test passa due argomenti come stringa di comando e passa due valori di default con una sovrapposizione, solo così possiamo ottenere una buona diffusione sui casi di test.

Eseguendo Mocha ora, dovresti ottenere una sorta di diff, contenente le differenze tra ciò che è previsto e ciò che ha effettivamente ottenuto.


Torniamo ora al tags.js modulo, e aggiungiamo questa funzionalità in. È una correzione abbastanza semplice da aggiungere, dobbiamo solo accettare il secondo parametro, e quando è impostato su un oggetto possiamo sostituire l'oggetto vuoto standard all'inizio con questo oggetto:

exports.parse = function (args, defaults) var options = ; if (typeof defaults === "oggetto" &&! (defaults instanceof Array)) options = defaults

Questo ci riporterà a uno stato verde. La prossima cosa che voglio aggiungere è la possibilità di specificare solo un tag senza un valore e lasciarlo funzionare come un booleano. Ad esempio, se ci limitiamo a impostare --searchContents o qualcosa del genere, lo aggiungerà al nostro array di opzioni con un valore di vero.

Il test per questo sarebbe simile al seguente:

 ("dovrebbe accettare tag senza valori come bool", function () var args = ["--searchContents"]; var results = tags.parse (args); expect (results) .to.have.a.property ("searchContents", true););

L'esecuzione di questo ci darà il seguente errore proprio come prima:


All'interno del per loop, quando abbiamo ottenuto una corrispondenza per un tag lungo formato, abbiamo controllato se conteneva un segno di uguale; possiamo scrivere rapidamente il codice per questo test aggiungendo un altro clausola a questo Se dichiarazione e semplicemente impostando il valore su vero:

 if (arg.indexOf ("=")! == -1) arg = arg.split ("="); var key = arg.shift (); var value = arg.join ("="); if (/^[0-9]+$/.test(value)) value = parseInt (valore, 10);  opzioni [chiave] = valore;  else options [arg] = true; 

La prossima cosa che voglio aggiungere sono le sostituzioni per i tag short-hand. Questo sarà il terzo parametro per il analizzare funzione e sarà fondamentalmente un oggetto con lettere e le relative sostituzioni corrispondenti. Ecco le specifiche per questa aggiunta:

 ("dovrebbe accettare tag di forma breve", function () var args = ["-sd = 4", "-h"]; var replacements = s: "searchContents", d: "depth", h: " ciao "; var results = tags.parse (args, , replacements); var expected = searchContents: true, depth: 4, hello: true; expect (results) .to.deep.equal (expected); );

Il problema con i tag di stenografia è che possono essere combinati in fila. Ciò che intendo con questo è diverso dai tag long format in cui ognuno è separato, con tag a mano breve - dato che sono ciascuno solo una lettera lunga - puoi chiamarne tre diversi digitando -VGH. Questo rende l'analisi un po 'più difficile perché dobbiamo ancora consentire all'operatore di uguali di aggiungere un valore all'ultimo tag menzionato, mentre allo stesso tempo è necessario registrare ancora gli altri tag. Ma non preoccuparti, non è niente che non possa essere risolto con abbastanza scoppi e cambi di marcia.

Ecco l'intera correzione, dall'inizio del analizzare funzione:

exports.parse = function (args, defaults, replacements) var options = ; if (typeof defaults === "oggetto" &&! (defaults instanceof Array)) options = defaults if (typeof replacements === "oggetto" &&! (default instanceof 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 [chiave];  args.push ("-" + key + "=" + value);  else arg = arg.split ("");  arg.forEach (function (key) if (replacements.hasOwnProperty (key)) key = replacements [chiave]; args.push ("-" + key);); 

È un sacco di codice (in confronto), ma tutto ciò che stiamo facendo è dividere l'argomento con un segno di uguale, quindi suddividere quella chiave nelle singole lettere. Quindi per esempio se siamo passati -gj = asd vorremmo dividere il asd in una variabile chiamata valore, e poi vorremmo dividere il gj sezione in singoli caratteri. L'ultimo personaggio (j nel nostro esempio) diventerà la chiave per il valore (asd) mentre qualsiasi altra lettera prima di essa, sarà aggiunta come tag booleani regolari. Non volevo elaborare solo questi tag ora, nel caso avessimo modificato l'implementazione in seguito. Quindi, ciò che stiamo facendo è convertire questi tag a mano breve nella versione lunga e poi lasciarla gestire in seguito.

Eseguire di nuovo Mocha ci riporterà ai nostri illustri risultati verdi di quattro test che passano per questo modulo.

Ora ci sono altre cose che possiamo aggiungere a questo modulo di tag per renderlo più vicino al pacchetto npm, come la possibilità di memorizzare anche argomenti di testo normale per cose come comandi o la possibilità di raccogliere tutto il testo alla fine, per un proprietà della query. Ma questo articolo sta già diventando lungo e vorrei passare all'attuazione della funzionalità di ricerca.


Il modulo di ricerca

Abbiamo appena creato un modulo passo dopo passo seguendo un approccio TDD e spero che tu abbia avuto l'idea e la sensazione di come scrivere in questo modo. Ma al fine di mantenere questo articolo in movimento, per il resto dell'articolo, accelererò il processo di test raggruppando le cose e mostrandovi solo le versioni finali dei test. È più una guida alle diverse situazioni che possono emergere e come scrivere test per loro.

Quindi basta creare un file chiamato search.js all'interno della cartella lib e a searchSpec.js file all'interno della cartella di test.

Successivamente apri il file spec e configuriamo il nostro primo test che può essere utilizzato dalla funzione per ottenere un elenco di file basati su a profondità parametro, questo è anche un ottimo esempio per i test che richiedono un po 'di configurazione esterna per farli funzionare. Quando si ha a che fare con dati esterni simili agli oggetti o nei file del nostro caso, si vorrà avere una configurazione predefinita che sai funzionerà con i test, ma non si desidera aggiungere informazioni false al proprio sistema.

Ci sono fondamentalmente due opzioni per risolvere questo problema, puoi prendere in giro i dati, come ho detto sopra se hai a che fare con i comandi propri della lingua per caricare i dati, non devi necessariamente testarli. In casi come questi, puoi semplicemente fornire i dati "recuperati" e continuare con il test, un po 'come quello che abbiamo fatto con la stringa di comando nella libreria dei tag. Ma in questo caso, stiamo testando la funzionalità ricorsiva che stiamo aggiungendo alle capacità di lettura dei file in lingua, a seconda della profondità specificata. In casi come questi, è necessario scrivere un test e quindi è necessario creare alcuni file demo per testare la lettura del file. L'alternativa è forse stub fs funzioni per eseguire semplicemente ma non fare nulla, e quindi possiamo contare quante volte la nostra funzione falsa ha funzionato o qualcosa del genere (controlla spie) ma per il nostro esempio, sto solo andando a creare alcuni file.

Mocha fornisce funzioni che possono essere eseguite sia prima che dopo i test, in modo da poter eseguire questo tipo di installazione e pulizia esterna attorno ai test.

Per il nostro esempio, creeremo un paio di file di test e cartelle a due diverse profondità per poter testare questa funzionalità:

var expect = require ("chai"). expect; var search = require ("... /lib/search.js"); var fs = require ("fs"); define ("Cerca", function () describe ("# scan ()", function () 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 "););););

Questi saranno chiamati in base al descrivere blocco in cui si trovano e puoi persino eseguire il codice prima e dopo ciascuno esso bloccare usando beforeeach o dopo ogni anziché. Le funzioni stesse usano solo i comandi nodo standard per creare e rimuovere i file rispettivamente. Quindi dobbiamo scrivere il test vero e proprio. Questo dovrebbe andare proprio accanto al dopo funzione, ancora dentro il descrivere bloccare:

 ("dovrebbe recuperare i file da una directory", 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 ();););

Questo è il nostro primo esempio di test di una funzione asincrona, ma come puoi vedere è semplice come prima; tutto ciò che dobbiamo fare è usare il fatto la funzione Mocha fornisce nel esso dichiarazioni per dirlo quando abbiamo finito con questo test.

Mocha rileva automaticamente se hai specificato il fatto variabile nel callback e attenderà che venga richiamato permettendoti di testare il codice asincrono con estrema facilità. Inoltre, vale la pena ricordare che questo modello è disponibile su Mocha, puoi ad esempio usarlo nel menu prima o dopo funzioni se fosse necessario impostare qualcosa in modo asincrono.

Successivamente vorrei scrivere un test che assicuri che il parametro depth funzioni se impostato:

 ("dovrebbe fermarsi ad una profondità specificata", function (done) search.scan (". test_files", 1, function (err, flist) expect (flist) .to.deep.equal ([".test_files / a "," .test_files / b ",]); done ();););

Niente di diverso qui, solo un altro semplice test. Eseguendo questo in Mocha si otterrà un errore che la ricerca non ha alcun metodo, fondamentalmente perché non abbiamo scritto nulla in esso. Quindi andiamo ad aggiungere un contorno con la funzione:

var fs = require ("fs"); exports = module.exports = ; exports.scan = function (dir, depth, done) 

Se ora esegui nuovamente Mocha, si interromperà in attesa del ritorno di questa funzione asincrona, ma poiché non abbiamo chiamato la funzione di callback, il test verrà interrotto. Per impostazione predefinita dovrebbe scadere dopo circa due secondi, ma è possibile regolarlo utilizzando this.timeout (millisecondi) all'interno di una descrizione o blocco, per regolare rispettivamente i loro timeout.

Questa funzione di scansione dovrebbe prendere un percorso e una profondità e restituire un elenco di tutti i file che trova. Questo è in realtà un po 'complicato quando inizi a pensare a come stiamo essenzialmente ricoverando due diverse funzioni insieme in una singola funzione. Abbiamo bisogno di recurse attraverso le diverse cartelle e quindi quelle cartelle devono eseguire la scansione di se stessi e decidere di andare oltre.

Fare questo in modo sincrono va bene perché puoi passarci attraverso uno ad uno, completando lentamente un livello o un percorso alla volta. Quando si ha a che fare con una versione asincrona diventa un po 'più complicato perché non si può semplicemente fare a per ciascuno loop o qualcosa del genere, perché non si fermerà tra le cartelle, funzioneranno essenzialmente tutte nello stesso momento, restituendo valori diversi e si sovrascriveranno reciprocamente.

Quindi, per farlo funzionare, è necessario creare una sorta di stack in cui è possibile elaborare in modo asincrono uno alla volta (o tutto in una volta se si utilizza una coda) e quindi mantenere un certo ordine in quel modo. È un algoritmo molto specifico, quindi tengo solo uno snippet di Christopher Jeffrey che puoi trovare su Stack Overflow. Non si applica solo al caricamento dei file, ma ho usato questo in un certo numero di applicazioni, in pratica qualsiasi cosa in cui è necessario elaborare una serie di oggetti uno alla volta utilizzando le funzioni asincrone.

Abbiamo bisogno di modificarlo un po ', perché vorremmo avere un'opzione di profondità, come funziona l'opzione di profondità è impostare quanti livelli di cartelle si desidera controllare, o zero per ricorrere indefinitamente.

Ecco la funzione completata che utilizza lo snippet:

exports.scan = function (dir, depth, done) depth--; var results = []; fs.readdir (dir, function (err, list) if (err) return done (err); var i = 0; (funzione next () var file = list [i ++]; if (! file) return done ( null, risultati); file = dir + '/' + file; fs.stat (file, funzione (err, stat) if (stat && stat.isDirectory ()) if (depth! == 0) var ndepth = (profondità> 1)? profondità-1: 1; exports.scan (file, ndepth, funzione (err, res) results = results.concat (res); next ();); else next () ; else results.push (file); next (););) ();); ;

Mocha dovrebbe ora superare entrambi i test. L'ultima funzione che dobbiamo implementare è quella che accetterà una serie di percorsi e una parola chiave di ricerca e restituirà tutte le corrispondenze. Ecco il test per questo:

 define ("# match ()", function () it ("dovrebbe trovare e restituire corrispondenze basate su una query", function () var files = ["hello.txt", "world.js", "altro. js "]; var results = search.match (" .js ", files); expect (results) .to.deep.equal ([" world.js "," another.js "]); results = search.match ("ciao", file); expect (results) .to.deep.equal (["hello.txt"]);););

E, ultimo ma non meno importante, aggiungiamo la funzione a search.js:

exports.match = function (query, files) var matches = []; files.forEach (function (name) if (name.indexOf (query)! == -1) matches.push (nome);); corrispondenze di ritorno; 

Per sicurezza, esegui di nuovo Mocha, dovresti avere un totale di sette test in tutto