Hello Jasmine! Primeros pasos para hacer BDD/TDD con JavaScript

9
39196

Creación: 07-12-2012

Índice de contenidos

1.Introducción
2. Entorno
3.Jasmine “a pelo”
3.1.Instalación
3.2.Escribiendo nuestras especificaciones
3.3.Escribiendo los tests y las expectativas
3.4.Escribiendo el código de producción
4.Jasmine con Maven
4.1.Configurando el proyecto Maven
4.2.Haciendo BDD/TDD
4.3.Integración con el ciclo de construcción de Maven
5.Jasmine con Node.js
5.1.Configurando el proyecto Node.js
5.2.Ejecutando los test
5.3.
Ejecutando los tests automáticamente

6.Conclusiones
7. Sobre el autor

1. Introducción

Según los propios autores, Jasmine es un framework de desarrollo dirigido por comportamiento para código JavaScript. No depende de ninguna otra librería JavaScript. No requiere un DOM. Y tiene una sintaxis obvia y limpia para que puedas escribir tus tests fácilmente.

En resumen, podríamos decir que desde que los señores creadores del conocido PivotalTracker sacaron a la luz este framework de test, prácticamente se ha convertido en el estándar de facto para el desarrollo con JavaScript.

En este tutorial vamos ha ver como empezar a trabajar con Jasmine en distintas plataformas, tanto usándolo de forma independiente, como integrado con Maven para Java, o Node para JavaScript puro.

En este enlace os podéis descargar todo el código de los ejemplos.

2. Entorno

El tutorial está escrito usando el siguiente entorno:

  • Hardware: Portátil MacBook Pro 15′ (2.5 GHz Intel i7, 8GB 1333 Mhz DDR3, 256GB Solid State Drive).
  • AMD Radeon HD 6770M 1024 MB
  • Sistema Operativo: Mac OS X Lion 10.8.2
  • Jasmine 1.3.1

3.Jasmine “a pelo”

3.1. Instalación

Nuestra primera opción va a ser utilizar Jasmine “a pelo”o como dicen ellos “standalone”. Es decir un fichero HTML y nuestro JavaScript sin ningún otro artificio. Para ello nos descargamos la última versión de Jasmine en:
https://github.com/jasmine/jasmine/releases (en nuestro caso el zip de la 1.3.1)

Si descomprimimos el zip nos encontraremos con la siguiente estructura:

Estructura de directorios usando Jasmine 'a pelo'

Vemos como tenemos una carpeta lib con las librerías de Jasmine, un carpeta src con nuestro código de producción (tal cual lo bajamos un pequeño ejemplo de canción y reproductor), y en la carpeta spec nuestros ficheros donde escribir las especificaciones de nuestro código en base a tests de Jasmine. Y por último, pero no por ello menos importante, el fichero SpecRunner.html (lo podemos ver remarcado en azul); este fichero es un HTML normal y corriente (puede tener cualquier otro nombre si lo preferimos) que al
abrirlo con nuestro navegador se encargará de ejecutar los tests para verificar las especificaciones.

Echemos un vistazo a este fichero:

<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
<html>
<head>
  <title>Jasmine Spec Runner</title>

  <link rel="shortcut icon" type="image/png" href="lib/jasmine-1.3.1/jasmine_favicon.png">
  <link rel="stylesheet" type="text/css" href="lib/jasmine-1.3.1/jasmine.css">
  <script type="text/javascript" src="lib/jasmine-1.3.1/jasmine.js"></script>
  <script type="text/javascript" src="lib/jasmine-1.3.1/jasmine-html.js"></script>

  <!-- include source files here... -->
  <script type="text/javascript" src="src/Player.js"></script>
  <script type="text/javascript" src="src/Song.js"></script>

  <!-- include spec files here... -->
  <script type="text/javascript" src="spec/SpecHelper.js"></script>
  <script type="text/javascript" src="spec/PlayerSpec.js"></script>

  <script type="text/javascript">
    (function() {
      var jasmineEnv = jasmine.getEnv();
      jasmineEnv.updateInterval = 1000;

      var htmlReporter = new jasmine.HtmlReporter();

      jasmineEnv.addReporter(htmlReporter);

      jasmineEnv.specFilter = function(spec) {
        return htmlReporter.specFilter(spec);
      };

      var currentWindowOnload = window.onload;

      window.onload = function() {
        if (currentWindowOnload) {
          currentWindowOnload();
        }
        execJasmine();
      };

      function execJasmine() {
        jasmineEnv.execute();
      }

    })();
  </script>

</head>

<body>
</body>
</html>

Que podemos destacar:

  • Líneas 8 y 9: Las librerías de Jasmine.
  • Líneas 12 y 13: Referencias a los ficheros con el código de producción (el código de nuestra aplicación).
  • Líneas 16 y 17: Referencia a los test donde tentemos las especificaciones escritas con Jasmine.
  • Líneas de la 19 a la 46: El runner de Jasmine que se encarga de ejecutar los tests con las especificaciones.

Si abrimos este fichero con cualquier navegador veremos algo como esto:

Runner de HTML de Jasmine


3.2. Escribiendo nuestras especificaciones

En el punto anterior hemos visto como poner en marcha Jasmine, ahora vamos a ver como empezar a escribir nuestros propios tests.
Para ello vamos a modificar los ficheros actuales para añadir las especificaciones de lo que podría ser la típica lista de cosas pendientes. Esta lista permitirá añadir tareas, dar por terminadas tareas o borrar tareas.

Para editar los ficheros podemos usar el editor que más nos guste en mi caso voy a usar IntelliJ IDEA, pero otra opción más sencilla puede ser Sublime Text. Así empezaremos a escribir nuestro fichero TodoListSpec.js

describe("Una lista de tareas", function() {

    it("debe estar vacía cuando está recién creada", function() {

    });

    describe("Cuando se añade una tarea", function() {

        it("no debe estar vacía", function() {

        });

        it("debe poder contener más de una tarea", function() {

        });

        it("debe estar sin completar nada más añadirse a la lista de tareas", function() {

        });

        it("se debe poder completar", function() {

        });

        it("debe quedar vacía al borrar la última tarea", function() {

        });
    });
});

Por supuesto en un ciclo de TDD o BDD lo primero es escribir el test y luego escribir el código de producción que hace que pase el tests. Ya sabéis, recordar siempre el ciclo Rojo
(falla) –> Verde (funciona) –> Refactor (mejora estructura del código sin cambiar funcionalidad). Así que lo que hemos hecho no es demasiado ortodoxo ya que deberíamos
haber hecho pasar el primer test antes de escribir el siguiente, pero lo que quería con este ejemplo es que vierais el aspecto que tendrán nuestras especificaciones.

  • Línea 1: Nos encontramos con la función describe.
    Esta tiene como primer parámetro un texto que da nombre a la suite o conjunto de tests que vamos a ejecutar. Este nombre, normalmente hará referencia al SUT (Subject Under Test), es decir, la pieza que estamos probando o para la que estamos definiendo las
    especificaciones, en nuestro caso la lista de tareas. El segundo parámetro es una función donde pondremos la suite de tests (conjunto de especificaciones).
  • Línea 3: Nos encontramos la función it.
    Esta define una especificación, tomando un primer parámetro que es el título y un segundo parámetro que es una función que tendrá el código del test y donde pondremos nuestras expectativas. La idea es que si concatenamos el nombre de la suite (el describe) con el título de la especificación (el it) formamos una frase que describe que es lo que debe cumplir el SUT.
  • Línea 7: Simplemente destacar que podemos tener describe anidados a cualquier nivel. Esto no es más que una forma de organizar nuestros tests y la gracia que tiene es que como leemos las especificaciones, por ejemplo en el caso del segundo it
    (el primero que está en el describre anidado) seria “Una lista de tareas Cuando se añade una tarea no debe estar vacía”.

Si damos de alta este fichero en SpecRunner.html y lo ejecutamos, encontraremos algo de este estilo:

Runner de HTML de Jasmine con los tests de la Lista de Tareas

Si os fijáis todo está en verde y esto es terrible!!! pero es natural porque nos hemos saltado el ciclo de Rojo/Verde/Refactor, y como no hemos definido ni test ni expectativas todo sale en verde. Vamos a arreglar esto en el siguiente punto.


3.3. Escribiendo los tests y las expectativas

Por ahora lo que hemos hecho es escribir la descripción de qué esperamos de nuestro SUT, pero no estamos ejecutando nada. Vamos a añadir un poco de código para darle vida al asunto (ojo, insisto en que en la “vida real” estoy hay que hacerlo paso a paso rojo/verde/refactor).

describe("Una lista de tareas", function() {

    var todoList;

    beforeEach(function() {
        todoList = new TodoList();
    });

    it("debe estar vacía cuando está recién creada", function() {
        expect(todoList.isEmpty()).toBe(true);
    });

    describe("Cuando se añade una tarea", function() {

        beforeEach(function() {
            todoList.addTask("Hacer tutorial para adictosaltrabajo.com");
        });

        it("no debe estar vacía", function() {
            expect(todoList.isEmpty()).toBe(false);
        });

        it("debe poder contener más de una tarea", function() {
            todoList.addTask("Ir a la comida de navidad de Autentia");
            expect(todoList.size()).toBe(2);
        });

        it("debe estar sin completar nada más añadirse a la lista de tareas", function() {
            expect(todoList.task(0).isDone()).toBe(false);
        });

        it("se debe poder completar", function() {
            todoList.task(0).done();
            expect(todoList.task(0).isDone()).toBe(true);
        });

        it("debe quedar vacía al borrar la última tarea", function() {
            todoList.removeTask(0);
            expect(todoList.isEmpty()).toBe(true);
        });
    });
});

Destacamos:

  • Líneas 5 a 7 y 15 a 17: Vemos como usamos la función beforeEach para inicializar el contexto antes de ejecutar cada test (cada it). La idea es que cada test (cada it) se pueda ejecutar de forma independiente, y para ello necesitamos inicializar un contexto o fixture sobre la que se ejecutará el test. También existe el método afterEach que nos permite limpiar el entorno una vez termina cada test (por ejemplo si quisiéramos hacer un rollback de base de datos).
  • Línea 10, 20, 25, …: Usamos la función expect para describir las expectativas que debe cumplir nuestro código. A esta función le concatenamos los Matchers, en el ejemplo el toBe para hacer una comparación ===, pero existen muchos más como: toEqual, toMatch, toBeDefined, … incluso podemos escribir los nuestros propios. Podemos ver la lista completa en al documentación sobre los Matchers de Jasmine.


3.4. Escribiendo el código de producción

Si ahora volvemos a ejecutar los test (es tan fácil como volver al navegador y recargar la página SpecRunner.html) veremos algo de este estilo:

Como muestra un error el unner de HTML de Jasmine

Así es como reporta Jasmine los errores. Nos va diciendo que especificaciones han fallado, el por qué, y la pila de llamadas hasta el fallo. Ahora mismo nos fallan todas las especificaciones parque no tenemos código de producción, así que vamos a añadirlo (vuelvo a insistir en que nos estamos saltando los pasos de TDD, que luego no se os olvide hacerlo bien).

/**
 * Representa una tarea de la lista de tareas.
 * @param title el título de la tarea
 * @constructor
 */
function Task(title) {
    this.title = title;
    this.alreadyDone = false;
}

Task.prototype.isDone = function() {
    return this.alreadyDone;
};

Task.prototype.done = function() {
    this.alreadyDone = true;
};

/**
 * Representa una lista de tareas.
 * Puede tener varias tareas, cada una de las cuales estará terminada o pendiente.
 * @constructor
 */
function TodoList() {
    this.tasks = [];
}

TodoList.prototype.isEmpty = function() {
    return this.tasks.length === 0;
};

TodoList.prototype.addTask = function(title) {
    this.tasks.push(new Task(title));
};

TodoList.prototype.size = function() {
    return this.tasks.length;
};

TodoList.prototype.task = function(taskIndex) {
    return this.tasks[taskIndex];
};

TodoList.prototype.removeTask = function(taskIndex) {
    return this.tasks.splice(taskIndex, 1);
};

Con este fichero conseguimos volver a tener en verde todos los tests. Eso si, que no se os olvide añadirlo al fichero SpecRunner.html!!!

Vamos a dejar ya de profundizar en Jasmine, pero hay mucho más, por ejemplo como usar el resto de Matchers o crear los nuestros, como hacer Spies, como mockear el reloj de JavaScript, como probar operaciones asíncronas, … para todo ello os recomiendo leer la
documentación oficial de Jasmine y practicar, practicar y practicar.

4. Jasmine con Maven


4.1. Configurando el proyecto Maven

Si tenemos un proyecto Java donde también tenemos código JavaScript, podemos integrar en la build de Maven los test de Jasmine gracias al plugin: jasmine-maven-plugin.

Supongamos que tenemos un proyecto web sencillo:

mvn archetype:generate 
    -DgroupId=com.autentia.tutorial 
    -DartifactId=jasmine-maven 
    -DarchetypeArtifactId=maven-archetype-webapp 
    -DinteractiveMode=false

Ahora debemos editar el pom.xml para añadir el plugin de jasmine:

<build>
    ...
    <plugins>
        ...
        <plugin>
            <groupId>com.github.searls</groupId>
            <artifactId>jasmine-maven-plugin</artifactId>
            <version>1.2.0.0</version>
            <extensions>true</extensions>
            <executions>
                <execution>
                    <goals>
                        <goal>test</goal>
                    </goals>
                </execution>
            </executions>
            <configuration>
                <jsSrcDir>src/main/webapp/js</jsSrcDir>
            </configuration>
        </plugin>
        ...
    </plugins>
    ...
</build>

La estructura de directorios y fichero debería ser la que se muestra en la siguiente figura. Nótese que hemos copiado los ficheros js del ejemplo anterior en los directorios correspondientes, y que los ficheros de producción y las especificaciones/test están
claramente separados tal como sugiere la estructura estándar de directorios de Maven:

Estructura de directorios usando Jasmine con Maven

4.2. Haciendo BDD/TDD

Ahora para hacer BDD/TDD podemos ejecutar en la línea de comandos:

mvn jasmine:bdd

Y abrir con nuestro navegador la dirección: http://localhost:8234/

Veremos la misma pantalla de resultados de Jasmine que veíamos en los ejemplos anteriores. La gran ventaja de este método es que Maven está vigilando tanto el directorio de ficheros de producción como el de test, de forma que en cuento modificamos o añadimos ficheros estos estarán disponibles sin necesidad de tener que añadirlos manualmente a SpecRunner.html, como nos pasaba anteriormente, y bastará con recargar la página en el navegador para ver los resultados de los cambios introducidos.

Además nos permite trabajar con CoffeScript directamente.


4.3. Integración con el ciclo de construcción de Maven

Para esto no tenemos que hacer nada, viene de serie! Basta con ejecutar el típico

mvn clean install

de Maven y veremos como ademas de todas las fases típicas de Maven aparece una más que lanza los test de Jasmine. En la consola deberíamos ver algo como:

Runner de Maven de Jasmine

Como veis, la integración con Maven es fácil, sencilla y muuuy práctica. Incluso nos puede merecer la pena aunque no trabajemos con Java (bastaría configurar el plugin para que los directorios de código y test se adaptaran mejor a nuestra estructura de proyecto).

5. Jasmine con Node.js


5.1. Configurando el proyecto Node.js

Por último vamos a ver como integrar Jasmine con Node.js.
Teniendo en cuenta que estamos hablando de desarrollo con JavaScript, estas opción no podía faltar 😉

Para hacer la instalación ejecutaremos en la línea de comandos:

sudo npm install jasmine-node -g

(en mi caso hago sudo porque estoy en Mac y quiero hacer una instalación global ‘-g’ para que esté disponible para todos los usuarios)

Además hay que tener en cuenta que node funciona con un sistema de módulos de forma que todo lo que declaramos en un fichero (variables, funciones, …) son privadas a este. Esto nos obliga a modificar ligeramente el código que habíamos visto hasta ahora. Os
recomiendo abrir el código para ver los cambios.

Estructura de directorios usando Jasmine con Node.js

En la estructura de ficheros también podemos apreciar que hemos cambiado el nombre de los ficheros de expectativas ya que ahora tienen que tener la extensión .spec.js o
.spec.coffee (sí, habéis visto bien, igual que con Maven, con Node también podemos escribir nuestras expectativas en CoffeScript).

5.2. Ejecutando los test

Basta con ejecutar en la línea de comandos:

jasmine-node spec

Siendo spec el nombre del directorio donde están los ficheros de expectativas de Jasmine. Este comando ya se encarga de ejecutar todos los tests que encuentre en este directorio o
subdirectorios. De forma que, en nuestro caso, obtendremos una salida de este estilo:

Runner de Node.js de Jasmine


5.3. Ejecutando los tests automáticamente

jasmine-node tiene un modo muy interesante de trabajo que consiste en ejecutar los tests automáticamente en cuanto modificamos un fichero. De esta forma ni siquiera tenemos que recargar el navegador, como nos pasaba con Maven. Bastará con guardar el fichero y veremos como cambia la consola.

Para activar este modo basta con ejecutar en la línea de comandos:

jasmine-node --autotest .

Nótese que hemos especificado como directorio donde están los tests . (el directorio actual que engloba tanto spec/ como src/). Esto es porque aunque los tests realmente sólo están el directorio spec/ queremos que Node esté vigilando los cambios tanto de estos ficheros como de los ficheros que están en el directorio de código de producción, de forma que en cuanto cambiemos un test o un fichero de producción, se ejecuten los tests automáticamente.

Una vez activado este modo, veremos en la consola lo siguiente:

Runner automático de Node.js de Jasmine

Se ve como se han ejecutado todos los tests y han ido bien. Se ve una última línea con { ‘0’: 0, ‘1’: null }, esto es algo interno y lo podemos ignorar (de hecho seguramente acaben quitándolo de la salida). También vemos como la consola se queda bloqueada en este punto. Vamos a cambiar algo en los fuentes para que fallen los tests, a ver que pasa en cuento salvemos los cambios:

Error en el runner automático de Node.js de Jasmine

Podemos ver el test que ha fallado con su pila correspondiente. Si ahora volvemos a cambiar el código y volvemos a grabar:

Vuelta al verde en el runner automático de Node.js Jasmine

Aquí es interesante destacar como primero ha ejecutado la suite que había fallado (6 tests, 6 assertions, 0 failures), y como después a ejecutado todos los tests (11 tests, 14 assertions, 0 failures). Este es muy útil ya que primero nos centramos en poner en verde lo que está rojo, y luego comprobamos que no hemos “roto” nada más 😉

Con esta forma de trabajar hay que destacar que según como funcione nuestro editor podemos tener algún problema porque no se detecten correctamente los cambios al guardar los ficheros. Esto por ejemplo pasa con el Vim o el IntelliJ ya que utilizan un modo de guardado seguro que lo que hace es guardar los cambios en un fichero nuevo, luego borrar el original y renombrar el nuevo. Para desactivar esto en IntelliJ podemos irnos a la configuración –> General –> Use “safe write” (safe changes to temporary file first).

6. Conclusiones

Da igual el lenguaje o plataforma en la que desarrollemos, si queremos hacer las cosas bien es fundamental utilizar técnicas como BDD y o TDD. Recordar que este tipo de herramientas nos ayudan a guiar nuestro diseño para conseguir piezas menos acopladas y más reutilizables, y nos permiten tejer una red seguridad que nos permitirá evolucionar nuestro sistema sin miedo al cambio, ya que los tests nos indicarán de inmediato si algo ha dejado de funcionar.

En este tutorial hemos visto como hacerlo para JavaScript, gracias a Jasmine, el framework que posiblemente se ha convertido en el estándar de facto. Y ojo, porque aquí sólo hemos visto como usarlo en solitario, con Maven o con Node, pero lo podemos usar combinado con muchas más plataformas, como Ruby o .NET.

7. Sobre el autor

Alejandro Pérez García, Ingeniero en Informática (especialidad de Ingeniería del Software) y Certified ScrumMaster

Socio fundador de Autentia (Desarrollo de software, Consultoría, Formación)

mailto:alejandropg@autentia.com

Autentia Real Business Solutions S.L. – «Soporte a Desarrollo»

http://www.autentia.com

  • Etiquetas
  • js
Alejandro Pérez García
Alejandro es socio fundador de Autentia y nuestro experto en Java EE, Linux y optimización de aplicaciones empresariales. Ingeniero en Informática y Certified ScrumMaster. Seguir @alejandropgarci Si te gusta lo que ves, puedes contratarle para darte ayuda con soporte experto, impartir cursos presenciales en tu empresa o para que realicemos tus proyectos como factoría (Madrid). Puedes encontrarme en Autentia: Ofrecemos servicios de soporte a desarrollo, factoría y formación.

9 COMENTARIOS

DEJA UNA RESPUESTA

Por favor ingrese su comentario!

He leído y acepto la política de privacidad

Por favor ingrese su nombre aquí

Información básica acerca de la protección de datos

  • Responsable:
  • Finalidad:
  • Legitimación:
  • Destinatarios:
  • Derechos:
  • Más información: Puedes ampliar información acerca de la protección de datos en el siguiente enlace:política de privacidad