Tester du code javascript

Publié par Éric Le Merdy

Il y a une semaine, Samori Gorce (@shinuza) est venu chez Valtech. L'objectif était de nous faire une démonstration inspirée par son dernier talk au ParisJS et de nous faire participer à une séance de TDD avec du code Javascript.

QUnit

Il a commencé par une présentation de QUnit. Ce framework de test est issu du fameux projet JQuery qui facilite les développements javascript dans le navigateur depuis déjà quelques années. Avec QUnit, on dispose d'une page web qui passe les tests. Il suffit d'importer sa librairie à tester et écrire les tests dans un autre script (exemple de source).

mocha

Mocha est un framework de tests unitaire qui tourne dans nodeJS et dans le navigateur. Nous avons eu l'occasion de le découvrir en détails pendant notre TP.

Tutorial

  1. Installation de la dernière version de node JS Les paquets ubuntu de nodejs déploient une version un peu trop ancienne pour mocha. Il faut donc déclarer un repository particulier pour obtenir la dernière version avec le système de gestion de paquet du système:
    $ sudo apt-get install python-software-properties
    $ sudo add-apt-repository ppa:chris-lea/node.js
    Mise à jour et installation des paquets:
    $ sudo apt-get update
    $ sudo apt-get install nodejs
    $ sudo apt-get install npm
    npm est le gestionnaire de dépendances de nodejs. Mocha est donc installé grâce à npm.
    $ sudo npm install mocha -g
    Vérifications:
    $ node --version
    v0.6.11
    $ npm --version
    1.1.0-2
    $ mocha --version
    0.12.1
  2. Premier lancement
    $ mkdir ~/mocha-tutorial/
    $ cd ~/mocha-tutorial
    $ mocha
    
    node.js:201
            throw e; // process.nextTick error, or 'error' event on first tick
                  ^
    Error: ENOENT, no such file or directory 'test'
        at Object.readdirSync (fs.js:390:18)
        at Object. (/usr/lib/node_modules/mocha/bin/_mocha:174:14)
        at Module._compile (module.js:441:26)
        at Object..js (module.js:459:10)
        at Module.load (module.js:348:31)
        at Function._load (module.js:308:12)
        at Array.0 (module.js:479:10)
        at EventEmitter._tickCallback (node.js:192:40)
  3. Créer la structure du projet Mocha s'attend à trouver un répertoire test.
    $ mkdir test
    $ mkdir lib  # Répertoire de sources
  4. Premier test
    var assert = require('assert');
    require('../lib/array.js')();
    
    describe('Array#prototype', function() {
            it('should return the first element of my array', function() {
                    assert.equal([1, 3, 3, 4, 5].first(), 1);
            });
            it('should return the last element of my array', function() {
                    assert.equal([1, 3, 3, 4, 5].last(), 5);
            });
    });
    Dans ce premier fichier de test, on importe la librairie d'assertions de mocha assert. On pourrait aussi utiliser une autre librairie d'assertions comme should.js par exemple.
    Le reste du fichier de test est la spécification de deux méthodes qui doivent étendre le prototype de la classe Array : first et last.
    $ mocha
    
    node.js:201
            throw e; // process.nextTick error, or 'error' event on first tick
                  ^
    Error: Cannot find module '../lib/array.js'
        at Function._resolveFilename (module.js:332:11)
        (...)
        at EventEmitter._tickCallback (node.js:192:40)
    
    Comme on pouvait le prévoir, le test ne passe pas.
  5. Première implémentation : étendre le prototype de la classe Array Créez le fichier array.js dans le répertoire lib
    module.exports = function() {
            Array.prototype.first = function() {
                    return this[0];
            };
            Array.prototype.last = function() {
                    return this[this.length - 1];
            };
    };
    module.exports fait partie de nodejs et permet d'isoler le contexte d’exécution dans un environnement restreint au test. C'est utile en particulier ici puisqu'on modifie le prototype de la classe Array.
    $ mocha 
    
      ..
    
      ✔ 2 tests complete (3ms)
    
    
    Les deux points montrent ici qu'on a exécuté deux tests. Dans le cas de tests plus long, ils permettent de montrer l'avancement (comme dans phpunit et d'autres frameworks de test probablement).
  6. Changer la sortie des tests Mocha peut représenter le résultat des tests de différentes façons. Par exemple, on peut demander une sortie de type spec :
    $ mocha --reporter spec
    
    
      Array#prototype
        ✓ should return the first element of my array 
        ✓ should return the last element of my array 
    
    
      ✔ 2 tests complete (3ms)
    
    
    Pour connaître la liste des reporters disponibles :
    $ mocha --reporters
    
        dot - dot matrix
        doc - html documentation
        spec - hierarchical spec list
        json - single json object
        progress - progress bar
        list - spec-style listing
        tap - test-anything-protocol
        landing - unicode landing strip
        xunit - xunit reportert
        teamcity - teamcity ci support
        json-stream - newline delimited json events
    
    
    Les connaisseurs auront tout de suite remarqué le reporter xunit qui leur permettra d'obtenir le résultat des tests dans leur usine logicielle préférée.
  7. Deuxième test : test d'une fonction asynchrone Créez un fichier hello.js
    var hello = require('../lib/hello.js');
    
    describe('Saying hello', function() {
            it('should wait before saying hello asynchronously', function(done) {
                    hello(function() {
                            done();
                    });
            });
    });
    Il s'agit ici de spécifier une fonction asynchrone.
  8. Implémentation de la fonction asynchrone Créez un fichier hello.js dans le répertoire lib :
    function hello(cb) {
            setTimeout(cb, 1000);
    }
    
    module.exports = hello;
    Le résultat de ce test montre que le test asynchrone a pris une seconde pour s’exécuter :
    $ mocha -R spec
    
    
      Saying hello
        ✓ should wait before saying hello asynchronously (1004ms)
    
      Array#prototype
        ✓ should return the first element of my array 
        ✓ should return the last element of my array 
    
    
      ✔ 3 tests complete (1014ms)
    
    
    Mocha trouve même ça bizarre et affiche ce temps. Dans la console, il colore même ce chiffre en rouge.
  9. "Mocha is watching" Il est possible de lancer Mocha en mode "watching". C'est-à-dire qu'il va inspecter les modifications dans le répertoire tests et repasser les tests en cas de modification. On a donc un feedback de test vraiment très rapide :
    $ mocha -w
    
      �? watching
    
    On est proche du comportement d'un [infinitest](http://infinitest.github.com/) dans l'IDE Java par exemple.

D'autres frameworks

  • [FuncUnit](http://funcunit.com/) Framework de test fonctionnel basé sur Selenium, JQuery, Qunit et EnvJS. Il Permet de rejouer des événements sur un site web dans un navigateur et dans l'usine logicielle grâce à EnvJS
  • JS Test Driver Intégration d'un moteur d'éxécution sous forme d'un serveur dans l'IDE. Les clients sont les navigateurs qui se connectent au serveur pour attendre les tests à faire passer.
  • [jsPerf](http://jsperf.com) Pour tester différentes implémentations de fonctions js dans des navigateurs différents.
  • [Vows](http://vowsjs.org) Equivalent de mocha. Ajoute son propre langage d'assertions. Interface sympa.
  • [JSCoverage](http://siliconforks.com/jscoverage) Couverture de code par les tests en javascript.
  • Frameworks MVC : JavascriptMVC - EmberJS - **Backbone** - KnockoutJS
  • **CasperJS** Fournit une API au dessus de [PhantomJS](http://www.phantomjs.org/) (navigateur headless) pour facilement scripter des tests fonctionnels

Conclusion

Résumé de la pile de test :

  • Côté serveur : mocha est particulièrement adapté pour tester des implémentations serveur nodejs. mocha peut s'éxécute dans Jenkins
  • Qunit pour les tests unitaires de composants IHM
  • Tests fonctionnels : FuncUnit qui est basé sur Selenium tourne aussi dans Jenkins.
  • Limiter le “<acronym title=“Code souvent dupliqué qui n'apporte pas de valeur mais qui doit quand même être là pour que ça marche”>Boilerplate” pour passer les tests.