Boire ou coder ... Pourquoi choisir?
Publié le 23 mars 2010 23:00

Testez le code javascript de vos applications

Pourquoi tester son code javascript ?

Lorsque l'on cherche à faire du TDD, on apprécie fortement de pouvoir tester tout son code. Ne pas pouvoir tester certaines parties parce qu'elles sont développées dans un langage différent est particulièrement frustrant. Et surtout on perds tout l'intérêt d'écrire des tests car il faut tester manuellement une partie de l'application.

Dans le cas d'une application web, c'est notamment le problème avec les tests javascripts. Depuis plusieurs mois, je cherche une solution à ce problème. J'ai même fait un atelier à ce sujet lors de Paris Web 2010. Tester ses applications javascript avec QUnit et Selenium.

Divers problèmes m'ont cependant fait m'éloigner de QUnit. Vous comprendrez plus bas.

La problématique

Nous avons une application rails (mais cela pourrait très bien être une application django ou utilisant toute autre technologie serveur). Cette application a de nombreuses fonctionnalités et manipule les données côté serveur.

Pour diverses raisons, nous souhaitons exporter une partie de la manipulation des données (tout ce qui est de l'affichage) du côté du navigateur. Ainsi, nous économisons notre architecture serveurs en utilisant le CPU de l'utilisateur. Notre application va donc recevoir des données depuis le serveur (l'application rails) au format JSON, les traiter et les afficher.

Ce n'est pas l'objet de cet article. Mais pour afficher vos données, je vous conseille fortement l'utilisation d'un système de templating tel que mustache.js par exemple.

Du côté de notre application rails, nous écrivons tout plein de tests en utilisant Test::Unit ou RSpec.

Mais nous désirons également tester notre javascript.

Diverses solutions

Plusieurs solutions existent afin de tester ce javascript. Faisons-en un petit récapitulatif.

Selenium

Selenium exécute un ou plusieurs navigateurs, charge la page et permet de faire des requêtes XPath sur celle-ci. Très puissant, il peut par exemple vous permettre de vérifier la présence d'un lien dans une page. Ou encore de faire un screenshot de cette même page.

L'avantage étant que vu que c'est directement le navigateur qui est exécuté, tous vos javascripts le sont également.

Deux inconvénients sont à déplorer :

  • Sélenium implique d'avoir le serveur du même nom de lancé. C'est du java et c'est assez lourd.
  • Les seules requêtes que vous pouvez faire avec Selenium sont des requêtes XPath. Si vous n'en maitrisez pas correctement la syntaxe, cela peut se révéler problématique.

Le fait que Selenium soit un serveur a tout de même un avantage. Vous pouvez l'exécuter en même temps sur plusieurs machines sous divers systèmes d'exploitation. Et ainsi aisément automatiser les tests de cross-compatibilité entre navigateurs.

QUnit/JSpec

QUnit est le framework de test javascript de jQuery. JSpec est fortement inspiré de RSpec.

Il s'agit de deux frameworks de test en javascript. Vous écrivez vos tests dans ce langage, ils exécutent votre application et vous signalent si vos tests passent ou non. Tester son code javascript avec QUnit est en soi assez simple.

Dans la pratique, c'est un petit peu plus problématique. En effet, pour exécuter vos tests, vous devez charger une page. Vous devez donc charger autant de pages qu'il y en a dans votre application. Vu que votre code javascript doit être exécuté, vous ne pouvez pas vous contenter de faire un curl sur chacune de ces pages et d'en vérifier le contenu. Il faut donc coupler Selenium à votre framework de tests afin de pouvoir exécuter ceux-ci. Nous en revenons à la problématique précédente avec la dépendance à un serveur java assez lourd.

Johnson / Harmony / Holygrail

Et il y a deux mois, miracle ! Je suis tombé sur johnson. Désolé pour les développeurs non ruby, ça devient spécifique au plus beau langage informatique la ;)

Cette librarie écrite en ruby utilise SpiderMonkey afin d'exécuter du code javascript. Vous pouvez donc faire :

Johnson.evaluate("4 + 4")
La fonction retournera 8 et c'est bien SpiderMonkey qui exécute la chose en tant que javascript.

A johnson, nous ajoutons une couche pour le DOM : harmony. Cette librairie utilise johnson pour exécuter le javascript. A cela elle ajoute la gestion du DOM HTML. elle vous permet de charger une page HTML ainsi que tous ses javascripts ... et de l'exécuter ! Ainsi vous pouvez même charger jQuery ou toute autre librairie et utiliser ses fonctionnalités dans vos tests !

Exemple :

page = Harmony::Page.new(<<-HTML)
  <html>
    <head>
      <title>Hello World !</title>
    </head>
    <body></body>
  </html>
HTML</p>

<p>page.execute_js("document.title") #=> "Hello World !"
  <html>
    <head>
      <title>Hello World !</title>
    </head>
    <body></body>
  </html>
HTML</p>

<p>page.execute_js("document.title") #=> "Hello World !"

Cela commence à devenir intéressant ! :)

Enfin nous ajoutons une dernière couche car nous sommes dans une application rails : holygrail. Cette librairie utilise harmony et permet d'exécuter du javascript sur le contenu rendu par votre test ruby, que ce soit Test::Unit ou RSpec.

Tests unitaires

Voici un exemple de test avec RSpec :

require 'spec_helper'</p>

<p>describe MyController do  
  integrate_views
  include ActionController::Assertions::HolyGrail
  
  before(:each) do
    login_as users(:first)
    get :index
    response.should be_success
  end
  
  it 'should return a missing i18n string' do
    js("I18n.t('test');").should eql('test')
  end
  
  it 'should return an i18n string' do
    js("I18n.t('save')").should eql('Sauvegarder')
  end
end

Pour que vos tests soient correctement exécutés, vous avez besoin de vos vues dans les tests du contrôleur. Pour cette raison, nous devons exécuter la fonction integrate_views lors de l'instanciation de la classe.

Ici, nous testons une librairie de traductions de chaines de caractères en javascript. Nous récupérons le contenu de notre page afin d'avoir toutes nos librairies javascript. Et en conservant ce contexte, nous vérifions que telle ou telle fonction retourne la valeur appropriée.

Vous pouvez tester ces choses en dehors de Ruby en utilisant la console Firebug.

Nous pouvons donc déjà maintenant tester correctement notre javascript. L'avantage qui a, dans mon cas, permis de faire ce choix, c'est que les tests sont écrits en Ruby et sont donc présent avec tous vos autres tests. Vous ne faites plus de différenciation entre les tests de votre code ruby et ceux du code javascript. Et il n'y a pas deux méthodes différentes pour exécuter ces tests. Lorsque vous lancez ceux-ci, tous sont lancés.

Tests ajax

Seule, cette solution a cependant une limite assez énorme : vous ne pouvez pas faire d'appels ajax. En effet, lorsque vous exécutez vos tests, il n'y a aucun serveur web de lancé. Vos appels ajax vont donc échouer lamentablement en tapant dans des erreurs dns.

Dans la pratique, c'est un petit peu différent car harmony est intelligent et redirige lui même tous vos appels ajax. Si vous appeller /test, ce n'est pas sur http://test.host/test que la requête sera faite. Mais sur file:///test. Cela a cependant ses limites. Allez vous amuser à mettre toutes vos fixtures de test à la base de votre système rien que pour les beaux yeux de votre application ;)

Moqueur

Ne trouvant pas de solution à ce niveau, j'ai donc décidé de créer la mienne ! Et cela donne moqueur. Son nom est un chouilla équivoque. Moqueur permet de mocker vos appels ajax jQuery.

Moqueur ne supporte que jQuery (et il n'est pas prévu de support de javascript natif, ni d'autres frameworks). Inutile donc de l'utiliser sans.

Reprenons notre url /test de tout à l'heure. Vous faites un appel ajax sur cette url :

jQuery.ajax({url: '/test'});

La valeur que doit vous retourner cet appel ajax est une string "Hello World". Mockons donc cette requête.

jQuery.mockAjax({
    url: '/test',
    content: "Hello World !"
});

Badoum ! Plus aucune requête ajax ne sera faite sur cette URL. Le contenu retourné sera toujours "Hello World !".

Intégration à Rails

La problématique est maintenant : ou, quand et comment j'intègre cela dans mon application rails ? En effet vous ne voulez pas mocker vos appels ajax pour vos utilisateurs.

Dans mon application, nous utilisons un dérivé de la stratégie de bundle javascript de github. En conséquent en faisant, dans ma vue :

<%= javascript_bundle "jquery" %>
Tous les fichiers javascript du dossier public/javascripts/jquery seront inclus.

Il suffit donc simplement de vérifier que l'on est bien dans l'environnement de test. Et si c'est le cas, j'inclue tous mes javascripts de test : moqueur et tous les mocks qui vont avec.

<%= javascript_bundle 'test' if Rails.env.test? %>

Zou, y'a plus qu'à écrire des tests ! :)

Evolutions de moqueur

A l'heure actuelle, moqueur est assez primitif. Il réponds toujours la même chose pour une même requête quelque soit la méthode utilisée, le type demandée et les paramètres transmis. Plusieurs évolutions sont cependant prévues et arriveront quand j'aurai le temps de les implémenter :

  • Mocker une requête en fonction de son type : GET, POST, PUT, DELETE
  • Mocker une requête en fonction des paramètres transmis (en GET ou POST)
  • Permettre à un mock de rendre une requête échouée

Ainsi que, potentiellement, les diverses améliorations que vous trouveriez utiles. N'hésitez pas à ouvrir un ticket à ce propos.

Vous pouvez également forker le projet, implémenter ces nouvelles fonctionnalités et me signaler ce que vous avez fait afin que je l'analyse et l'implémente.

Conclusion

J'ai cherché pendant assez longtemps une solution viable permettant de tester convenablement mon code javascript. J'en parlais d'ailleurs avec Jean Michel lors du dernier apéro Ruby à Lyon. Et la conclusion que nous avions eu était que à l'heure actuelle, c'était trop prise de tête.

Je ne pensais pas trouver une solution qui me convienne moins de deux mois plus tard ! :) Et vous, vous testez votre code javascript ? Comment ?

Commentaires

jpvincent
jpvincent dit: 25 mars 2010 00:00 Site web

salut

si je comprends bien, on se passe du browser pour tester JS car celui ci est interprété côté serveur ? du coup on prend le risque de passer à côté de bugs spécifiques aux browsers non ? est ce qu'avec cette solution JS peut modifier le DOM, et celui est testable par la suite ? quid des events type mouseover ou onsubmit après avoir appuyé sur entrée, sont ils émulables ?

Damien
Damien dit: 25 mars 2010 00:00 Site web

Effectivement on se passe du browser. Johnson utilise le moteur de Firefox. Donc si c'est un bug spécifique à Firefox, on tombera dessus. Pour les bugs spécifiques à IE et Chrome, on ne tombera pas dessus.

Il serait en revanche tout à fait envisageable d'exécuter le code avec V8 pour la cross compatibilité avec Chrome. Pour tester avec IE, il faudra se contenter de Selenium par contre.

C'est interprété en ruby. Pas réellement "côté serveur" car les tests étant interprétés en local, il n'y a pas de "serveur". Uniquement la machine qui exécute ces tests ;)

Damien
Damien dit: 22 avril 2010 00:00 Site web

Merci pour le commentaire du "PS". Je vais bientôt remplacer Wordpress par une application maison. Je rajoute la notification par email des commentaires dans ma todolist ;)

Johan BLEUZEN
Johan BLEUZEN dit: 22 avril 2010 00:00 Site web

Bonjour, Dans la partie sur "QUnit/JSpec", vous recommandez de coupler les tests unitaires avec Selenium. Pour ma part, j'utilise JSTestDriver qui permet d'exécuter toutes les suites de tests unitaires définie en JavaScript. Cela présente l'avantage de pouvoir industrialiser le développement JavaScript en automatisant l'exécution des tests quotidiennement.

Voila, cétait mes 2 cents...

PS : J'aime bien le site et le contenu (dans le Reader) mais il manque un bouton pour recevoir les nouveaux commentaires...

Postez un commentaire

Markdown activé