Test unitaire sur du canvas HTML5

Test unitaire sur du canvas HTML5
Le canvas HTML5 permet de faire énormément de choses : afficher et manipuler des images, dessiner des formes, etc. Il est aujourd’hui surtout utilisé afin de faire de belles animations sur des sites internet ou encore de créer des jeux vidéos.
Quid des tests unitaires ?
 

Introduction au canvas

L’élément canvas HTML5 permet de créer des éléments graphiques en 2D ou 3D. Les avantages par rapport au logiciel de rendu graphique Adobe Flash sont que le canvas ne nécessite pas d’installation de plugins et que le langage de développement est le JavaScript. Il faut cependant avoir un navigateur assez récent (Il est utilisable à partir de IE 9) pour pouvoir l’utiliser correctement.
Les possibilités offertes par le canvas sont diverses et variées, voici d’ailleurs quelques exemples:

Je suis sûr qu’après avoir vu tout ça, vous avez envie d’utiliser ou de continuer à utiliser les canvas pour faire de jolies animations ou des jeux. Mais, qu’en est-il des tests unitaires et surtout comment faire pour que nos tests nous permettent de certifier que le rendu est conforme à nos attentes ? C’est ce que nous allons découvrir de ce pas.

Mise en pratique : le tic-tac-toe

Connaissez vous le tic-tac-toe (“Morpion” en français) ? Il s’agit d’un simple jeu où nous devons aligner 3 croix ou 3 ronds pour gagner. Ce mini-jeu très simple va nous permettre de découvrir comment faire des tests unitaires et surtout de nous assurer que le rendu est conforme à nos attentes. Voici les éléments dont nous avons besoin pour pouvoir commencer les tests :

  • Le jeu que vous pouvez découvrir en cliquant ici
  • Le fichier source disponible ici

Rassurez-vous, il n’est pas nécessaire de regarder et de connaître en détail les fonctions créées. Néanmoins, sachez que lorsque l’on développe sur du canvas, on distingue très vite que notre code peut se séparer en deux parties bien distinctes : la logique et le rendu. Nous allons voir comment tester ces deux parties.

Installation de l’environnement de test

Jest : l’outil de test à tout faire

Il est temps de mettre la main à la patte et mettre en place notre environnement de test. Pour ce faire, nous allons utiliser Jest. Cette librairie développée par Facebook est une librairie “Out of the box”, ce qui signifie que nous n’avons pas besoin d’installer d’autres librairies pour qu’il fonctionne et qu’il demande très peu de configuration. Jest est très complet car il encapsule des modules robustes et utiles tels que :

  • Karma pour l’exécution des tests
  • JSDom pour “mock” votre DOM
  • Istanbul pour connaître la couverture de vos tests

Pour installer Jest, il suffit d’exécuter cette commande sur votre projet :

yarn add --dev jest

Voilà, vous pouvez dorénavant commencer à écrire votre premier test :

// app.test.js
const App = require("./../App");
describe("App", () => {
  it("should be initialized", () => {
    const canvas = document.createElement("canvas");
    canvas.id = "canvas";
    canvas.width = 200;
    canvas.height = 200;
    const app = new App();
    expect(app.canvas).not.toBeNull();
  });
});

Ce premier petit test sert juste à s’assurer que notre application se construit bien et qu’il récupère bien le canvas que l’on a créé. Mais, si jamais vous exécutez ce test, vous allez rapidement vous retrouver avec cette erreur : “Cannot read property ‘getContext’ of null”.
Pourtant, l’élément canvas a bien été créé et “app.canvas” n’est pas null 
Mais rassurez-vous, ce problème est normal car JSDom mock uniquement la création d’un élément mais ne permet pas d’émuler les fonctionnalités du canvas. C’est pour cela que la fonction “getContext” n’existe pas. Il n’est donc, à première vue, impossible de tester les interactions avec les canvas. Heureusement, une librairie est là pour résoudre ce problème …

Node-canvas à la rescousse

Automattic a créé une excellente librairie pour palier à ce problème: node-canvas. Il s’agit d’un portage de la librairie Cairo Graphics.
Celui-ci étant développé en C, vous aurez besoin cependant de faire quelques commandes en plus afin que celui-ci fonctionne.
Par exemple, si vous êtes sur un mac, vous devrez exécuter ces deux commandes :

$ yarn add canvas
$ brew install pkg-config cairo pango libpng jpeg giflib

Si vous êtes sur un autre OS, je vous invite à consulter la page Github de node-canvas afin de connaître les commandes nécessaires à l’installation de cette librairie.
Un des avantages à utiliser cette librairie est que JSDom prend en compte cette librairie. Vous n’avez donc besoin de ne rien faire d’autre pour intégrer node-canvas à JSDom.
Si vous relancez le test à nouveau, celui-ci ne devrait plus avoir d’erreur.

Les tests unitaires

Les tests sur la logique

Cette partie des tests unitaires est la plus simple car il ne s’agit ni plus ni moins que de tests classiques.
Dans cette partie, vous devrez vous assurer que les principales fonctionnalités du morpion s’exécutent correctement : vérifier que votre tableau de données se met bien à jour, que les points se comptabilisent bien, etc.
Voici un exemple de test unitaire :

it("should return the cell info 0", () => {
   const cell = app.getCellInfo(0, 0);
   expect(cell).toBe(0);
});

Dans cet exemple, on s’assure que les fonctions qui permettent de récupérer les données depuis le tableau de données est fonctionnel.
Une fois que vous avez testé tous les cas possibles de vos fonctions logiques, il ne manque plus qu’à vous assurer que le rendu correspond à vos attentes …

Les tests sur le rendu

Voici la partie qui nous intéresse le plus. Ici, le but sera de s’assurer que le rendu est conforme à nos attentes dans les différents cas qui peuvent arriver. Dans un premier temps, vous devrez vous poser et réfléchir à tous les cas possibles :

  • L’affichage d’une croix au bon endroit
  • L’affichage d’un rond au bon endroit
  • L’affichage d’une ligne qui va traverser tous les pions qui ont permis de remporter la partie.
  • La suppression de l’affichage des pions lorsque l’on commence une autre partie.

Facebook a mis au point un outil magique qui nous permet de valider les différents points cités ci-dessus : les Snapshots.
Les snapshots sont des fichiers JavaScript qui vont capturer les informations que nous lui donnons et vont les comparer entre les nouvelles captures qui se feront à chaque test :

  • Lorsque nous lançons le test pour la première fois, les données capturées seront stockées dans un fichier JavaScript prévu à cet effet.
  • Lorsque nous lançons un deuxième test, les données nouvellement capturées seront testées avec les données stockées afin d’être sûr que les deux informations soient identiques.
  • Si jamais les informations ne sont pas identiques, les tests échouent. Il se peut que le nouveau résultat obtenu soit le résultat attendu. Pour cela, vous devez mettre à jour votre fichier de données Snapshots en entrant la commande suivante:
    jest -u

Voyons maintenant un cas pratique :

it("should render the board", (done) => {
  app.start();
  app.canvas.toDataURL(type, (err, base64) => {
    expect(base64).toMatchSnapshot();
    done();
  });
});

Et voilà ! Nous transformons le contenu du canvas en base64 afin de le comparer aux Snapshots. Le tour est joué !

Pour aller plus loin

Couverture de code

canvas-unit-testing_coverage
Un des avantages de Jest est son implémentation d’Istanbul, comme cité plus haut. Celui-ci permet de connaître la couverture de votre code. Ici, pas besoin de configuration ou d’installations de librairies en tout genre, il vous suffira de rajouter ceci dans la configuration de Jest dans le package.json :

"jest": {
    "collectCoverage": true,
    "coverageReporters": [
      "json",
      "html"
    ]
}

Et voilà !
Maintenant lorsque vous lancerez un test, un dossier “coverage” apparaîtra à la racine de votre projet et vous n’aurez qu’à double-cliquer sur le fichier “index.html” pour ainsi consulter la couverture de votre code. Afin d’obtenir plus d’informations, je vous invite à consulter la doc d’Istanbul ici.

Intégration continue avec Travis CI

Travis CI est un outil d’Intégration Continue. Son but est de build votre projet et de lancer les tests unitaires afin de s’assurer qu’il n’y ait pas de régression dans votre code. Pour l’utiliser, il suffit de vous connecter sur le site à l’aide de votre compte Github, vous n’aurez ensuite qu’à choisir les projets qui vont utiliser cet outil pour que le tour soit joué. Maintenant, il ne vous restera plus qu’à configurer le processus à l’aide du fichier “.travis.yml” que vous devrez créer à la racine de votre projet. Voici le contenu du fichier dans notre projet :

language: node_js
sudo: required
os:
  - osx
before_install: brew install cairo pango libpng jpeg && brew upgrade pkg-config giflib
node_js:
  - "7.0.0"
  • language: permet de définir le langage utilisé dans votre projet (ici, node_js)
  • sudo: permet d’activer le mode sudo (nécessaire si vous avez besoin d’installer des packages, ce qui est notre cas ici)
  • os: permet de choisir l’os d’exécution (vous avez le choix entre “osx” et “linux”)
  • before_install: permet d’exécuter des commandes shell avant l’installation de votre projet sur la machine virtuelle
  • node_js: permet de préciser la version de Node JS qui sera utilisé pour vos tests

REMARQUE: J’ai volontairement choisi le langage “osx” et non “linux” car sinon les tests ne fonctionneront pas. En effet, l’algorithme permettant de générer les canvas diffèrent entre la version OSX et Linux, ce qui fait que les Snapshots seront vraisemblablement différents (de l’ordre de quelques pixels de différence, invisibles à l’oeil nu).

Conclusion

Il est important de ne pas négliger les tests unitaires même si cela sert uniquement à tester le rendu visuel.
Le Snapshot de Jest est un outil très puissant qui ne se limite pas qu’au test sur du canvas. Il permet aussi de tester, par exemple, le rendu visuel de vos composants React. Si vous souhaitez en savoir plus, je vous invite à consulter cette page dédiée à l’utilisation des Snapshots sur React.
Bon développement à toutes et à tous 🙂