Commençons par le commencement : pourquoi accélérer l'exécution des tests ?

La première raison est ce qu'on appelle la boucle de feedback. En gros le temps qu'on va mettre à savoir qu'on a cassé des choses quand on en code. Très pratique de savoir qu'en modifiant quelques lignes qui nous paraissaient anodines on a cassé 3 fonctionnalités qu'on avait pas en vu. S'il faut 10-15 min pour exécuter l'ensemble des tests de l'application, peu de chance qu'on les lance souvent. S'ils tournent en moins d'une minute, on pourra sans doute les faire tourner en continue et regarder à chaque sauvegarde de notre code si on casse des tests et si c'est normal (au sens où on savait qu'on allait casser ces tests-là du fait de ce qu'on change) ou pas (par exemple si on casse une fonctionnalité à l'autre bout de l'application).

La seconde raison est la frustration de l'attente. J'ai souvent remarqué qu'on est peu motivé à lancer les tests si leur temps d'exécution est très long. Du coup on en fait le moins possible pour éviter que le temps se rallonge encore et encore, on les lance jamais du coup ils sont souvent incomplets et inutilisés…

Pour aller plus loin on pourrait même aller jusqu'à dire qu'avoir des tests qui s'exécute vite permet de faire du TDD, avec des tests très lents c'est très inconfortable donc peu de chance qu'on en fasse.

Dans le vif : comment on accélère nos tests ?

Avant de commencer, je fais une petite note sur ce qui va suivre : je vais surtout me baser sur mon expérience sur un gros projet Angular avec la stack de base donc du Karma/Jasmine. Sauf erreur, ce que je vais dire va pouvoir s'appliquer à tous les frameworks et toutes les stacks de tests.

Je vous le dis de suite : mettre en place ces points prend du temps, ce n'est pas quelque chose qu'on peut faire en un ou deux jours sur un gros projet, ça impose même parfois de construire différemment notre application. Donc ne soyez pas pressé, prenez le temps de bien comprendre les points pour les appliquer.

Pas de métier dans les composants

L'idée de base d'un composant front c'est d'afficher des éléments à l'utilisateur et recueillir ses actions. Donc aucun besoin d'y associer vraiment du métier pour que ça fonctionne. En tout cas peu de raison de le faire. C'est une considération de bonne pratique de code qui permet une meilleure ségrégation des responsabilités.

Séparer au maximum les composants des fonctionnalités métiers permet aussi d'accélérer fortement l'exécution des tests. En effet si on fait des tests de nos composants, on doit effectuer le rendu des composants, mais faire ce rendu est lent. Imaginez simplement tester en série des éléments métiers (disons 10-20 par composant) et faire autant de rendu que de test et ce pour chaque composant. On en vient vite à multiplier par 4-5 la durée d'exécution des tests à cause de ce rendu potentiellement inutile.

Si vous vous posez la question : par défaut en Angular tous les tests que vous ferez dans le fichier de test associé à votre composant vont faire un nouveau rendu du composant, peu importe ce que vous voulez tester, du fait de l'activation du TestBed qui crée un nouveau contexte à chaque test. Même en sachant ça, n'essayez pas de faire de la réutilisation des instances de composant, vous vous en mordrez les doigts à un autre moment.

Favoriser les fonctions pures et évitez les mocks

J'enfonce des portes ouvertes, mais vous devez savoir que c'est une bonne pratique et que c'est une bonne chose pour la qualité et la maintenabilité de votre code de faire des fonctions pures, non ?

Une raison qui pousse à favoriser les fonctions pures c'est leur testabilité : leur résultat ne dépend que des paramètres donc on a besoin d'aucun mock/spy.

Les mocks et les spy sont très pratiques. Je ne vous dis pas de les bannir, beaucoup de cas sont extrêmement simple à tester grâce aux mocks. Mais créer des mocks et des spy c'est assez lent (en plus de parfois rendre compliqué la lecture de votre test). En utiliser le moins possible c'est accélérer l'exécution de vos tests automatiquement.

Tester vraiment vos composants

Quand vous déportez le métier hors des composants vous verrez qu'on se retrouve assez vite avec des composants qui ne font pratiquement rien. En tout cas dont le rôle c'est afficher le contenu de leurs propriétés et appeler des fonctions quand l'utilisateur effectue une action. Dans ces cas-là vous pouvez vous passer des tests sur ces composants.

En Angular par exemple on le voit très bien aux composants qui contiennent aucun ngOnInit() (ni aucun autre lifecycle hook), uniquement des input/output. Dans ces cas-là soit on fait des tests très graphiques – mais au niveau des tests unitaires ça n'apporte pas grand-chose et c'est peu pratique – soit on ne fait que tester que les propriétés sont bien affichées et qu'on appelle bien nos fonctions quand on fait un clic. On en est pratiquement à tester le framework quand on y réfléchit…

Éviter les tests d'injecteur

C'est très spécifique à Angular je pense mais quand on fait grossir une application Angular on se retrouve assez vite avec des fichiers de tests qui ne teste rien du tout mais demande de la maintenance pour la partie TestBed et le mock de l'injection de dépendance. Si vous n'avez mis aucun test dans ce fichier, le seul test qui est présent par défaut va uniquement valider que le mock de l'injection de dépendance est correcte.

Pas certain qu'on souhaite perdre du temps à l'exécuter… Mais pas question de supprimer le fichier. On peut très facilement sauter l'exécution de ces tests (remplacer describe par xdescribe pour le describe général du fichier), dans le même temps ça permettra d'avoir déjà le squelette si un jour on a besoin de faire des tests à ce niveau (la flemme de créer un fichier de test peut toujours casser l'éland d'un de vos collègues un jour).

Se pencher sur le reporter

En général les frameworks de test proposent plusieurs reporter. Sous Karma/Jasmine pour un projet Angular on aura progress et kjhtml. Le premier va s'occuper d'afficher les tests dans la console, le second dans le navigateur.

Après quelques recherches, on s'était aperçu avec un collègue que le reporter progress est assez lent quand le volume de test augmente (en plus de créer un affichage assez moche sur nos CI). Pour corriger ça très simplement, il suffit de remplacer progress par le reporter mocha (qui est à la base prévue pour le framework de test mocha mais qui fonctionne parfaitement bien avec jasmine).

Pour replacer le reporter console "progress" par "karma-mocha-reporter", voici les changements à faire :

package.json

"devDependencies": {
+"karma-mocha-reporter": "^2.2.5",

karma.conf.js

- reporters: ['progress', 'kjhtml'],
+ reporters: ['mocha', 'kjhtml'],

On peut aussi observer ce genre de ralentissement avec d'autres reporters sur d'autres frameworks de tests. Une piste à creuser, car les reporter console sont souvent lents.

Conclusion

Parlons peu parlons chiffre. Ça donne quoi d'appliquer tout ça après plusieurs mois ?

Sur le gros projet Angular qui me sert de référence ici, en appliquant plus ou moins bien les points que j'ai listés ici, on est passé d'environ 3 à 4 minutes pour 400 tests à 45 à 55 secondes pour l'exécution de 1300 tests. Théoriquement c'est encore assez long mais déjà beaucoup plus exploitable.

On a aussi changé notre pratique de test travaillant beaucoup plus en TDD, pratique dont j'ai un peu forcé la mise en place au moment d'une très grosse refonte de la base de code, ce qui nous a permis de gagner en confiance sur notre code et en vitesse de développement sur les nouvelles fonctionnalités. Ce qui a été extrêmement bénéfique pour le projet.