BDD automated-testing con Gherkin y Jest en Node.js 🦄
Tags: bdd, node, gherkin, jest
Published: 2019-12-08 02:30:24

---
title: BDD automated-testing con Gherkin y Jest en Node.js 🦄
published: true
description: Crearemos un test automatizado 🤖 en Node.js para crear una estructura de datos de Lista enlazada, apoyándonos en la sintaxis Gherkin y con la automatización de Jest. 🔥 🦄
tags: #BDD, #node, #gherkin, #jest
cover_image: https://thepracticaldev.s3.amazonaws.com/i/ylpyv4ijxj3aahgf2fsi.jpg
---

Creo que los test en una aplicación son la mejor documentación que podemos crear. No solo son pruebas, también pueden narrar el comportamiento de la aplicación y sus restricciones.

Además, es un buen punto donde las necesidades de negocio y el QA se pueden acercar al nivel de desarrollo.

Pero gestionar el testing de una aplicación no es tarea fácil, porque las necesidades se transforman en el tiempo y se nos requiere mantener toda la estructura de tests que tenemos desarrollada. Por si fuera poco, también tenemos que mantener la documentación, arquitectura de la aplicación, etc.

Es por eso que la automatización se vuelve un enclave vital para mantener nuestro proyecto lo más ágil (sí, _agile_ en Español) posible.

<figure>
![BDD TDD](https://thepracticaldev.s3.amazonaws.com/i/vvnln8o2dkbanq55f9hp.jpg)
<figcaption>[Image Source](https://blog.testlodge.com/tdd-vs-bdd)
</figcaption>
</figure>

Aquí es donde __vamos a entrar en juego__.


## __Caso en un entorno Agile__

> __Todo comienza por una necesidad.__

Vamos a suponer que formamos parte de un equipo de 3 desarrolladores emprendedores los cuales nos hemos repartido roles en el de desarrollo de software. Nosotros (usted lector y yo) nos vamos a encargar de las soluciones más abstractas del proyecto.

Uno de los compañeros quiere que desarrollemos la implementación de una estructura de datos de una __Lista Enlazada__. Él no se quiere encargar de dicha abstracción, por que está dedicado su tiempo a concreciones de más alto nivel para el negocio. Sin embargo, esta necesidad es importante porque un error en la implementación podría suponer un gran coste y un sin fin de problemas. Además pensamos utilizarla en varias partes del proyecto.

> Realmente él habría hecho el mismo proceso de Descubrimiento, Formulación, Automatización que vamos a narrar en este artículo pero con un lenguaje más de producto y no tanto de ciencias de la computación.

Para el que no lo sepa, una Lista Enlazada tiene la siguiente estructura. [Aquí podrás encontrar más información](https://www.geeksforgeeks.org/linked-list-set-1-introduction/).

<figure>
![Lista Enlazada](https://miro.medium.com/max/953/1*DnMUy7SEP31IRXq1zbMwWA.png)
<figcaption>Forma visual de una Lista Enlazada</figcaption>
</figure>

Quien se dedique a implementar algoritmos y estructuras de datos sabrá que es fácil _colarse_ por algún lado. Es importante sentarse previamente a definir cómo funcionará, aunque normalmente todos vamos corriendo al código 😌. En este caso, la Lista Enlazada ya está más que definida porque es muy popular y muy sencilla.

Practicamos un poco de BDD y nos sentamos con nuestro compañero emprendedor para desarrollar la historia de usuario.

![](https://cucumber.io/img/bdd-practices-diagram.png)

### __Descubrimiento: lo que podría hacer__

Nos pide que necesita la implementación de la Lista Enlazada.

>Como desarrollador de componentes de la aplicación, quiero crear una lista enlaza con la secuencia de teclas que pulsamos en el teclado para poder implementar la lógica de un historial de comandos.

<figure>
![Historial de comandos de Photoshop](https://helpx.adobe.com/content/dam/help/es/photoshop/using/undo-history/_jcr_content/main-pars/image_0/wa_17.png)
<figcaption>Sería algo parecido al historial de comandos de Photoshop</figcaption>
</figure>

Conversando con nuestro compañero, descubrimos que para lo que necesita ahora, con la siguiente serie de requerimientos podemos empezar:
>Requerimientos
>- Poder instanciar una lista
>- La lista estará vacía por defecto
>- La lista tendrá una longitud
>- La lista ha de tener un HEAD y un TAIL
>- Poder añadir elementos al final de la lista
>- Los elementos guardarán un puntero al siguiente elemento

### __Formulación: Qué debe de hacer__
Sabemos lo que nuestro _cliente_ quiere, ahora vamos a escribir los requerimientos en lenguaje [Gherkin](https://cucumber.io/docs/gherkin/) para sacar juguito a lo que vamos a ver después 🧃.

Gherkin es un Domain-specific language (DSL) / sintáxis que permite definir muy explícitamente el __comportamiento__ de una _Feature_ y los __criterios de aceptación__. Se puede utilizar para __todos los niveles de testing__ sin restricción, dado que el __dominio__ dará el contexto.


El objetivo de este artículo __no es explicar o entrar en detalle sobre Gherkin__, pero si tienes algun comentario de mejora o quieres que profundice en más detalle, házlo saber en los comentarios ✍.

Tendremos que hacer un ejercicio por __desarrollar los escenarios__ en base a la información que hemos extraído en el __proceso de Descubrimiento previo__.

```gherkin
// file 'lib/data-structures/features/singly-linked-list.feature'
Feature: Singly Linked List

  Scenario: Instancing a list
    Given no instance
    When instancing a new Singly Linked List
    Then initial head must be null
    And initial tail must be null
    And length must be 0

  Scenario: Pushing a value on a empty list
    Given an Empty Singly Linked List with 0 nodes
    When pushing 'First Node' as node value
    Then head value becomes 'First Node'
    And tail value becomes 'First Node'
    And the list length becomes 1

  Scenario: Pushing values on a list with a single node
    Given a Singly Linked List with a single node as 'First Node' as value
    When pushing the following values
    | NodeValue   |
    | Second Node |
    | Third Node  |
    Then list length gets incremented to 3
    And tail value becomes 'Third Node'
    But head value still equals to 'First Node'
```

Opcionalmente le pasamos dicho fichero al compañero para que nos compruebe que hemos entendido correctamente sus necesidades. Si hubiese sido un cliente sin habilidades técnicas __también sería capaz de entender este lenguaje__, y ese es unos de los principios y beneficios de Gherkin.

> Cuando uses Gherkin, describe el comportamiento requerido, no el cómo.

Escribir este tipo de textos no es tarea fácil al principio. No obstante es una disciplina que hay que entrenar y que aumentará la calidad de tus entregas y tu pensamiento como desarrollador. Existen buenas y malas prácticas.

> ##Sufre el dolor de la disciplina o sufre el dolor del arrepentimiento.



### __Automatización: Qué es lo que realmente hace__

Aquí viene lo sexy 😏. El principal coste que tiene trabajar con Gherkin es que __hay que mantener dos cosas__: la definición de la feature y los tests.

Pero amigo mio, vamos a hacer que mantener las dos cosas sea muy liviano, porque definiones y tests __estarán enlazados explícitamente__ a través de [jest](https://www.npmjs.com/package/jest) + [jest-cucumber](https://www.npmjs.com/package/jest-cucumber).

![Jest 💗 Cucumber](https://thepracticaldev.s3.amazonaws.com/i/cgk55ldqcl8iad5s23jj.png)

Instalemos ambos paquetes
```sh
npm i -D jest jest-cucumber
```

La conexión entre los tests y los ficheros de definición de `.feature` es __bidireccional__ y muy práctica.

Cuando hagamos un mínimo cambio en el fichero `singly-linked-list.feature`, el fichero `singly-linked-list.steps.js` fallará 🚨 para avisarnos de que hay adaptaciones por hacer. Sólo así lograremos lo que se denomina una __Documentacion viva__ real.

Hay que entender que si el fichero `singly-linked-list.feature` cambia, es porque han habido __cambios de negocio__. De haber habido cambios en el negocio, nunca estará de más que queden explícitos. Esto lo hacemos a través de dicho fichero. Esto permitirá que el software escale mucho más, sobretodo con la __incorporación de nuevas personas al equipo__.

>Si recién te incorporas a un proyecto, te darás cuenta que hacer TDD puede ser casi imposible. Con la ayuda semántica de los ficheros .feature a través de BDD este proceso se hace mucho más armonioso.

Por otra parte, si el título de alguno de los tests no es exacto a la definición, también fallará. Vamos a poner esto a prueba.

Vamos a crear el siguiente steps test __incompleto__ y vamos a cargar la `.feature` a través de la función `loadFeature()`:

```js
// file 'lib/data-structures/features/steps/singly-linked-list.steps.js'
const { loadFeature, defineFeature } = require('jest-cucumber');
const { SinglyLinkedList } = require('./SinglyLinkedList');

const feature = loadFeature('./SinglyLinkedList.feature', { loadRelativePath: true, errors: true });

defineFeature(feature, test => {
    test('Instancing a list', ({ given, when, then, and }) => {

        let list;

        given('no instance', () => {
            expect(list).toBeUndefined;
        });

        when('instancing a new Singly Linked List', () => {
            list = new SinglyLinkedList();
        });

        then('initial head must be null', () => {
            expect(list.head).toBe(null);
        });
        and('initial tail must be null', () => {
            expect(list.tail).toBe(null);
        });
        and('length must be 0', () => {
            expect(list.length).toBe(0);
        });
    });

    test('Pushing a value on a empty list', ({ given, when, then, and }) => {

        let list;

        given(/^an Empty Singly Linked List with (.*) nodes$/, (arg0) => {
            list = new SinglyLinkedList();

            expect(list.length).toBe(Number.parseInt(arg0));
        });

        when(/^pushing (.*) as node value$/, (arg0) => {
            list.push(arg0);
        });

        then(/^head value becomes (.*)$/, (arg0) => {
            expect(list.head.value).toBe(arg0);
        });
        and(/^tail value becomes (.*)$/, (arg0) => {
            expect(list.tail.value).toBe(arg0);
        });
        and(/^the list length becomes (.*)$/, (arg0) => {
            expect(list.length).toBe(Number.parseInt(arg0));
        });
    });
});
```

Ahora corremos Jest para que compruebe los test y obtendríamos el siguiente resultado:

```
$ npx jest
 PASS  19. Singled Linked List/SinglyLinkedList.steps.js  Singly Linked List
    √ Instancing a list (5ms)
    √ Pushing a value on a empty list (1ms)
    √ Pushing values on a list with a single node (1ms)

 FAIL  19. Singled Linked List/SinglyLinkedList-demo.steps.js
  Singly Linked List
    × encountered a declaration exception (9ms)

  ● Singly Linked List › encountered a declaration exception

Feature file has a scenario titled "Pushing values on a list with a single node", but no match found in step definitions. Try adding the following code:

test('Pushing values on a list with a single node', ({ given, when, then, and, but }) => {
  given('a Singly Linked List with a single node as \'First Node\' as value', () => {

   });

    when('pushing the following values', (table) => {

    });

    then(/^list length gets incremented to (.*)$/, (arg0) => {

    });

    and('tail value becomes \'Third Node\'', () => {

    });

    but('head value still equals to \'First Node\'', () => {

    });
});
```

Como has podido observar, falta el `Scenario: Pushing values on a list with a single node` y Jest nos está regalando un precioso copy-paste con el que podremos salir del apuro y ahorrar tiempo. Es una plantilla que se puede mejorar si así lo deseamos; vamos a desarrollar la implementación y hacer que los `Third Node` y `First Node` se pasen como argumentos para que el test sea más explícito. El fichero final quedará así:

```js
// file 'lib/data-structures/features/steps/singly-linked-list.steps.js'
    ...
    test('Pushing values on a list with a single node', ({ given, when, then, and, but }) => {

        let list;

        given(/^a Singly Linked List with a single node as '(.*)' as value$/, (arg0) => {
            list = new SinglyLinkedList();
            list.push(arg0);

            expect(list.length).toBe(1);
        });
      
        when('pushing the following values', (table) => {
            table.forEach((row) => {
                list.push(row.NodeValue);
            });
        });

        then(/^list length gets incremented to (.*)$/, (arg0) => {
            expect(list.length).toBe(Number.parseInt(arg0));
        });
        // Utilizo Regex para determinar qué valor será pasado a través de arg0
        and(/^tail value becomes '(.*)'$/, (arg0) => {
            expect(list.tail.value).toBe(arg0);
        });
        // Utilizo Regex para determinar qué valor será pasado a través de arg0
        but(/^head value still equals to '(.*)'$/, (arg0) => {
            expect(list.head.value).toBe(arg0);
        });
    });
});
```

Y ahora sí, los tests serán 💚.

##Bonus: Extensiones de VSCode
Voy a mencionar dos extensiones que pueden llevar esto a otro nivel de productividad

>[Jest](https://marketplace.visualstudio.com/items?itemName=Orta.vscode-jest)
VSCode estará corriendo los tests de fondo y permitirá ver en tiempo real el resultado de ellos, así como debuggear de manera individual.

![Jest demo](https://thepracticaldev.s3.amazonaws.com/i/48m795hy3osk409lkuaw.gif)

> [Cucumber (Gherkin) Full Support](https://marketplace.visualstudio.com/items?itemName=alexkrechik.cucumberautocomplete)
Syntax highligther, gherkin-to-test copy paste converter, autocompletion ... Una maravilla.

![Cucumber demo](https://thepracticaldev.s3.amazonaws.com/i/p7tpl2wj6ygellv0956s.gif)

¿Qué te ha parecido el tema tratado? ¿Te puedo ayudar en algo 🐱‍👓? Muchas gracias por dedicar tu tiempo a leer este artículo y nos vemos en el siguiente.