Pruebas en Node.js

Un ciclo de desarrollo basado en pruebas simplifica el proceso de pensamiento de escribir código, lo hace más fácil y más rápido a largo plazo. Pero de lo que se trata solo es escribir las pruebas no es suficiente por sí mismo, saber de qué tipo de pruebas se deben escribir y cómo estructurar el código para ajustarse a este patrón. En este artículo, veremos cómo construir una pequeña aplicación en Node.js siguiendo un patrón TDD.

Además de las simples pruebas de 'unidad', con las que todos estamos familiarizados; También podemos ejecutar el código asíncrono de Node.js, que agrega un extra dimensión en el sentido de que no siempre sabemos el orden en el que se ejecutarán las funciones o podemos estar intentando probar algo en una devolución de llamada o verificando cómo funciona una función asíncrona.

En este artículo, crearemos una aplicación Node que puede buscar archivos que coincidan con una consulta determinada. Sé que ya hay cosas para esto (ack) pero por el bien de demostrar TDD, creo que podría ser un proyecto completo.

El primer paso es, obviamente, escribir algunas pruebas, pero incluso antes de eso, debemos elegir un marco de prueba. Puedes usar el Nodo de vainilla, ya que hay un afirmar biblioteca incorporada, pero no es mucho en términos de un corredor de prueba, y es prácticamente lo esencial.

Otra opción y probablemente mi favorita para uso general es Jasmine. Es bastante autónomo, no tiene otras dependencias para agregar a sus scripts y la sintaxis es muy clara y fácil de leer. La única razón por la que no voy a usar esto hoy es porque creo que Jack Franklin hizo un excelente trabajo cubriendo esto en su reciente serie Tuts + aquí, y es bueno conocer sus opciones para que pueda elegir la mejor herramienta para su situación..


Lo que estaremos construyendo

En este artículo utilizaremos el corredor de prueba flexible 'Mocha' junto con la biblioteca de aserciones de Chai.

A diferencia de Jasmine, que es más como un conjunto de pruebas completo en un paquete, Mocha solo se ocupa de la estructura general, pero no tiene nada que ver con las afirmaciones reales. Esto le permite mantener una apariencia coherente al ejecutar sus pruebas, pero también le permite ejecutar la biblioteca de aserciones que mejor se adapte a su situación.

Por ejemplo, si vas a utilizar la biblioteca de 'aserción' de vainilla, puedes emparejarla con Mocha para agregar algo de estructura a tus pruebas.

Chai es una opción bastante popular, y también se trata de opciones y modularidad. Incluso sin ningún complemento, solo usando la API predeterminada tiene tres sintaxis diferentes que puede usar dependiendo de si le gustaría usar un estilo TDD más clásico o una sintaxis BDD más detallada..

Así que ahora que sabemos lo que vamos a usar, entremos en la instalación.


La puesta en marcha

Para empezar, instalemos Mocha globalmente ejecutando:

npm instalar -g mocha

Cuando termine, cree una nueva carpeta para nuestro proyecto y ejecute lo siguiente:

npm instalar chai

Esto instalará una copia local de Chai para nuestro proyecto. A continuación, crea una carpeta llamada prueba Dentro del directorio de nuestro proyecto, ya que esta es la ubicación predeterminada, Mocha buscará pruebas.

Eso es todo para la configuración, el siguiente paso es hablar sobre cómo estructurar sus aplicaciones cuando se sigue un proceso de desarrollo basado en pruebas..


Estructurando tu aplicación

Es importante saber, al seguir un enfoque de TDD, qué se necesita para las pruebas y qué no. Una regla de oro es no escribir pruebas para el código ya probado de otras personas. Lo que quiero decir con esto es lo siguiente: digamos que su código abre un archivo, no necesita probar el individuo fs función, es parte del lenguaje y supuestamente ya está bien probado. Lo mismo ocurre cuando se utilizan bibliotecas de terceros, no debe estructurar funciones que llamen principalmente a estos tipos de funciones. Realmente no escribes pruebas para estos y debido a esto tienes brechas en el ciclo TDD.

Ahora, por supuesto, con cada estilo de programación hay muchas opiniones diferentes y las personas tendrán diferentes puntos de vista sobre cómo hacer TDD. Pero el enfoque que utilizo es que crea componentes individuales para usar en su aplicación, cada uno de los cuales resuelve un problema funcional único. Estos componentes se crean utilizando TDD, lo que garantiza que funcionen como se espera y que no romperá su API. Luego escribe su script principal, que es esencialmente todo el código de pegamento, y no necesita ser probado / no puede ser probado, en ciertas situaciones.

Esto también significa que la mayoría de sus componentes se pueden reutilizar en el futuro, ya que no tienen mucho que hacer directamente con el script principal..

Siguiendo lo que acabo de decir, es una práctica común crear una carpeta llamada 'lib'donde pones todos los componentes individuales. Entonces, hasta este punto, deberías tener instalado Mocha y Chai, y luego un directorio de proyectos con dos carpetas: 'lib'y'prueba'.


Empezando con TDD

En caso de que sea nuevo en TDD, pensé que sería una buena idea cubrir rápidamente el proceso. La regla básica es que no puede escribir ningún código a menos que el corredor de pruebas le indique que.

Esencialmente, estás escribiendo lo que se supone que debe hacer tu código antes de hacerlo realmente. Tienes una meta muy enfocada mientras codificas y nunca comprometes tu idea al dejar de lado o pensar demasiado hacia el futuro. Además, dado que todo su código tendrá una prueba asociada con él, puede estar seguro de que nunca romperá su aplicación en el futuro.

Una prueba, en realidad, es solo una declaración de lo que se espera que haga una función cuando se ejecute, luego ejecuta su corredor de prueba, que obviamente fallará (ya que aún no ha escrito el código) y luego escribe la cantidad mínima de código necesario para pasar la prueba de falla. Es importante que nunca omita este paso, porque a veces se pasa una prueba incluso antes de agregar cualquier código, debido a otro código que tenga en la misma clase o función. Cuando esto sucede, o bien escribiste más código de lo que se suponía que debías hacer para una prueba diferente o esto es solo una prueba mala (generalmente no es lo suficientemente específica).

Nuevamente, de acuerdo con nuestra regla anterior, si la prueba pasa de inmediato, no puede escribir ningún código, porque no se lo indicó. Al escribir continuamente pruebas y luego implementar las funciones, construye módulos sólidos en los que puede confiar.

Una vez que haya terminado de implementar y probar su componente, puede regresar y refactorizar el código para optimizarlo y limpiarlo, pero asegurándose de que la refactorización no falla ninguna de las pruebas que tiene implementadas y, lo que es más importante, no lo haga. t agregar cualquier característica que no esté probada.

Cada biblioteca de prueba tendrá su propia sintaxis, pero generalmente siguen el mismo patrón de hacer afirmaciones y luego verifican si pasan. Ya que estamos usando Mocha y Chai, echemos un vistazo a ambas sintaxis que comienzan con Chai.


Mocha y Chai

Usaré la sintaxis de BDD 'Expect', porque como mencioné, Chai viene con algunas opciones fuera de la caja. La forma en que funciona esta sintaxis es comenzar por llamar a la función expectativa, pasarle el objeto sobre el que desea hacer una afirmación y luego encadenarlo con una prueba específica. Un ejemplo de lo que quiero decir podría ser el siguiente:

esperar (4 + 5) .equal (9);

Esa es la sintaxis básica, estamos diciendo que espere la adición de 4 y 5 A igual 9. Ahora bien, esto no es una gran prueba porque la 4 y 5 Node.js lo agregará antes de que se llame la función, por lo que esencialmente estamos probando mis habilidades matemáticas, pero espero que se haga una idea general. La otra cosa que debe tener en cuenta es que esta sintaxis no es muy legible, en términos del flujo de una oración normal en inglés. Sabiendo esto, Chai agregó los siguientes organizadores de cadenas que no hacen nada, pero puedes agregarlos para hacerlos más detallados y legibles. Los buscadores de cadena son los siguientes:

  • a
  • ser
  • estado
  • es
  • ese
  • y
  • tener
  • con
  • a
  • de
  • mismo
  • una
  • un

Usando lo anterior, podemos reescribir nuestra prueba anterior a algo como esto:

espera (4 + 5) .to.equal (9);

Realmente me gusta la sensación de toda la biblioteca, que puedes consultar en su API. Cosas simples como negar la operación es tan fácil como escribir .no antes de la prueba:

espera (4 + 5) .to.not.equal (10);

Así que incluso si nunca ha usado la biblioteca antes, no será difícil averiguar qué es lo que una prueba está tratando de hacer.

Lo último que me gustaría revisar antes de comenzar nuestra primera prueba es cómo estructuramos nuestro código en Mocha

Moca

Mocha es el corredor de pruebas, por lo que realmente no se preocupa demasiado por las pruebas reales, lo que le importa es la estructura de las pruebas, porque así es como sabe qué está fallando y cómo diseñar los resultados. La forma en que lo construyes, es crear múltiples describir bloques que describen los diferentes componentes de su biblioteca y luego agrega eso Bloques para especificar una prueba específica.

Para un ejemplo rápido, digamos que teníamos una clase JSON y que esa clase tenía una función para analizar JSON y queríamos asegurarnos de que la función de análisis pueda detectar una cadena JSON mal formateada, podríamos estructurarlo así:

describe ("JSON", function () describe (". parse ()", function () it ("debería detectar cadenas JSON mal formadas", function () // Test Goes Here)););) ;

No es complicado, y es alrededor del 80% de preferencia personal, pero si mantiene este tipo de formato, los resultados de las pruebas deberían aparecer en un formato muy legible..

Ahora estamos listos para escribir nuestra primera biblioteca, comencemos con un módulo síncrono simple, para familiarizarnos mejor con el sistema. Nuestra aplicación tendrá que ser capaz de aceptar opciones de línea de comandos para configurar cosas como cuántos niveles de carpetas debería buscar nuestra aplicación y la consulta en sí..

Para encargarnos de todo esto, crearemos un módulo que acepte la cadena del comando y analice todas las opciones incluidas junto con sus valores..

El módulo de etiquetas

Este es un gran ejemplo de un módulo que puede reutilizar en todas sus aplicaciones de línea de comandos, ya que este problema surge mucho. Esta será una versión simplificada de un paquete real que tengo en npm llamado ClTags. Así que para empezar, crea un archivo llamado tags.js dentro de la carpeta lib, y luego otro archivo llamado tagsSpec.js dentro de la carpeta de prueba.

Necesitamos activar la función Chai expect, ya que esa será la sintaxis de aserción que utilizaremos y debemos introducir el archivo de etiquetas real para poder probarlo. En conjunto con una configuración inicial, debería verse algo como esto:

var espera = requiere ("chai"). espera; var tags = require ("… /lib/tags.js"); describe ("Etiquetas", función () );

Si ejecuta el comando 'mocha' ahora desde la raíz de nuestro proyecto, todo debería pasar como se esperaba. Ahora pensemos en lo que hará nuestro módulo; queremos pasarle la matriz de argumentos de comando que se usó para ejecutar la aplicación, y luego queremos que construya un objeto con todas las etiquetas, y sería bueno si también pudiéramos pasarle un objeto de configuración predeterminado, por lo que si Nada se anulará, tendremos algunos ajustes almacenados.

Cuando se trata de etiquetas, muchas aplicaciones también ofrecen opciones de acceso directo que son solo un carácter, por lo que, digamos que queremos establecer la profundidad de nuestra búsqueda, podríamos permitirle al usuario especificar algo como --profundidad = 2 o algo como -d = 2 que debería tener el mismo efecto.

Así que comencemos con las etiquetas largas (por ejemplo, '--depth = 2'), para empezar, escribamos la primera prueba:

describe ("Etiquetas", función () describe ("# parse ()", función () it ("debería analizar etiquetas formadas largas", función () 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 ("Hola Mundo"); ); ); );

Agregamos un método a nuestro conjunto de pruebas llamado analizar gramaticalmente y añadimos una prueba para las etiquetas formadas largas. Dentro de esta prueba, creé un comando de ejemplo y agregué dos aserciones para las dos propiedades que debería recoger.

Al ejecutar Mocha ahora, debería obtener un error, a saber, que etiquetas no tiene un analizar gramaticalmente función. Así que para solucionar este error vamos a añadir una analizar gramaticalmente Función al módulo de etiquetas. Una forma bastante típica de crear un módulo de nodo es así:

exportaciones = module.exports = ; exports.parse = función () 

El error decía que necesitábamos un analizar gramaticalmente método así que lo creamos, no agregamos ningún otro código interno porque aún no nos lo dijo. Al seguir el mínimo, está seguro de que no escribirá más de lo que debe y terminará con un código no probado..

Ahora ejecutemos Mocha nuevamente, esta vez deberíamos estar recibiendo un error que nos dice que no puede leer una propiedad llamada profundidad de una variable indefinida. Eso es porque actualmente nuestra analizar gramaticalmente La función no está devolviendo nada, así que agreguemos algo de código para que devuelva un objeto:

exports.parse = function () var options =  opciones de retorno; 

Nos estamos moviendo lentamente, si ejecutas Mocha de nuevo, no debería haber excepciones, solo un mensaje de error que dice que nuestro objeto vacío no tiene ninguna propiedad llamada profundidad.


Ahora podemos entrar en algún código real. Para que nuestra función analice la etiqueta y la agregue a nuestro objeto, necesitamos desplazarnos por la matriz de argumentos y eliminar los guiones dobles al comienzo de la clave.

exports.parse = function (args) var options =  for (var i en args) // Ciclo a través de args var arg = args [i]; // Verifique si la etiqueta es de larga duración if (arg.substr (0, 2) === "-") arg = arg.substr (2); // Compruebe si el signo es igual si (arg.indexOf ("=")! == -1) arg = arg.split ("="); clave var = arg.shift (); opciones [clave] = arg.join ("=");  opciones de retorno; 

Este código recorre la lista de argumentos, se asegura de que estamos tratando con una etiqueta de formato largo y luego la divide por el primer carácter igual para crear el par de clave y valor para el objeto de opciones.

Ahora, esto casi resuelve nuestro problema, pero si ejecutamos Mocha nuevamente, verá que ahora tenemos una clave para la profundidad, pero está configurada como una cadena en lugar de un número. Es más fácil trabajar con los números más adelante en nuestra aplicación, por lo que el siguiente fragmento de código que debemos agregar es convertir los valores en números siempre que sea posible. Esto se puede lograr con algunos RegEx y parseInt funciona de la siguiente manera:

 if (arg.indexOf ("=")! == -1) arg = arg.split ("="); clave var = arg.shift (); var value = arg.join ("="); if (/^[0-9◆+$/.test(value)) value = parseInt (value, 10);  opciones [clave] = valor; 

Al ejecutar Mocha ahora, debes obtener un pase con una prueba. Podría decirse que la conversión de números debería estar en su propia prueba, o al menos mencionada en la declaración de pruebas para que, por error, no elimine la afirmación de conversión de números; así que simplemente agregue "sumar y convertir números" a la eso declaración para esta prueba o separarla en una nueva eso bloquear. Realmente depende de si considera este "comportamiento predeterminado obvio" o una característica separada.


Ahora, como he estado tratando de enfatizar a lo largo de todo este artículo, cuando ves una especificación de aprobación, es hora de escribir más pruebas. Lo siguiente que quería agregar era la matriz predeterminada, por lo que dentro de etiquetasEspecificaciones archivo vamos a agregar lo siguiente eso Bloque justo después del anterior:

 it ("debe analizar etiquetas formadas largas y convertir números", function () var args = ["--depth = 4", "--hello = world"]; var results = tags.parse (args); expect ( resultados) .to.have.a.property ("profundidad", 4); espera (resultados) .to.have.a.property ("hola", "mundo");); it ("debería recurrir a los valores predeterminados", function () var args = ["--depth = 4", "--hello = world"]; var defaults = depth: 2, foo: "bar"; var results = tags.parse (args, por defecto); var expected = depth: 4, foo: "bar", hello: "world"; expect (results) .to.deep.equal (expected););

Aquí estamos usando una nueva prueba, la profunda igual que es buena para hacer coincidir dos objetos para valores iguales. Alternativamente, puede utilizar el eql Prueba que es un atajo pero creo que esto es más claro. Esta prueba pasa dos argumentos como la cadena de comando y pasa dos valores predeterminados con una solapada, solo para que podamos obtener una buena distribución en los casos de prueba.

Al ejecutar Mocha ahora, debería obtener una especie de diferencia, que contiene las diferencias entre lo que se espera y lo que realmente obtuvo..


Ahora continuemos de nuevo a la tags.js módulo, y agreguemos esta funcionalidad. Es una solución bastante simple de agregar, solo tenemos que aceptar el segundo parámetro, y cuando se establece en un objeto, podemos reemplazar el objeto vacío estándar al comienzo con este objeto:

exports.parse = función (argumentos, valores predeterminados) var options = ; if (typeof defaults === "object" &&! (defaults instanceof Array)) options = defaults

Esto nos llevará de vuelta a un estado verde. Lo siguiente que quiero agregar es la capacidad de especificar una etiqueta sin un valor y dejar que funcione como un booleano. Por ejemplo, si acabamos de configurar --buscar contenido o algo así, simplemente lo agregará a nuestra matriz de opciones con un valor de cierto.

La prueba para esto se vería como la siguiente:

 it ("debería aceptar etiquetas sin valores como un valor bool", function () var args = ["--searchContents"]; var results = tags.parse (args); expect (results) .to.have.a.property ("searchContents", true););

Ejecutar esto nos dará el siguiente error como antes:


Dentro de la para bucle, cuando obtuvimos una coincidencia para una etiqueta de formato largo, verificamos si contenía un signo igual; Podemos escribir rápidamente el código para esta prueba agregando un más cláusula a eso Si declaración y acaba de establecer el valor de cierto:

 if (arg.indexOf ("=")! == -1) arg = arg.split ("="); clave var = arg.shift (); var value = arg.join ("="); if (/^[0-9◆+$/.test(value)) value = parseInt (value, 10);  opciones [clave] = valor;  else opciones [arg] = verdadero; 

Lo siguiente que quiero agregar son las sustituciones de las etiquetas de mano corta. Este será el tercer parámetro para el analizar gramaticalmente Funciona y será básicamente un objeto con letras y sus correspondientes reemplazos. Aquí está la especificación para esta adición:

 it ("debe aceptar etiquetas formadas cortas", function () var args = ["-sd = 4", "-h"]; var reemplaza = s: "searchContents", d: "depth", h: " hola "; var results = tags.parse (args, , reemplazos); var expected = searchContents: true, depth: 4, hello: true; espera (resultados) .to.deep.equal (esperado); );

El problema con las etiquetas de taquigrafía es que se pueden combinar en una fila. Lo que quiero decir con esto es a diferencia de las etiquetas con formato largo donde cada una está separada, con etiquetas de mano cortas, ya que cada una es solo una letra larga, puedes llamar a tres diferentes escribiendo -vgh. Esto hace que el análisis sea un poco más difícil porque aún necesitamos permitir que el operador igual agregue un valor a la última etiqueta mencionada, mientras que al mismo tiempo necesita registrar las otras etiquetas. Pero no se preocupe, no es nada que no pueda resolverse con suficientes cambios de posición..

Aquí está la solución completa, desde el principio de la analizar gramaticalmente función:

exports.parse = función (argumentos, valores predeterminados, reemplazos) var options = ; if (typeof defaults === "object" &&! (defaults instanceof Array)) options = defaults if (typeof reemplazos === "object" &&! (defaults instanceof Array)) for (var i en 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 (""); clave var = arg.pop (); if (replacements.hasOwnProperty (clave)) clave = reemplazos [clave];  args.push ("-" + key + "=" + value);  else arg = arg.split ("");  arg.forEach (función (clave) si (reemplaza.hasOwnProperty (clave)) clave = reemplazos [clave]; args.push ("-" + clave);); 

Es una gran cantidad de código (en comparación), pero todo lo que realmente estamos haciendo es dividir el argumento por un signo igual, y luego dividir esa clave en las letras individuales. Así por ejemplo si pasamos -gj = asd dividiríamos el asd en una variable llamada valor, y luego dividiríamos el gj sección en caracteres individuales. El último personaje (j en nuestro ejemplo) se convertirá en la clave para el valor (asd) mientras que cualquier otra letra anterior, solo se agregará como etiquetas booleanas normales. No quería simplemente procesar estas etiquetas ahora, en caso de que cambiáramos la implementación más adelante. Entonces, lo que estamos haciendo es simplemente convertir estas etiquetas de mano corta en la versión de formato largo y luego dejar que nuestro script lo maneje más tarde..

Correr Mocha nuevamente nos llevará de regreso a nuestros ilustres resultados verdes de cuatro pruebas aprobadas para este módulo.

Ahora hay algunas cosas más que podemos agregar a este módulo de etiquetas para acercarlo más al paquete npm, como la capacidad de almacenar también argumentos de texto sin formato para cosas como comandos o la capacidad de recopilar todo el texto al final, para una propiedad de consulta. Pero este artículo ya se está haciendo largo y me gustaría continuar con la implementación de la funcionalidad de búsqueda..


El módulo de búsqueda

Acabamos de crear un módulo paso a paso siguiendo un enfoque TDD y espero que tengas la idea y el sentido de cómo escribir de esta manera. Pero por el bien de mantener este artículo en movimiento, para el resto del artículo, aceleraré el proceso de prueba agrupando las cosas y solo mostrándole las versiones finales de las pruebas. Es más una guía para diferentes situaciones que pueden surgir y cómo escribir exámenes para ellas..

Así que solo crea un archivo llamado search.js dentro de la carpeta lib y una searchSpec.js archivo dentro de la carpeta de prueba.

A continuación, abra el archivo de especificaciones y configuremos nuestra primera prueba, que puede ser para que la función obtenga una lista de archivos basada en una profundidad parámetro, este también es un gran ejemplo para las pruebas que requieren un poco de configuración externa para que funcionen. Cuando trabaje con datos externos similares a objetos o en nuestros archivos de casos, deseará tener una configuración predefinida que sepa que funcionará con sus pruebas, pero tampoco desea agregar información falsa a su sistema.

Básicamente, existen dos opciones para resolver este problema: puede simularse con los datos, como mencioné anteriormente si está tratando con los comandos propios del idioma para cargar datos, no necesariamente tiene que probarlos. En casos como ese, simplemente puede proporcionar los datos 'recuperados' y continuar con sus pruebas, algo así como lo que hicimos con la cadena de comandos en la biblioteca de etiquetas. Pero en este caso, estamos probando la funcionalidad recursiva que estamos agregando a las capacidades de lectura de archivos de idiomas, dependiendo de la profundidad especificada. En casos como estos, necesita escribir una prueba y, por lo tanto, necesitamos crear algunos archivos de demostración para probar la lectura del archivo. La alternativa es tal vez apalear la fs Las funciones solo se ejecutan pero no hacen nada, y luego podemos contar cuántas veces se ejecutó nuestra función falsa o algo así (mira a los espías), pero para nuestro ejemplo, solo voy a crear algunos archivos..

Mocha proporciona funciones que pueden ejecutarse antes y después de sus pruebas, para que pueda realizar este tipo de configuración externa y limpieza en torno a sus pruebas.

Para nuestro ejemplo, crearemos un par de archivos y carpetas de prueba a dos profundidades diferentes para que podamos probar esa funcionalidad:

var espera = requiere ("chai"). espera; var search = require ("… /lib/search.js"); var fs = require ("fs"); describe ("Buscar", función () describe ("# scan ()", función () antes (función () si (! 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 "););););

Estos serán llamados basados ​​en el describir Bloque en el que están, e incluso puede ejecutar código antes y después de cada eso bloque usando antes de cada o después de cada en lugar. Las funciones en sí mismas solo usan comandos de nodo estándar para crear y eliminar los archivos respectivamente. A continuación tenemos que escribir la prueba real. Esto debería ir justo al lado de la después función, todavía dentro de la describir bloquear:

 it ("debería recuperar los archivos de un directorio", función (hecho) search.scan (". test_files", 0, función (err, flist) esperar (flist) .to.deep.equal ([".test_files / a "," .test_files / b "," .test_files / dir / c "," .test_files / dir2 / d "]); done ();););

Este es nuestro primer ejemplo de prueba de una función asíncrona, pero como puede ver, es tan simple como antes; todo lo que necesitamos hacer es usar el hecho función que Mocha proporciona en el eso Declaraciones para avisar cuando hayamos terminado con esta prueba..

Mocha detectará automáticamente si especificaste la hecho variable en la devolución de llamada y esperará a que se le llame, lo que le permitirá probar el código asíncrono con mucha facilidad. Además, vale la pena mencionar que este patrón está disponible en Mocha, por ejemplo, puede usarlo en el antes de o después Funciona si necesitas configurar algo de forma asíncrona.

A continuación, me gustaría escribir una prueba que asegure que el parámetro de profundidad funcione si se establece:

 it ("debería detenerse a una profundidad especificada", función (realizada) search.scan (". test_files", 1, función (err, flist) expect (flist) .to.deep.equal ([".test_files / a "," .test_files / b ",]); done ();););

Nada diferente aquí, solo otra prueba simple. Al ejecutar esto en Mocha, obtendrás un error de que la búsqueda no tiene ningún método, básicamente porque no hemos escrito nada en ella. Entonces vamos a agregar un esquema con la función:

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

Si ahora ejecuta Mocha de nuevo, se detendrá la espera de que regrese esta función asíncrona, pero como no hemos llamado a la devolución de llamada en absoluto, la prueba simplemente expirará. De forma predeterminada, debería expirar después de unos dos segundos, pero puede ajustar esto usando this.timeout (milisegundos) Dentro de una descripción o bloque, para ajustar sus tiempos de espera respectivamente.

Se supone que esta función de exploración toma un camino y profundidad, y devuelve una lista de todos los archivos que encuentra. En realidad, esto es un poco complicado cuando empiezas a pensar en cómo estamos recurriendo esencialmente dos funciones diferentes en una sola función. Necesitamos revisar a través de las diferentes carpetas y luego esas carpetas necesitan escanearse y decidir ir más lejos.

Hacer esto de forma sincrónica está bien porque puede pasar uno por uno, completando lentamente un nivel o una ruta a la vez. Cuando se trata de una versión asíncrona, es un poco más complicado porque no se puede simplemente hacer un para cada bucle o algo, porque no se detendrá entre las carpetas, todas se ejecutarán esencialmente al mismo tiempo, cada una devolviendo valores diferentes y se sobreescribirían mutuamente.

Por lo tanto, para que funcione, debe crear una especie de pila en la que pueda procesar de forma asíncrona una a la vez (o todas a la vez si usa una cola) y luego mantener el orden de esa manera. Es un algoritmo muy específico, así que solo guardo un fragmento de Christopher Jeffrey que puedes encontrar en Stack Overflow. No se aplica solo a la carga de archivos, sino que lo he utilizado en varias aplicaciones, básicamente en cualquier cosa en la que necesite procesar una matriz de objetos de uno en uno utilizando funciones asíncronas..

Necesitamos modificarlo un poco, porque nos gustaría tener una opción de profundidad, cómo funciona la opción de profundidad si establece cuántos niveles de carpetas desea verificar, o cero para que se repita indefinidamente.

Aquí está la función completada usando el fragmento de código:

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

Mocha ahora debería pasar ambas pruebas. La última función que necesitamos implementar es la que aceptará un conjunto de rutas y una palabra clave de búsqueda y devolverá todas las coincidencias. Aquí está la prueba para ello:

 describe ("# match ()", function () it ("debería encontrar y devolver coincidencias según una