Tester le code JavaScript de vos webapp Java

tester-code-javascript-webapp-logoVous développez une application web en Java. Le couche présentation est assurée typiquement par un framework MVC situé côté serveur : Spring MVC, Struts 2, Tapestry ou bien encore JSF.  Votre projet est parfaitement industrialisé : infrastructure de build sous maven, intégration continue, tests unitaires, tests Selenium, analyse qualimétrique via Sonar.

A priori, vous n’avez rien à envier à la richesse grandissante de l’écosystème JavaScript, de l’outillage et des frameworks MV* côté clients. Et pourtant, quelque chose vous manque cruellement. En effet, depuis que RIA et Ajax se sont imposés, votre application Java contient davantage de code JavaScript qu’il y’a 10 ans. S’appuyant sur des librairies telles que jQuery ou Underscore, ce code JavaScript est typiquement embarqué dans votre WAR. Pour le valider, les développeurs doivent démarrer leur conteneur web et accéder à l’écran sur lequel le code est utilisé. Firebug ou Chrome sont alors vos meilleurs amis pour la mise au point du script.

Ce code JavaScript n’est généralement pas documenté. Le tester manuellement demande du temps.  Les modifications sont sources d’erreur. Tout changement est donc périlleux. Si, à l’instar de vos tests JUnit pour vous classes Java, vous disposiez de tests JavaScript, vous en seriez comblés. Or, c’est précisément ce qu’il vous manque. Et c’est là où Jasmine et son plugin maven viennent à votre rescousse.

Présentation de Jasmine

Les développeurs AngularJS le connaissent déjà. Mis au point par Pivotal (ex SpringSource), Jasmine est un framework de tests unitaires pour JavaScript et CoffeeScript. Contrairement à QUnit qui est a été initialement créé pour tester le projet jQuery, Jasmine est indépendant de tout framework JavaScript. Son principal avantage réside dans le fait qu’il ne nécessite pas de navigateur pour exécuter les tests : un simple moteur JavaScript suffit.

Complet, Jasmine offre tout l’outillage nécessaire à l’écriture de tests :

Fonctionnalité Description Frameworks Java équivalent
Structuration des tests Suite de tests (describe), fonctions de tests (it), setUp et tearDown, ignore JUnit, TestNG
Matchers Fonctions utilisées pour les assertions : expect, toEqual, toBe, not, toBeDefined … Feist Assert, JUnit, Hamcrest
Bouchons Création d’espions et de simulacres: createSpy, andReturn, andCallFake, toHaveBeenCalled … Mockito

Vous expliquez ici comment écrire des tests avec Jasmine dépasse le cadre de cet article. Je vous renvoie à la documentation officielle et au tutorial Testing JavaScript using the Jasmine framework.

Dans la suite de cet article, les fichiers Player.js, Song.js, PlayerSpec.js et SpecHelper.js issus de la version standalone de Jasmine sont utilisés comme jeu d’exemple.
Pour les adeptes de jQuery, le projet jasmine-jquery étend Jasmine de 2 manières :

  1. Ajout de matchers liés au DOM : toBeSelected, toContainText, toHaveClass, toContainHtml, toBeVisible …
  2. Initialisation du DOM à partir d’un fichier HTML lors de l’étape de fixture.

Le fichier PasswordSpec.js en montre un exemple d’utilisation.

Intégration de Jasmine à maven

Le plugin pour maven jasmine-maven-plugin permet d’exécuter vos tests Jasmine (aussi appelés specs) lors de la phase de test de votre build maven.

tester-code-javascript-webapp-arboLe pom.xml du projet jasmine-test-webapp donne un exemple de configuration du plugin. Pour mieux comprendre sa configuration, vous présenter l’organisation du projet est nécessaire.

L’arborescence du projet suit les conventions maven d’un war. Pages dynamiques et ressources statiques se trouvent dans le répertoire src/main/webapp.

Par choix, Le code JavaScript spécifique à l’application est localisé dans le sous-répertoire static/js/app du répertoire src/main/webapp. C’est ce code qui doit être testé.
Les librairies tierces comme ici jQuery sont placées dans un sous-répertoire static/js/lib.

Les fichiers de tests JavaScript sont quant à eux placés dans le répertoire src/test/javascript. Pour utiliser l’extension jasmine-jquery dans les specs, la seule présence du fichier jasmine-jquery.js suffit.

La configuration associée du jasmine-maven-plugin est la suivante :

Configuration du jasmine-maven-plugin

Remarque : afin que l’objet $ soit défini lors du chargement du script Password.js, la librairie jQuery est chargée en premier.

Voici le résultat de la sortie console de la commande mvn test  :

[INFO] ------------------------------------------------------------------------
[INFO] Building JavaEtMoi Samples :: jasmine-test-webapp - war 1.0-SNAPSHOT
[INFO] ------------------------------------------------------------------------
...
[INFO] 
[INFO] --- maven-surefire-plugin:2.10:test (default-test) @ jasmine-test-webapp ---
[INFO] No tests to run.
...
[INFO] --- jasmine-maven-plugin:1.3.1.4:test (default) @ jasmine-test-webapp ---
[INFO] Executing Jasmine Specs
[INFO] 
-------------------------------------------------------
 J A S M I N E   S P E C S
-------------------------------------------------------
[INFO] 
password validation label
  should become red when password is too short
  should be green when password length is more then 6 symbols
Player
  should be able to play a Song
  when song has been paused
    should indicate that the song is currently paused
    should be possible to resume
  tells the current song if the user has made it a favorite
  #resume
    should throw an exception if song is already playing
Results: 7 specs, 0 failures
...
[INFO] ------------------------------------------------------------------------
[INFO] BUILD SUCCESS
[INFO] ------------------------------------------------------------------------
[INFO] Total time: 14.035s
[INFO] Finished at: Sat Mar 01 19:26:03 CET 2014
[INFO] Final Memory: 10M/247M
[INFO] ------------------------------------------------------------------------

Mise à part l’installation de Java et de Maven, aucun prérequis n’est nécessaire. Magique, vous ne trouvez pas ? Mais alors, comment est interprété le code JavaScript ? Et qui met à disposition le DOM manipulé dans le JavaScript ?
En plus de Jasmine, le jasmine-maven-plugin embarque HmtlUnitDriver, une implémentation de Selenium WebDriver. En interne, HmtlUnitDriver s’appuie sur HtmlUnit pour le DOM et sur Rhino pour le JavaScript. Tous les deux sont écrits en Java et sont Open Source. Initié par la fondation Mozilla, le moteur JavaScript Rhino a été intégré à Java 6.
Respectant le standard HTML, HmtlUnitDriver permet d’émuler des spécificités de certains navigateurs.
Jasmine-maven-plugin permet de tirer parti cette fonctionnalité. A sa configuration, il est possible d’ajouter la balise suivante : <browserVersion>FIREFOX_17</browserVersion>

Behavior-Driven Development

Le plugin jasmine-maven-plugin permet de développer en TDD sans avoir à lancer un clean test à chaque changement de code.
La commande mvn jasmine:bdd  lance un serveur web qui scrute tout changement dans le répertoire du code JavaScript et des tests JavaScript. Rafraichir la fenêtre de son navigateur permet de réexécuter les tests.

tester-code-javascript-webapp-bdd

Couverture de tests

A présent que jasmine-maven-plugin est en place sur votre projet, vous pouvez en profiter pour générer à moindre coût la couverture du code JavaScript testé. En effet,  ce plugin s’interface avec le saga-maven-plugin.
Par la ligne de configuration suivante, on indique au jasmine-maven-plugin de ne pas arrêter le serveur web une fois les tests unitaires exécutés :

<keepServerAlive>true</keepServerAlive>

La configuration du saga-maven-plugin est triviale et très bien documentée :

Configuration du saga-maven-plugin

Des rapports Cobertura et HTML sont générés dans le sous-répertoire target/coverage pendant la phase verify de maven :
tester-code-javascript-webapp-saga

Utilisation de PhantomJS

Le principal inconvénient de la solution présentée jusqu’ici est que ni HtmlUnit ni Rhino ne sont  utilisés par un quelconque navigateur du marché. Comment être certain que votre code s’exécute sur un Chrome ou un Safari ? C’est là que PhantomJS rentre en jeu. En effet, PhantomJS est un navigateur headless (sans interface graphique) basé sur WebKit (le moteur HTML de Safari, d’Opera et du fork de Chrome). Le plugin Jasmine pour maven permet d’utiliser PhantomJS à la place de HtmlUnit.

Pour utiliser PhantomJS, il est tout d’abord nécessaire de l’installer. Une archive existe pour chaque OS supporté : Windows, MacOSX, Linux 32 bits et 64 bits. Afin que maven puisse installer l’archive PhantomJS de l’OS sur lequel le build est exécuté, il est possible d’uploader ces archives dans votre repo d’entreprise (ex : Nexus, Artifactory). Voici un exemple de commande maven :

mvn deploy:deploy-file -DgroupId=org.phantomjs -DartifactId=phantomjs -Dversion=1.9.7 -Dpackaging=zip -Dclassifier=windows -Dfile=phantomjs-1.9.7-windows.zip -DrepositoryId=javaetmoi-cloudbees-release -Durl=https://repository-javaetmoi.forge.cloudbees.com/release/

Une fois déployées, les archives sont disponibles dans le répertoire org/phantomjs/phantomjs/1.9.7/

Le goal unpack du plugin maven-dependency-plugin permet d’installer PhantomJS dans le répertoire target/ pendant la phase initialize de la commande mvn test  :

<plugin>
  <groupId>org.apache.maven.plugins</groupId>
  <artifactId>maven-dependency-plugin</artifactId>
  <executions>
    <execution>
      <phase>initialize</phase>
      <goals>
        <goal>unpack</goal>
      </goals>
      <configuration>
        <artifactItems>
          <artifactItem>
            <groupId>org.phantomjs</groupId>
            <artifactId>phantomjs</artifactId>
            <version>1.9.7</version>
            <type>zip</type>
            <classifier>windows</classifier>
            <outputDirectory>${project.build.directory}</outputDirectory>
          </artifactItem>
        </artifactItems>
      </configuration>
    </execution>
  </executions>
</plugin>

Une autre solution permettant d’installer automatiquement PhantomjS consiste à utiliser le plugin PhantomJS pour maven.

Enfin, pour substituer PhantomJS à HtmlUnit, le jasmine-maven-plugin doit être configuré de la manière suivante :

<webDriverClassName>org.openqa.selenium.phantomjs.PhantomJSDriver</webDriverClassName>
<webDriverCapabilities>
  <capability>
    <name>phantomjs.binary.path</name>
    <value>${project.build.directory}/phantomjs-1.9.7-windows/phantomjs.exe</value>
  </capability>
</webDriverCapabilities>

Lorsque des tests échouent, les erreurs remontées par PhantomJS sont, de manière générale, plus compréhensibles que leurs homologues HtmlUnit.
Voici un exemple de message d’erreur lorsqu’on oubli de rajouter la librairie underscore. js et que la variable globale _ n’est pas définie :

Avec HtmlUnit :

[ERROR] java.lang.RuntimeException: org.openqa.selenium.WebDriverException: com.gargoylesoftware.htmlunit.ScriptException: TypeError: Impossible dappeler la méthode "{1}" de {0} (script in http://localhost:3213/ from (6, 34) to (15, 12)

Avec PhantomJS :

\[WARNING\] JavaScript Console Errors:
&nbsp; * TypeError: 'undefined' is not an object (evaluating '_.each')

Le pom.xml utilise les profiles maven pour utiliser à la demande PhantomJS. Il installe la version de PhantomJS correspondant à l’OS sur lequel la commande maven est exécutée. Le profile phantomJS permet d’exécuter les tests avec PhantomJS :
mvn test -PphantomJS

Conclusion

Au travers de ce billet, nous avons vu comment intégrer l’exécution de tests unitaires JavaScript dans une infrastructure de build basée sur maven. L’intégration de Jasmine avec maven est telle qu’il n’est nullement nécessaire de mettre en place des outils du monde JavaScript basés sur Node.JS tel Grunt ou Gulp.
Avec le plugin Jasmine pour maven, vos tests unitaires peuvent aussi  bien tester des fonctions métiers que des fonctions manipulant le DOM du navigateur. En outre, l’intégration de PhantomJS permet de se garantir que le code JavaScript testé fonctionnera sur les navigateurs basés sur WebKit.

Vos tests unitaires JavaScript peuvent dès à présent intégrer votre plateforme d’intégration continue. Preuve en est, les tests unitaires du projet jasmine-test-webapp ont été exécutés par Travis CI  et par le Jenkins de CloudBees.

1 thoughts on “Tester le code JavaScript de vos webapp Java

Laisser un commentaire

Votre adresse e-mail ne sera pas publiée. Les champs obligatoires sont indiqués avec *

Ce site utilise Akismet pour réduire les indésirables. En savoir plus sur comment les données de vos commentaires sont utilisées.