jasmine

Categories:

Ya que vimos el concepto teórico de cómo evaluar código con JavaScript vamos a revisar de manera general la parte práctica con jasmine.

Organizar archivos en Jasmine
Descargamos el zip del repositorio de Jasmine en github.

Jasmine en github

Descomprimimos el archivo.

Organización jasmine

Dentro de la carpeta principal están las carpetas lib, src, spec.

La carpeta lib contiene la librería no hay que modificar nada ahí.

En la carpeta src vamos a incluir el código fuente – archivos de JavaScript que queremos evaluar.

En la carpeta spec vamos a incluir las especificaciones – pruebas con las que vamos a evaluar el código. No es necesario que los archivos de las especificaciones tengan un nombre en particular pero es recomendable utilizar el nombre del archivo del código fuente seguido de Spec para asociar las especificaciones con el código fuente. Las especificaciones también son archivos JavaScript.

Abrimos el archivo SpecRunner.html en un editor de texto. Modificamos las etiquetas script para incluir nuestros archivos. Donde dice include source files here vamos a mandar llamar los archivos del código fuente y donde dice include spec files here vamos a incluir las especificaciones. Los archivos que se incluyen son ejemplos que podemos utilizar como referencia. Si no los necesitamos los podemos eliminar.

<!-- 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>

Abrir el archivo SpecRunner.html en un navegador.

Crear especificaciones

Para crear las especificaciones que van a evaluar el código fuente podemos seguir el siguiente proceso.

  1. Creamos el archivo del código fuente en src, la especificación en spec y los incluimos en el archivo SpecRunner.html
  2. Dentro de la especificación describimos un escenario.
    Este escenario es un contexto general donde se va a probar el código.
  3. Dentro del escenario se crea una especificación.
    Esta especificación describe lo que el código debe hacer.
  4. Cada especificación tiene expectativas del resultado que se va a obtener cuando se evalúe el código.
  5. Ahora tenemos que escribir el código para que cumplir con la expectativa.
  6. Si es necesario, debemos crear diferentes expectativas dentro del mismo contexto.
  7. También es posible crear distintos contextos – diferentes escenarios – dentro del contexto principal para evaluar el código en distintas circunstancias.
  8. Repetir del paso 3 al 7 por cada archivo de código fuente que queramos evaluar.

Archivo con especificaciones

Dentro del archivo de las especificaciones podemos encontrar los siguientes elementos.

describe

Para describir un escenario se utiliza describe seguido del nombre del escenario (o contexto) y una función la cual va a contener las especificaciones.

describe("Mi escenario", function () {
/*
Escenario.
Aquí van las especificaciones. También se pueden
incluir otros escenarios para dar un contexto más específico.
*/
});

Especificación
Dentro del escenario (o de los escenarios) creamos distintas especificaciones.
Una especificación – de manera general – es la descripción de lo que queremos que el código fuente realice.

/*
  Especificación de lo que esperamos que haga el código.
*/

it("Define la especificación", function () {
  // Aquí definimos las expectativas.
});

Expectativa
Las expectativas son la manera que tenemos para comparar un valor esperado con el resultado de la evaluación del código fuente. Comienzan con spec.

// Las expectativas comienzan con expect
expect

Para poder comparar distintos valores Jasmine tiene un conjunto de igualadores.
En el caso de que ningún igualador sea conveniente para nuestro código es posible crear igualadores personalizados.

Igualadores

Los igualadores se encargan de comparar el valor obtenido del código con las expectativas. El valor que se obtiene lo utiliza jasmine para determinar si el valor esperado es correcto – la especificación pasó la prueba – o no es correcto – la especificación no pasó la prueba -.
Cualquier igualador puede evaluar a una afirmación negativa al encadenar a la llamada de la expectativa un not antes del igualador.

Comparación

Igualador

Sintaxis

Sintaxis (negativa)

Compara que los valores sean idénticos

toBe

expect(true).toBe(true);
expect(false).not.toBe(true);

Compara que los valores sean iguales

toEqual

var uno = 1;
expect(uno).toEqual(1);
  
  var foo = { 
  a: 12,      
  b: 34       
  };

  var bar =  {
  a: 12,      
  b: 34       
  };           
  expect(foo).toEqual(bar);
var uno = 1;
expect(uno).not.toEqual(1);

var foo = { 
a: 12,      
b: 34       
}; 

var fu =  { 
  a: 1,     
  b: 2,     
};

expect(foo).not.toEqual(fu);

RegExp

toMatch

var message = 'foo bar baz';
expect(message).toMatch(/bar/);
expect(message).toMatch('bar');
var message = 'foo bar baz';
expect(message).not.toMatch(/quux/);

Verifica que el valor se encuentre definido, que no sea undefined

toBeDefined

var a = {foo: 'foo'};
expect(a.foo).toBeDefined();
var a = {foo: 'foo'};
expect(a.bar).not.toBeDefined();

Verifica que el valor no se encuentre definido, que sea undefined

toBeUndefined

var a = { foo: 'foo' };
expect(a.bar).toBeUndefined();
var a = {foo: 'foo' };
expect(a.foo).not.toBeUndefined();

Verifica que un valor sea nulo (null)

toBeNull

var a = null;
var foo = 'foo';
expect(null).toBeNull();
expect(a).toBeNull();
var a = null;
var foo = 'foo';
expect(foo).not.toBeNull();

Espera que el valor sea verdadero

toBeTruthy

var a, foo = 'foo';
expect(foo).toBeTruthy();
var a, foo = 'foo';
expect(a).not.toBeTruthy();

Espera que el valor sea falso

toBeFalsy

var a, foo = 'foo';
expect(a).toBeFalsy();
var a, foo = 'foo';
expect(foo).not.toBeFalsy();

Busca un elemento dentro de un arreglo

toContain

var semana = ['L','M', 'X', 'J', 'V', 'S', 'D'];
expect(a).toContain('M');
var semana = ['L','M', 'X', 'J', 'V', 'S', 'D'];
expect(a).not.toContain('Lunes');

Compara que un valor sea menor a otro

toBeLessThan

var uno = 1;
var seis = 6;
expect(uno).toBeLessThan(seis);
var uno = 1;
var seis = 6;
expect(seis).not.toBeLessThan(uno);

Compara que un valor sea mayor a otro

toBeGreaterThan

var uno = 1;
var seis = 6;
expect(seis).toBeGreaterThan(uno);
var uno = 1;
var seis = 6;
expect(uno).not.toBeGreaterThan(seis);

Verifica si una función envía una excepción

toThrow

function suma(){
  return a + 1;
}
expect(suma).toThrow();
function otra_suma(){
  return 2 + 1;
}
expect(otra_suma).not.toThrow();

Instalación y desmontaje

Para evitar que se duplique el código, si se quieren realizar ciertas acciones antes o después de que se evalúe cada especificación dentro de un contexto, jasmine tiene dos métodos que realizan esa tarea.

beforeEach

Esta función se evalúa antes de ejecutar cada especificación dentro de un contexto (describe).

afterEach
Esta función se llama después de ejecutar cada especificación dentro de un contexto (describe).

beforeEach(function () {
        curso.nombre = "JavaScript";
        // Los meses van de 0 Enero al 11 Diciembre
        curso.inicio = new Date(2014, 0, 1);
        curso.termino = new Date(2014, 0, 11);        
      });

Referencia de uso
En este caso utilicé el ejemplo de la clase curso que está en la publicación programación orientada a objetos para evaluarlo.

var Curso = function () {
  this.nombre;
  this.inicio;
  this.termino;
  this.duracion = duracion;
  
  function duracion() {  
    if (this.inicio !== undefined && this.termino !== undefined) {
      return Math.abs((this.termino.getTime() - this.inicio.getTime())/ 86400000) + " dias";
    } else {
      return undefined;
    }
  } 
}

Curso es una clase con tres atributos (nombre, inicio, término) y una función que calcula la duración del curso en días.

Como es una clase, en las especificaciones debemos de interactuar con una instancia (objeto).
Es por eso que se crea un curso en el contexto general.
En esta especificación hay dos contextos, el primero es cuando se crea una instancia los valores de los atributos no deben estar definidos y el resultado de la función tampoco debe de estar definido.

En el segundo caso, cuando se asignan valores a los atributos, deben de tener un valor definido. En este caso también comparo el valor que tiene la instancia. Como hay diferentes especificaciones que evalúan los diferentes atributos le asigno valores a los atributos antes de que se ejecuten las especificaciones en ese contexto únicamente.

describe("Curso", function() {

  var curso = new Curso();


  describe("Al crear una nueva instancia", function () {
    it("El nombre no está definido", function() {
      expect(curso.nombre).toBeUndefined();
    });      
    
    it("La fecha de inicio no está definida", function() {
      expect(curso.inicio).toBeUndefined();
    });
    
    it("La fecha de término no está definida", function() {
      expect(curso.termino).toBeUndefined();
    });
    
    it("La duración no está definida", function() {
      var duracion = curso.duracion();
      expect(duracion).toBeUndefined();
    });
  });

    describe("Al asignarle valores a la instancia", function () {
      
      beforeEach(function () {
        curso.nombre = "JavaScript";
        // Los meses van de 0 Enero al 11 Diciembre
        curso.inicio = new Date(2014, 0, 1);
        curso.termino = new Date(2014, 0, 11);        
      });
      
      
      it("Tiene nombre asignado", function() {
        expect(curso.nombre).toBeDefined();
        expect(curso.nombre).toBe("JavaScript");        
      });      
    
      /*
      + delante de una fecha convierte el valor a milisegundos.
      La fecha está en formato Epoch y comienza el 1 de enero de 1970 a las 00:00:00
      
      http://www.epochconverter.com/

      var fecha = new Date(1969, 11, 31, 18, 0 ,0);

      -> Wed Dec 31 1969 18:00:00 GMT-0600 (CST)

      Si al obtener una fecha se obtiene una zona horaria se ajustan los milisegundos. 
      
      +fecha
      
      */
    
      it("Tiene fecha de inicio asignada", function() {
        var fecha = new Date(2014, 0, 1);
        expect(curso.inicio).toBeDefined();
        expect(+curso.inicio).toBe(+fecha);                
      });
    
      it("Tiene fecha de término asignada", function() {
        var fecha = new Date(2014, 0, 11);
        expect(curso.termino).toBeDefined();
        expect(+curso.termino).toBe(+fecha);
      });
    
      it("Tiene duración asignada", function() {
        var duracion = curso.duracion();
        expect(duracion).toBeDefined();        
        expect(duracion).toBe("10 dias");
      });
    
  });

});

Si abrimos el archivo specRunner.html en el navegador podemos ver el contexto general, los dos contextos y un vínculo por cada especificación.

Si presionamos el vínculo de una especificación únicamente se evalúa esa especificación dentro de ese contexto. Esto es útil si estamos trabajando con una parte del código en específico.

Visualizar specRunner en el navegador

¿Y si no utilizamos clases para nuestro código?

A veces únicamente necesitamos utilizar funciones para nuestro código, aún así es recomendable evaluar el código. En este caso, podemos crear variables dentro de las especificaciones y almacenamos el resultado de las funciones para después compararlo con valores esperados.

it("suma dos números", function () {
      var resultado = sumar(2, 4);
      expect(resultado).toBe(6);
    });

En esta referencia de jasmine están el código y especificaciones del curso y de las funciones.