Tests unitaires : Pourquoi et comment
Introduction
« Les tests unitaires, c’est comme le Poker ou le Go : 5 minutes pour apprendre les techniques et toute une vie pour les maitriser » © Vivien FABING 2018
Les tests automatisés sont un axe d’amélioration classique lorsque l’on souhaite renforcer la qualité d’une application. Il existe toute une batterie de types différents de tests automatisés, ayant chacun leurs rôles et avantages/inconvénients.
Pour avoir une vision un peu plus globale de ces différents tests, je vous renvoie sur le premier article de Teddy concernant l’implémentation des tests d’intégration, et notamment sur son introduction où il explique la classique « Pyramide de tests ».
Je représente toujours les tests automatisés comme une sorte de « verrou », qui permettrait de « sécuriser » au fur et à mesure les développements qui ont été faits. Chaque type de test permettant de verrouiller différents niveaux :
- Les tests d’interface permettant de verrouiller des comportements d’une interface graphique,
- Les tests d’intégration permettant de verrouiller des fonctionnalités métier,
- Et enfin les tests unitaires qui permettent de verrouiller des choix d’implémentation de code et d’améliorer évidemment la qualité fonctionnelle, mais surtout la qualité de code
Pourquoi investir sur les tests unitaires ?
Outre leur aspect « verrouillage » sécurisant, les tests unitaires sont également particulièrement appréciés pour :
- Leur maintenance « simple » d’un point de vu intellectuel (dans le sens où l’on ne perd pas une ou plusieurs demi-journées à déboguer un test et l’environnement sur lequel il est exécuté). En effet les tests unitaires devant être parfaitement isolés, ils ne sont censés échouer que pour 2 raisons uniquement : soit une régression a été introduite, soit la fonctionnalité a évolué et il est nécessaire de mettre à jour le code du test unitaire. Pas de problématique de perte de temps en débogage sur problèmes de configuration d’environnement, de logiciel pas à jour, de jeu de données pas propre, etc.
- Leur faculté à encourager les développeurs à produire du code plus maintenable dit « SOLID» ( https://fr.wikipedia.org/wiki/SOLID_(informatique) ) : Utilisation d’interfaces, principe d’inversion des dépendances, etc. Ces nombreuses pratiques sont naturellement appliquées lors de l’écriture de tests unitaires parce qu’elles permettent d’en faciliter leur écriture.
- Leur aspect pratique pour déboguer un morceau de code précis sans devoir exécuter l’application complète : pas besoin de mettre un point d’arrêt, puis d’attendre que l’application se lance, pour ensuite naviguer jusqu’au bon endroit dans mon application et enfin accéder au code à déboguer !
- Et évidemment pour leur vitesse d’exécution, permettant de compléter facilement plusieurs milliers de tests unitaires en quelques minutes (à chaque Build d’intégration continue bien entendu 😊)
Cependant, la plupart des bénéfices des tests unitaires apparaissent plus visiblement dès lorsqu’ils sont couplés avec une pratique de TDD (Test Driven Development). Ce dernier correspondant bien entendu au fait d’écrire le code des tests unitaires avant d’écrire le code de la fonctionnalité correspondante.
Le fait d’expliciter le résultat attendu au travers du test unitaire rajoute d’autres avantages, notamment le fait :
- De plus se focaliser, en explicitant un objectif simple et défini, et de moins divaguer dans diverses idées qui pourraient nous arriver aléatoirement à l’esprit lors d’une méthode de coding plus « exploratoire »
- De s’assurer naturellement que le code du test unitaire ne sera pas inutilement complexe. Un reproche assez classique des développeurs qui implémentent des tests unitaires après avoir écrit le code métier est que le code de leur test unitaire est bien souvent trop complexe. C’est généralement la partie « Arrange » du test unitaire, correspondant au setup nécessaire pour pouvoir se mettre dans le contexte de test de la méthode qui est la plus touchée (Le terme « Arrange » est tiré d’une méthode classique d’écriture de tests unitaires, provenant des « 3 A » soit « Arrange, Act, Assert » https://msdn.microsoft.com/en-us/library/hh694602.aspx#Anchor_3 )
- De resservir plus tard de documentation vivante et à jour. L’exemple que je cite régulièrement pour illustrer ce sujet : En tant que développeur, il arrive parfois que l’on ne se souvienne plus exactement de comment fonctionne une méthode du Framework .NET. Pour cela un des premiers réflexes est de se référer directement à la documentation en ligne de Microsoft. Une fois arrivé sur la page, je pense que vous avez le même réflexe que tout le monde, scroller le gros pavé de texte explicatif de la méthode, pour arriver directement sur le morceau de code qui illustre comment se servir de la méthode concernée. Les tests unitaires permettent également d’illustrer comment se servir de votre code !
Les trucs qui marchent avec les tests unitaires
Comme évoqué durant l’introduction de cet article, l’implémentation de tests unitaires n’est « techniquement pas complexe », mais reste selon moi le type de test qui importe un changement dans la culture et la méthode de travail le plus difficile à pérenniser.
Comme le vieil adage le rappelle, c’est en « forgeant qu’on devient forgeron ». Pour les tests unitaires c’est la même chose. Et je rajouterai également que si pour certains développeurs aguerris, l’écriture de tests unitaires est suffisamment maitrisée pour faire en sorte que cela ne devienne pas un centre de coût, il faut s’attendre à un coût d’entrée non négligeable lorsque des personnes plus débutantes sur le sujet se retrouvent confrontées à la pratique. Néanmoins, cela reste un investissement qui me semble inestimable sur le long terme, et sur lequel je recommande de foncer au plus vite.
Mais le vrai levier qui peut vraiment faire en sorte que la pratique se généralise, c’est de s’appuyer sur un ou plusieurs développeurs déjà convaincus et familiers avec les tests unitaires, et qui sauront, si l’on leur donne les moyens au travers de « Code Review », de « Pair programming », ou encore de « Coding Dojo », convaincre leurs collègues et étendre la pratique.
Le Coding Dojo est notamment une pratique que je recommande et qui se pratique sous le format une équipe de développement + « une salle, un écran et un seul clavier ». C’est l’occasion de montrer du code d’une application et de discuter ensemble de la meilleure manière d’améliorer la qualité de ce code, notamment via les tests unitaires. C’est généralement l’occasion rêvée pour les développeurs les plus experts de partager leurs techniques, et d’entrainer avec eux leurs collègues.
Rappel de la démarche et conclusion :
En conclusion, quelques rappels qui peuvent sembler « évidents » mais que je préfère répéter :
- Les tests unitaires doivent être écrits par des développeurs (écrire des tests unitaires demande des connaissances sur les détails d'implémentations, que les testeurs fonctionnels n'ont généralement pas).
- Ils sont écrits idéalement en TDD (écriture du code de test avant écriture du code de la fonctionnalité), en prenant bien soin d’isoler le test unitaire du reste du monde via différents Framework de Mock tel que Moq ou encore FakeItEasy https://fakeiteasy.github.io/ (Qui pourront faire l’objet d’un prochain article, notamment par Thomas, mainteneur de FakeItEasy 😊)
- Ils amélioreront assurément la qualité fonctionnelle, mais surtout la qualité du code, et notamment sa maintenabilité
- Il est pratiquement impossible de calculer le ROI de la mise en place de tests unitaires (car il faudrait être capable de réaliser 2 fois un même projet, une fois avec tests unitaires et une fois sans, puis comparer les résultats), mais l’impact sur le confort de développement, ainsi que sur la qualité du code produit ne sont pas à négliger, sans oublier la potentielle diminution des retours de bug, bien entendu !
Commentaires