En busca del entorno de tests perdido

Desde hace algún tiempo he estado trabajando con JavaScript y desde el principio he buscado el entorno de tests perfecto. En el mundo Java todo está más estandarizado. Si trabajas en un proyecto Java seguramente te encuentres con JUnit, alguna librería de mocks, por ejemplo Mockito, y todo ello configurado con Maven. También te encontrarás una integración perfecta con el IDE y si usas integración continua tendrás alguna herramienta como Jenkins que pasa los tests por ti.

Jasmine

Cuando empecé a trabajar con JavaScript encontré que había muchas librerías y me costó decidirme por una. Decidí utilizar Jasmine por su sintaxis y que tuviese soporte para espías. El único inconveniente que encontré fue su sintaxis para ejecutar código asíncrono. Me parece que hace que los tests sean menos legibles. La utilicé durante un tiempo pero me sentía incómodo cada vez que escribía un test de código asíncrono.

Mocha

Buscando alternativas a Jasmine encontré Mocha. La principal característica que me llamó la atención fue lo simple que se volvían los test de código asíncrono, simplemente un callback que se llama cuando el test haya finalizado. Viniendo de Jasmine me pareció una solución brillante. A raíz de empezar a utilizar Mocha descubrí otras características interesantes, como por ejemplo los diffs cuando un test falla o la detección de variables globales. Mocha tiene soporte para NodeJS y para código de navegador.

Para poder ejecutar los tests de navegador desde la consola utlicé grunt-mocha. Un plugin de grunt que permite ejecutar los tests en un PhantomJS. El problema con este plugin es que para especificar los ficheros de fuentes y de tests necesitas crear un fichero html donde se incluyen todos los ficheros. Al principio me pareció bien porque simplemente abriendo ese fichero con cualquier navegador se ejecutaba la batería de tests. El problema con el fichero html es que no permite wildcards (test/*.js) y debía especificar los ficheros uno a uno.

Karma

Hace tiempo vi en HackerNews una herramienta llamada Testacular. Hice unas pequeñas pruebas pero no fue hasta este fin de semana que decidí hacer pruebas más en profundidad. Testacular es un juego de palabras entre testing y spectacular un poco desafortunado ya que recuerda a una parte del cuerpo… Los creadores decidieron, de forma muy acertada, renombrar el proyecto y ahora se llama Karma. Sus creadores son los mismos que los de AngularJS.

Karma permite probar el código en navegadores y dispositivos reales o en instancias de PhantomJS. Es agnóstico al framework de testing con el que están escrito los tests, por lo tanto puedo seguir utilizando Mocha y simplemente configurar Karma con el adapter adecuado para Mocha.

Karma se instala utilizando NPM.

$ npm install karma --save-dev

Para las pruebas vamos a utilizar un ejemplo de la vida real, un proyecto muy complejo: una calculadora.

src/Calculator.js

(function () {
    "use strict";

    var Calculator = function () {};

    Calculator.prototype = {
        add : function (a, b) {
            return a + b;
        }
    };

    window.Calculator = Calculator;

}());

test/Calculator.test.js

describe('Calculator', function () {
    it('should add two numbers', function () {
        var calculator = new Calculator();
        expect(calculator.add(1, 2)).to.equal(3);
    });
});

Lo primero que tenemos que hacer es configurar Karma.

$ karma init

Este comando mostrará un asistente para configurar Karma. La configuración que utilicé fue:

El resultado del comando es un fichero karma.conf.js con la configuración que hemos especificado.

Mocha permite trabajar con varias librerías de assertions. En mi caso utilizo chai. Esto también es necesario configurarlo en Karma, por suerte existe el plugin karma-chai.

npm install karma-chai --save-dev

Y añadimos chai en la lista de frameworks del fichero karma.conf.js

frameworks: ['mocha', 'chai'],

Esta es la configuración mínima necesaria para que Karma sea capaz de ejecutar los tests del proyecto.

$ karma start

Con el comando start se abrirán todos los navegadores configurados y se ejecutarán los tests en todos ellos. Karma se queda a la escucha y cuando detecta un cambio en alguno de los ficheros vuelve a ejecutar los tests automáticamente.

Desde la primera vez que probé Karma el proyecto ha madurado bastante. Ha simplificando mucho su configuración y se ha convertido en una herramienta muy modular con bastantes plugins.

Integración con Grunt.js

Es muy importante tener los test integrados con el ciclo de empaquetado de la aplicación. Ya hablé sobre Grunt.js en un post anterior. Para integrar estas dos herramientas podemos utilizar el plugin grunt-karma.

$ npm install grunt grunt-karma --save-dev

La configuración de Grunt sería la siguiente

module.exports = function (grunt) {

    grunt.loadNpmTasks("grunt-karma");

    grunt.initConfig({
        karma  :{
            test : {
                configFile : 'karma.conf.js',
                singleRun: true
            },
            dev : {
                configFile : 'karma.conf.js'
            }
        }
    });

    grunt.registerTask("test", "karma:test");
    grunt.registerTask("dev", "karma:dev");
};

De esta forma estamos configurando dos tareas:

Integración con IntelliJ IDEA

Hasta ahora tenemos una configuración perfecta para trabajar con un editor de texto y la consola. En mi día a día trabajo con IntelliJ IDEA que cuenta con un plugin para Karma. El plugin se puede instalar desde el repositorio de plugins. Está disponible únicamente para IntelliJ IDEA 13 EAP.

La configuración del plugin es muy sencilla, después de instalarlo, añade una nueva run/debug configuration de tipo karma y configura la ruta del fichero karma.conf.js. Al ejecutar los tests podemos ver el resultado dentro del IDE.

Karma run

La ejecución de los tests se puede depurar pero sólo conseguí que funcionara con Chrome.

Karma debug

Por último también, se puede hacer un análisis de coverage modificando el fichero karma.conf.js.

    reporters: ['progress', 'coverage'],

    preprocessors: {
      'src/*.js': ['coverage']
    },

Karma coverage

Integración continua con Travis CI

La integración continua es muy importante para garantizar la calidad del código. En mi trabajo utilizamos Jenkins, pero si estas trabajando en un proyecto open source quizás no tengas la infraestructura necesaria para poder instalarlo. Travis CI es una herramienta totalmente gratuita para este tipo de proyectos. Se integra perfectamente con GitHub y require muy poca configuración.

Para empezar a utilizar Travis CI es necesario que el código esté subido a un repositorio público de GitHub. Aquí está el código de ejemplo de este post https://github.com/axelhzf/post-karma.

Para integrar Travis con Karma seguí las instrucciones de la documentación de karma. Añadí una nueva tarea en el Gruntfile.js para que karma se ejecutara únicamente en PhantomJS.

module.exports = function (grunt) {

    grunt.loadNpmTasks("grunt-karma");

    grunt.initConfig({
        karma  :{
            test : {
                configFile : 'karma.conf.js',
                singleRun: true
            },
            dev : {
                configFile : 'karma.conf.js'
            },
            ci : {
                configFile : 'karma.conf.js',
                singleRun: true,
                browsers: ['PhantomJS']
            }
        }
    });

    grunt.registerTask("test", "karma:test");
    grunt.registerTask("dev", "karma:dev");
    grunt.registerTask("ci", "karma:ci");
};

Configuré el fichero package.json con el comando que va a ejecutar Travis para pasar los tests:

{
    "name" : "karma-post",
    "version" : "0.0.1",
    "description" : "Karma post",
    "dependencies" : {

    },
    "devDependencies" : {
        "grunt" : "~0.4.1",
        "grunt-karma" : "~0.6.2",
        "karma" : "~0.10.2",
        "karma-mocha" : "~0.1.0",
        "karma-chai" : "~0.0.2",
        "karma-coverage" : "~0.1.0",
        "karma-chrome-launcher" : "~0.1.0",
        "karma-firefox-launcher" : "~0.1.0",
        "karma-phantomjs-launcher" : "~0.1.0",
        "karma-html2js-preprocessor" : "~0.1.0",
        "mocha" : "~1.13.0",
        "grunt-cli": "~0.1.9"
    },
    "scripts" : {
        "test": "node_modules/grunt-cli/bin/grunt ci"
    },
    "repository" : {
        "type" : "git",
        "url" : "https://github.com/axelhzf/post-karma.git"
    },
    "author" : "Axel Hernández Ferrera <axelhzf@gmail.com>",
    "license" : "MIT",
    "bugs" : {
        "url" : "https://github.com/axelhzf/post-karma/issues"
    }
}

Y creé el fichero .travis.yml

language: node_js
node_js:
  - 0.10

Lo último que falta es habilitar los tests para este repositorio desde https://travis-ci.org/profile.

Con esta configuración, Travis ejecutará los tests cada vez que hagamos un push al repositorio. Aquí se puede ver el resultado https://travis-ci.org/axelhzf/post-karma

Conclusiones

Buscar un entorno que permita ejecutar los tests de un proyecto de forma adecuada es muy importante. Merece la pena invertir tiempo en la configuración del entorno de test adecuado. JavaScript es una plataforma en constante evolución por lo que es importante estar atento a las nuevas herramientas que se desarrollan y analizar si son adecuadas para mejorar el proyecto.

Encontrar el entorno perfecto para ejecutar los tests es una tarea complicada y depende mucho de las preferiencias de cada uno. Este post explica la configuración que a día de hoy me parece más adecuada por cumplir mis requisitos:

Comments