Last Updated: 2019-08-31

¿Por qué diseñar pruebas orientadas por el comportamiento?

BDD (Behavior Driven Development) es una estrategia de desarrollo en la que se encamina todo el proceso de construcción de software en función de su comportamiento observable. Esto permite establecer metas dentro del equipo en un lenguaje más tangible y cercano a las personas: la experiencia de usuario.

Su existencia se inspira en una estrategia con propósitos similares, denominada TDD (Test Driven Development), en la que el proceso de construcción de software gira en torno al cumplimiento de las pruebas que se definen para el programa. En ambos casos, establecer las pruebas antes del desarrollo permite tener claridad sobre los criterios de evaluación a los que será expuesto el producto y facilita la planeación de etapas del proyecto.

¿Qué es Cucumber?

Cucumber es la herramienta ideal para trabajar con un esquema BDD, pues permite ejecutar pruebas de aceptación automatizadas para probar las funcionalidades de un sistema haciendo uso de un lenguaje cercano al natural. Inicialmente Cucumber era una herramienta exclusiva para el lenguaje Ruby, pero actualmente cuenta con frameworks en varios de los lenguajes de programación más populares.

¿Qué construirá?

Al final de este tutorial usted tendrá:

¿Qué aprenderá?

Al desarrollar este tutorial aprenderá:

¿Qué necesita?

Crear la estructura básica de un proyecto nuevo

Dado que se utilizará Cucumber sobre Node.js, lo primero que debe hacer es crear un proyecto vacío haciendo uso de la herramienta npm. Para esto, abra una terminal y ubíquese en el directorio donde desea crear su proyecto. Allí, ejecute el comando npm init -y para crear un proyecto vacío.

Ahora, usando el comando mkdir desde el terminal, o utilizando la interfaz del explorador de archivos, cree un directorio llamado features, donde ubicará sus archivos con los escenarios a ejecutar, y un subdirectorio features/step_definitions. Dentro de este subdirectorio cree un archivo llamado stepdefs.js, donde ubicará las instrucciones de su prueba. Para utilizar efectivamente Cucumber, en el directorio raíz cree un archivo llamado cucumber.js con el siguiente contenido:

module.exports = {

default: `--format-options '{"snippetInterface": "synchronous"}'`

}

También debe modificar el contenido de su archivo package.json para poder correr las pruebas con el comando de Cucumber. Para esto, asegúrese de que el valor del atributo "tests" en el atributo "scripts" tenga el valor "cucumber-js".

Instalar Cucumber

Ahora que tiene la estructura básica del proyecto, debe instalar la dependencia de Cucumber en su proyecto. Para esto, en una terminal, ubíquese en el directorio raíz de su proyecto y ejecute el siguiente comando:

npm install cucumber --save-dev

Ahora puede acceder a las herramientas de la librería Cucumber en su proyecto.

Comprender la estructura de un proyecto de Cucumber

Como se mencionó en la sección introductoria, Cucumber es una herramienta originalmente de Ruby que actualmente cuenta con soporte para muchos más lenguajes de programación. En el caso de este tutorial se está haciendo uso de JavaScript, en proyectos de Node.js. Por este motivo, hay ciertos pasos que van a variar según el lenguaje de programación que se esté utilizando. No obstante, todo proyecto de Cucumber cuenta con una estructura básica que parte de un directorio contenedor del proyecto, cuya organización depende de la herramienta del lenguaje que se utilice (como npm, Bundler, Maven, Gradle, entre otros), y dentro de este directorio, el proyecto se comprende de un directorio features, el cual contendrá todos los archivos .feature con los escenarios a ejecutar, y un subdirectorio step_definitions, el cual contiene un archivo escrito en el lenguaje de programación elegido, donde se utilizan los métodos Given, When y Then de la librería de Cucumber para definir los steps que pueden utilizarse en los escenarios. Además, en varios lenguajes se necesitará modificar un archivo a nivel de proyecto para elegir Cucumber como el motor de pruebas.

Para el caso de este tutorial se harán pruebas para verificar la correcta funcionalidad de una librería que permite utilizar un cliente para bases de datos MongoDB desde Node.js. En muchas ocasiones se da por sentado que la conexión con la base de datos es trivial, pero estas librerías también son desarrolladas por equipos que suelen programarlas en el mismo lenguaje de programación, o en ocasiones en uno de más bajo nivel (Como es el caso de C). Las pruebas de este estilo son útiles, entonces, para dichos equipos.

Crear un clúster para bases de datos Mongo en Atlas

Para obtener una base de datos de MongoDB gratuita y de forma rápida, en este tutorial hará uso de una solución en la nube que ofrece la base de datos como servicio, llamada Atlas. En primer lugar, debe crear una cuenta para obtener la prueba gratuita. Ingrese al siguiente enlace, que lo llevará al formulario de registro: https://www.mongodb.com/try. El contenido de esta página se muestra como en la siguiente imagen:

Información sobre Atlas junto a un formulario para iniciar en Atlas con los campos \

Imagen 1. Sección de MongoDB Atlas, con información y el formulario de registro.

Llene el formulario con la información requerida, seleccione la casilla de términos y condiciones, y presione el botón "Get started free". Esto le mostrará una pantalla de carga, y luego de ello podrá ver una pantalla con las opciones de planes para utilizar el servicio de DbaaS de Mongo, como la de la siguiente imagen:

Al ingresar se debe crear un cluster de tipo shared (gratis), dedicated (0.08 USD/hora) o dedicated multi-region (0.13 USD/hora)

Imagen 2. Opciones de cluster en Atlas.

Seleccione la opción de la izquierda "Shared clusters" para hacer uso del servicio de forma gratuita y proceder a crear un cluster con MongoDB el cual satisface las necesidades de este tutorial. Al hacer clic en el botón "Create a cluster" de dicha opción, se le mostrará una pantalla para configurar aspectos del despliegue del cluster como el proveedor y la región, los cuales puede dejar en sus valores por defecto y proceder haciendo clic en el botón "Create cluster".

La creación de su cluster puede tomar hasta 3 minutos, mientras se realiza el aprovisionamiento de los recursos. Una vez este proceso termine, podrá ver la pantalla de Clusters con su nuevo Cluster0 y alguna información relacionada a este, como en la siguiente imagen:

Sección de la pagina que muestra información sobre el cluster, opciones para conectarse, métricas y gráficas de monitoreo

Imagen 3. Información del nuevo clúster creado.

Ahora debe crear un usuario para su base de datos, y asegurar que la base de datos sea accesible desde la máquina en que ejecutará la prueba. En cuanto a la creación de usuario, en el menú de la parte lateral izquierda podrá ver una sección llamada "Security" con la pestaña "Database Access". Ingrese a esta pestaña y podrá ver un botón con el texto "Add New Database User". Haga clic en dicho botón y podrá ver un diálogo modal con el formulario para crear un usuario, como el de la siguiente imagen:

Formulario para crear un usuario eligiendo método de autenticación, credenciales de autenticación y privilegios del usuario según su rol.

Imagen 4. Formulario de creación de un nuevo usuario de la base de datos.

Luego de completar este formulario para crear un usuario con rol admin, debe configurar los permisos para acceder desde internet. En la misma barra lateral izquierda mencionada anteriormente, podrá ver una pestaña con el nombre "Network Access". Haga clic en ella, y al ingresar en la pantalla correspondiente se encontrará con un botón en el centro de la pantalla con el texto "Add IP Address". Haga clic en este botón y podrá ver un diálogo modal con el formulario para agregar direcciones IP, como el de la siguiente imagen:

Formulario para ingresar un patrón o un listado de direcciones IPv4 para agregar a una lista blanca con la opción de permitir acceso desde cualquier lugar y desde la dirección IP cliente

Imagen 5. Formulario para agregar direcciones IP.

Para asegurarse de que no tendrá problemas de conexión a su base de datos haga clic en el botón "Allow access from anywhere" y luego en el botón "Confirm". Ahora tiene todo lo que necesita para acceder a la base de datos.

Instalar el cliente de Mongo en su proyecto

Para interactuar con su base de datos hará uso de una librería de Node.js que actúa como driver, llamada MongoClient. En primer lugar, debe incluir esta dependencia en su proyecto. Para esto, abra una terminal en el directorio raíz del proyecto, donde está el archivo package.json y ejecute el siguiente comando

npm install mongodb

Ahora podrá acceder a las funcionalidades de la librería de mongodb.

Desde la página de sus clústeres en Atlas, podrá ver su clúster con un botón "Connect". Haga clic en dicho botón para ver un diálogo modal que le indica varias formas de conectarse a su base de datos. Seleccione la segunda opción "Connect your application", y podrá ver un diálogo con instrucciones para varios lenguajes de programación y un botón dropdown que le permite elegir otro lenguaje; seleccione la opción Node.js para ver las instrucciones de configuración. Deberá ver una pantalla como la siguiente:

Diálogo indicando instrucciones para conectarse según un driver y una versión (en este caso node.js versión 3.6+)

Imagen 6. Diálogo con las instrucciones para conectar al clúster.

Por lo pronto, sólo asegúrese de guardar la cadena de texto que se le presenta en el punto 2 para establecer la colección, ya que la necesitará en el siguiente paso.

Plantear los escenarios en lenguaje natural

Ahora que cuenta con las dependencias necesarias para ejecutar pruebas de Cucumber sobre el cliente de Mongo, podemos pensar en las operaciones que un miembro del equipo de desarrollo de esta librería desearía validar. En principio, el número de operaciones a probar es bastante amplio si se tienen en cuenta todos los escenarios a los que se puede enfrentar el sistema en cuestión. Para el caso de este tutorial, se harán pruebas sobre el cliente de Mongo relacionadas a las operaciones básicas sobre una colección, donde se validen los detalles del estado de la colección a medida que se ejecutan operaciones básicas.

Así, se probarán los escenarios mencionados a continuación para garantizar que una aplicación pueda hacer uso de la librería mongodb.

  1. Escenario de conexión básica a la base de datos: la librería debería permitir que una aplicación se conecte al cluster y cree una nueva base de datos y una nueva colección vacía dentro de ella.
  2. Escenario de creación y consultas de elementos sobre la colección: la librería debería permitir insertar elementos con estructuras básicas a la colección y luego probar las operaciones countDocuments y find.
  3. Escenario de eliminación de elementos según un criterio: la librería debería permitir eliminar una serie de elementos que cumplan con cierto criterio.

Conectar el cliente a su base de datos

En primer lugar, debe asegurarse de incluir el cliente de MongoDB en su proyecto y en sus steps para poder probar las funcionalidades utilizando Cucumber. Por este motivo, es necesario que incluya la dependencia de mongodb en el archivo features/step_definitions/step_defs.js e inicialice un cliente que esté conectado al clúster que creó previamente y en particular a la base de datos de la tienda. Por lo pronto, su archivo debe tener el siguiente contenido:

const assert = require('assert');
const { Given, When, Then } = require('cucumber');
const MongoClient = require('mongodb').MongoClient;

const uri = "mongodb+srv://<username>:<password>@cluster.info.mongodb.net/<dbname>?retryWrites=true&w=majority"; //Change for your cluster id
const client = new MongoClient(uri, { useNewUrlParser: true , useUnifiedTopology: true });

Como puede ver, las tres primeras constantes contienen las dependencias que son necesarias para construir las pruebas (Verificación de aserciones, instrucciones de Gherkin en Cucumber y cliente de MongoDB). La constante uri debe contener la cadena de texto que permite establecer una conexión con una base de datos de nombre <dbname> en el clúster que usted ha creado, y le autentica con el usuario <usr> y su contraseña <password>. Reemplace el valor de esta constante por la cadena de texto que le brinda Atlas en la opción "Connect" de su clúster. Finalmente, la constante client instancia el objeto que le permitirá interactuar con las colecciones de su base de datos por medio de los métodos expuestos en la librería.

Definir los steps necesarios

Recuerde que los steps de Gherkin que utiliza en los archivos .feature serán una abstracción de los procedimientos que usted defina en su archivo step_defs.js. Por este motivo, un step puede realizar cualquier operación que soporte el lenguaje siempre y cuando sea definida con las estructuras básicas de Gherkin (Given, When, Then, And, But). Los steps en este tutorial estarán comprendidos por llamados a métodos de la librería de mongodb, los cuales deben permitir interactuar con la base de datos y obtener información de su estado para almacenarla en variables locales de la prueba.

En primer lugar, su archivo establece una conexión común del cliente con su cluster de Atlas a lo largo de la prueba para poder compartir el estado entre instrucciones de un escenario. Luego de esto, simplemente puede proceder a definir las instrucciones con los métodos Given, When y Then de la librería de Cucumber. Se sugiere que los métodos Given correspondan a pre condiciones o funciones que establecen un ambiente posterior para la prueba, los métodos When correspondan a interacciones que pueda tener un usuario con la aplicación y que cambien el estado, y que los métodos Then correspondan a aserciones o poscondiciones donde se evalúa el estado según un comportamiento esperado.

Asegúrese que el resto del contenido de su archivo step_defs.js sea el siguiente:

const assert = require('assert');
const { Given, When, Then } = require('cucumber');
const MongoClient = require('mongodb').MongoClient;

// Replace the uri string with your MongoDB deployment's connection string.
const uri = "Su uri de Atlas"

const client = new MongoClient(uri, { useNewUrlParser: true , useUnifiedTopology: true })

Given('I connect with db called {string}', async function(dbName){
  await client.connect();
  assert.strictEqual(client.isConnected(), true)
  this.db = client.db(dbName)
})

Given('I instance a collection named {string}', async function(col){
  assert.strictEqual(client.isConnected(), true)
  await this.db.dropCollection(col).catch(e=>{}) //Try to clean it if it exists
  this.collection = this.db.collection(col)
})

Given('I add a basic element with the name {string}', async function (string){
  assert.strictEqual(client.isConnected(), true)
  await this.collection.insertOne({'name':string})
})

When('I count all the collection elements', async function (){
  assert.strictEqual(client.isConnected(), true)
  this.numElements = await this.collection.countDocuments()
})

When('I get the elements with the name {string}', async function(string){
  assert.strictEqual(client.isConnected(), true)
  this.elements = await this.collection.find({'name':string})
  this.numElements = await this.elements.count()
})


When('I delete elements with the name {string}', async function(string){
  assert.strictEqual(client.isConnected(), true)
  await this.collection.deleteMany({'name':string})
})

Then('I should see {int} document(s)', async function(num){
  assert.strictEqual(client.isConnected(), true)
  assert.strictEqual(this.numElements,num)
})

Lea detenidamente el contenido anterior. Podrá ver que los métodos de Cucumber se utilizan para encapsular las instrucciones directas a la librería de mongodb, definiendo a su vez un patrón o una cadena de texto, que incluso recibe parámetros, para llamar dichas instrucciones desde sus archivos .feature. Note también que estas definiciones soportan el uso de funciones asíncronas de JavaScript, lo cual permite interactuar con cualquiera de las operaciones de la librería. Así mismo, podrá ver que a lo largo de la definición de steps se utilizan variables como this.collection, las cuales son accesibles desde cualquier step sin necesidad de haberlas definido explícitamente a nivel global. Estas variables permiten que sus escenarios compartan un estado, de forma que las acciones que ocurran en uno de ellos se vean reflejadas en los pasos posteriores. Finalmente, note que el step de conexión con la colección tiene una instrucción que elimina la colección con el nombre pasado por parámetro en caso de que exista. Esto podría ser manejado también con los hooks de Cucumber, pero dado que se necesita el parámetro del nombre de la colección, esto se dejó como parte de dicho step. Puede aprender más sobre los hooks de Cucumber en el siguiente enlace: https://cucumber.io/docs/cucumber/api/#hooks.

Escribir el escenario en un archivo .feature

Anteriormente, usted configuró su proyecto para ejecutar las pruebas del comando npm test haciendo uso de Cucumber. Este script ejecuta todos los escenarios que encuentre en los archivos con extensión .feature en el directorio features de su proyecto. Para cada archivo usted puede definir varios escenarios, y en este tutorial se crearán las pruebas de dicha forma.

Cree un archivo llamado db.feature en el directorio features de su proyecto, y asegúrese de incluir el siguiente contenido:

Feature: Basic database operations
  We want to know if the driver works

  Scenario: Brand new empty collection (1)
    Given I connect with db called "nonexistent"
    And I instance a collection named "newcol"
    When I count all the collection elements
    Then I should see 0 documents

  Scenario: Write and get elements from a collection (2)
    Given I connect with db called "basedb"
    And I instance a collection named "basecol"
    And I add a basic element with the name "elemento1"
    And I add a basic element with the name "elemento2"
    When I count all the collection elements
    Then I should see 2 documents
    When I get the elements with the name "elemento1"
    Then I should see 1 document

  Scenario: Write, delete and get elements from a collection (3)
    Given I connect with db called "basedb"
    And I instance a collection named "basecol"
    And I add a basic element with the name "elemento1"
    And I add a basic element with the name "elemento2"
    When I count all the collection elements
    Then I should see 2 documents

    When I delete elements with the name "elemento1"
    And I count all the collection elements
    Then I should see 1 document
    
    Given I add a basic element with the name "elemento2"
    When I count all the collection elements
    Then I should see 2 documents
    
    When I delete elements with the name "elemento2"
    And I count all the collection elements
    Then I should see 0 documents

Tómese un tiempo ahora para leer y entender las instrucciones que se incluyeron en el archivo que acaba de crear. Note, en primer lugar, la facilidad con la que se puede entender las instrucciones allí descritas y la cercanía de la sintaxis de Gherkin en sus steps con el lenguaje natural. Esta es una de las mayores ventajas de utilizar Cucumber-Gherkin para sus pruebas, puesto que ahora cualquier persona puede desarrollar nuevos escenarios utilizando los steps que usted definió sin necesidad de conocer sobre lenguajes de programación. Note también, que existen los 3 escenarios definidos anteriormente dentro del mismo archivo, y que estos se ejecutarán de forma secuencial. Finalmente, note también que es posible utilizar instrucciones que comienzan por And en los casos en que dos o más operaciones seguidas correspondan al mismo método Given, When o Then.

Ahora que ha indicado los escenarios que desea probar y las instrucciones que se deben ejecutar como equivalentes a las instrucciones de Gherkin, ya puede ejecutar dichas pruebas en su proyecto utilizando Cucumber. Como podrá recordar, en un paso anterior se modificó el archivo package.json para que el script test ejecute Cucumber. De esta forma, la ejecución de las pruebas es tan sencilla como correr el siguiente comando

npm test

Ejecute el comando anterior y podrá ver que se ejecutan pruebas utilizando los comandos de Cucumber.

Al ejecutar este comando, lo primero que verá es una retroalimentación en la consola que contiene dos líneas indicando la ruta absoluta de su proyecto y de cucumber-js. Al final de la ejecución podrá ver tres líneas que indican el número de escenarios ejecutados y pasados, el número de steps ejecutados y pasados y el tiempo de ejecución de las pruebas. En este tutorial, es posible que se generen varias líneas de registros hechos por la librería de mongodb. Su consola debería verse como la de la siguiente imagen:

Terminal corriendo el comando npm test. Se ven dos líneas indicando la ruta del proyecto y el motor de pruebas \

Imagen 7. Registros de la prueba en consola.

En caso de que su prueba presente errores, estos se mostrarán en medio de las líneas iniciales y finales de su retroalimentación por consola, y al final de la ejecución verá una línea con el texto npm ERR! Test failed. See above for more details.

Como habrá podido notar, Cucumber no ofrece mayor retroalimentación sobre el contenido mismo de la prueba, precisamente por lo que los steps son altamente personalizables al punto que es difícil establecer la información que merece ser registrada a nivel general de cualquier prueba. En caso de que usted desee conocer mayor detalle de la ejecución de la prueba, puede optar por utilizar registros ad-hoc como parte de los steps que define, haciendo uso de la instrucción console.log() o alguna otra herramienta de logging.

¡Felicidades!

Al finalizar este tutorial, usted pudo familiarizarse con el proceso requerido para realizar pruebas en un proyecto de Node.js haciendo uso de la librería Cucumber y definiendo escenarios en notación Gherkin.

Ahora usted podrá hacer uso de dichos frameworks para definir escenarios de forma no técnica para validar el funcionamiento de sus aplicaciones con la misma estrategia de este tutorial.

Créditos

Versión 1.0 - Mayo 30, 2020

Juan Sebastián Espitia Acero

Autor

Norma Rocio Héndez Puerto

Revisora

Mario Linares Vásquez

Revisor