/ Articles
Comment bien tester une application frontend en 2025 ? 🧪
L'automatisation des tests
Une application robuste est avant tout une application bien testée : pas de crash inattendu ou de régression entre chaque déploiement.
L'erreur étant humaine, l'industrie a su progressivement développer des outils pour automatiser ces tests et se passer des vérifications manuelles, fastidieuses et peu fiables. Les tests automatisés sont dorénavant un maillon essentiel dans n'importe quelle CI/CD digne de ce nom et leur succès un préalable requis à toute passage en production.
L'approche pyramidale
Mais la mise en place de tests dans une application complexe emporte aussi avec elle son lot de pièges et d'arbitrages techniques. Car on ne sait jamais trop par où commencer, ni ce qu'on doit tester exactement... La fameuse « pyramide de tests » popularisée par Mike Cohn et reprise par Martin Fowler sert souvent de feuille de route :
Elle se décompose ainsi :
👉 BEAUCOUP de tests unitaires simples et rapides (fonctions métiers, utilitaires, fonctions pures etc.)
👉 QUELQUES tests d'intégration à plus haut niveau (composants avec leurs dépendances)
👉 SI POSSIBLE des tests end-to-end lents mais plus réalistes car simulant un navigateur réel
Dans les faits, on rencontre surtout des applications bardées de tests unitaires (pour celles qui ont des tests...). Ces tests sont généralement écrits après coup, sans stratégie globale, avec parfois quelques tests de composant qui surnagent au milieu — si on a de la chance ! 🫠
L'approche trophée
De plus, les outils et les frameworks ont évolué tendant à rendre cette approche pyramidale caduque. Kent C. Dodds par exemple a défendu une version révisée de la pyramide avec le « trophée de test » :
1️⃣ L'analyse statique
Au pied du trophée, on trouve d'abord une couche statique, forme de « test préventif », assurée par tous les outils de lint et de typage à notre disposition (ESLint, Biome, Typescript, SonarQube). Ces technologies permettent de détecter précocement des erreurs syntaxiques, des problèmes de type, des anti-patterns ou d'appliquer des conventions d'écriture pour assurer la qualité et la lisibilité maximales de votre code. Il ne s'agit pas de tests à proprement parler mais des sentinelles qui parcourent votre code pour lever régulièrement des alertes.
2️⃣ Les tests unitaires
Vient ensuite une base de tests unitaires pour tester notre logique en isolation. Jest ou Vitest (compatible avec Jest mais plus rapide) font ça très bien.
Si c'est un reflexe sain que de chercher à tester son code, encore faut-il se garder du travers de tout tester, y compris les fonctions les plus triviales. Sur-tester est certes moins dangereux que sous-tester, mais tout aussi nocif pour la maintenabilité du code et expose à bien d'autres problèmes. Des tests trop « pointilleux » qui s'attardent sur des détails internes aux classes ou fonctions cassent très facilement à la moindre refactorisation et finissent immanquablement par ralentir le développeur. Or des tests en surnombre et trop sensibles à un certain type d'implémentation favorisent l'inertie dans un projet car ils élèvent le coût de la réécriture (il m'a été donné de voir des tests de composant portant sur la présence de... classes CSS, tests qu'il fallait donc reprendre dès qu'on remaniait un tant soit peu le template ou le jsx du composant 😱 ). Inutile donc de fétichiser un certain pourcentage idéal de couverture de test comme le très ambitieux 100% : il faut apprendre à tester ce qui gagne à l'être et laisser en dehors ce qui ne le vaut pas quitte à être un peu en dessous. En somme, il faut privilégier la qualité des tests plutôt que leur quantité.
3️⃣ Les tests d'intégration
L'approche « trophée » met surtout en vedette les tests d'intégration de nos composants, ces briques essentielles des applications front modernes. Testing Library (anciennement React Testing Library) permet dorénavant de gérer cette partie à moindres frais. On peut même en coupler l'usage avec Storybook qui permet de générer une documentation interactive de nos composants, un outil particulièrement adapté si on construit un design system afin d'avoir tous nos composants sous les yeux dans tous les états et actions possibles.
La frontière entre test unitaire et test d'intégration est historiquement assez floue. Martin Fowler, dans la réponse qu'il a apportée à la forme « trophée » défendue par Kent C. Dodds, rappelle cette difficulté à différencier les deux notions et propose plutôt de distinguer les tests unitaires « solitaires » et les tests unitaires « sociaux ». Dans le cas où on souhaite tester un composant ou une classe isolément, on peut choisir soit de conserver les dépendances qu'il appelle (auquel cas le test est « social ») soit de les remplacer par des mocks ou des stubs (auquel cas le test est « solitaire »). Tous deux peuvent prétendre à l'appellation de test unitaire puisque il s'agit de tester une unité de code, ici une classe ou un composant, mais dans le premier on intègre à celui-ci ses dépendances réelles alors que dans le second on les simule. Pour Fowler, un test d'intégration de composant peut être vu comme un test unitaire « social ». Il ne s'agit donc pas vraiment d'un autre type de test mais d'une approche différente et complémentaire du test unitaire en réaction à l'utilisation excessive de mocks.
4️⃣ Les tests End-to-End (E2E)
Ces derniers, qui forment le sommet de plus en plus resserré du trophée, restent coûteux en temps de calcul et de maintenance mais offrent l'avantage de répliquer des comportements utilisateurs réels en faisant tourner sous le capot un navigateur pour interagir avec notre application. Ils demeurent le seul vrai moyen de tester l'expérience utilisateur complète, y compris les interactions avec des services externes. Playwright et Cypress sont les librairies de référence dans ce domaine.
Bref, que votre structure de tests ressemble à une pyramide, un trophée ou un Barbapapa, peu importe tant qu'on s'accorde sur un point : les tests (intelligents) ne sont jamais vos ennemis 🤗 Non seulement ils protègent votre application (et votre santé mentale) en lui évitant de partir en production avec des mauvaises surprises pour vos utilisateurs, mais ils documentent aussi votre code avec des cas d'usage et vous forcent à anticiper les cas limites qui peuvent survenir. Une attitude de développement guidée par l'écriture des tests a également plus de chances de vous alerter rapidement si vous faites fausse route dans votre implémentation. La blague court souvent parmi les développeurs, pour moquer la paresse que nous inspirent les tests, que tester, c'est douter. En réalité, bien tester, c'est ne plus douter !