close

Découvrez nos offres pour faire du digital le levier de votre croissance !

download
Modèles

Téléchargez le Guide Ultime de gestion de projet digitale pour vous aider à piloter vos transformations et faire les bons choix !

Image mise en avant pour l'article

La vérité sur les tests unitaires

Les tests unitaires sont souvent mal compris. Et de fait mal maîtrisés, mal aimés pour ne pas dire malmenés. Pourtant, ils ont contribué au succès de nombreux projets. Et leur absence a été la cause de bien des échecs.


Cette ignorance des tests unitaires n’est pas que du fait des développeurs. C’est dans toute la chaîne de valeur d’un projet digital que les tests unitaires – et par ailleurs des tests d’autres natures dont nous parlerons une autre fois – doivent trouver leur place. Une place de choix. Une place centrale.

Ces tests, il faut savoir les mettre en œuvre techniquement. Ces tests, il faut aussi savoir les intégrer à une méthodologie. Et ces tests unitaires, il faut savoir les « vendre ».

L’objectif de cet article est de démystifier les tests unitaires et de convaincre – si c’était encore la peine – de leur efficacité et de leur caractère incontournable.

 

Test unitaire : une définition s'impose

Un test unitaire (unit test en anglais et dans le jargon de l’ingénierie logicielle) a une définition bien précise. Il s’agit d’un élément de code source fait pour « tester » une « unité » de code source.

Par « unité » on entend un fragment de code source cohérent, dont le but est bien défini et « unique ». Ainsi, puisque les tests unitaires sont mis en œuvre dans une démarche de programmation objet, une « unité » correspond à une classe d’objet. Nous reviendrons plus loin sur la nécessité de tester par petite « unité », « brique par brique » et les implications dans le travail du développeur.

Il faut réaliser les tests unitaires petit à petit, brique par brique

Par « tester » on entend « vérifier le bon fonctionnement » de cette unité de code. Dans tous les cas, et dans tous les contextes d’exécution de cette unité de code. Nous sommes tous d’accord qu’une classe d’objet, qu’une méthode de cette classe, qu’une autre méthode de la même classe, doivent avoir un comportement, ou réaliser une action, conforme à ce que le développeur a voulu implémenter. Et de façon fiable à 100 %. Sinon, la classe, la méthode, sont buggées…

Alors pour donner une définition « choc » d’un test unitaire, on pourrait dire qu’il est un outil contre les bugs, contre les régressions, contre les effets de bord, trois ennemis de développeurs, trois ennemis des projets, et trois ennemis du retour sur investissement d’un tel projet.

 

Techniquement, comment marche un test unitaire ?

Une fois cela dit, une fois la lutte contre les bugs affirmée, il faut savoir comment on met en œuvre des tests unitaires dans un projet.

Nous l’avons déjà dit, les tests unitaires sont faits eux-mêmes de code source. On pourrait dire qu’ils sont un « méta-code source » du projet, puisqu’ils ne remplissent pas de rôle technique ou fonctionnel dans le projet. Ils sont juste là pour veiller à ce que le code source technique et fonctionnel, réalisé par les développeurs, soit testés.

Pour faire une analogie, si dans un chantier de construction, un mur de briques est une partie du « code source » du projet de construction d’un bâtiment, alors le fil à plomb, le niveau à bulle et les étais disposés par les maçons, sont les « tests unitaires » dans ce cas.

D’ailleurs si vous étiez architecte ou maître d’œuvre, laisseriez-vous les maçons monter les murs de votre maison sans fil à plomb ? Et donc, laissez-vous les développeurs coder sans tests unitaires ?

Les tests unitaires sont le fil à plomb pour réaliser parfaitement des sites Web

Les « outils » concrets pour les développeurs sont facile d’accès, qualitatifs (ils sont eux-mêmes testés unitairement ^^), et productifs. Il existe pour chaque environnement technologique des frameworks de tests unitaires. Souvent même livrés en standard avec le framework applicatif utilisé. Et possiblement contenus dans des « suites »(01) intégrant les autres types de tests, comme les tests d’acceptance (mais n’allons pas trop vite, on ne parle pour l’instant que de tests unitaires).

Ainsi puisqu’Adimeo travaille essentiellement sur des projets basés sur PHP (à travers Symfony bien sûr, mais aussi dans nos projets Drupal et Wordpress), c’est PHPUnit qui est l’outil essentiel de notre pratique des tests unitaires. Et pour bien faire, il est intégré à nos outils de productivité ainsi qu’à notre plateforme d’intégration continue.

 

Comment écrire un bon test unitaire ?

Une fois les bons conseils de principe donnés, et compris, il est temps d’être pragmatique et concret. Car écrire un test unitaire, puis un second, puis un autre, c’est ce qu’on attend d’un développeur au quotidien.

Affirmons-le d’emblée, écrire des tests unitaires s’apprend. Et comme pour toute chose, cela s’apprend en deux temps, par un enseignement, puis par la pratique. On peut à raison trouver la pratique moins intuitive que celle qui consiste à implémenter du code « métier ». Et il faut donc avoir la patience de progresser dans une courbe d’apprentissage toutefois « rapide ». Les bénéfices des tests unitaires sont à portée de tous les dév’.

Pour donner une première « clé » et peut-être la plus importante, un test unitaire doit être « simple ». Il faut garder à l’esprit que l’on doit tester des choses « unitaires » donc limitées dans leur portée.

Un test unitaire ne doit s’écrire qu’en quelques lignes et ne pas comporter une complexité affolante. Ou alors c’est un indice majeur que le code à tester ne correspond déjà pas, avant même d’avoir été écrit, aux bonnes pratiques de l’ingénierie logicielle. Comme celles de S.O.L.I.D., par exemple, dont le « S » initial correspond au « mantra » que devrait se répéter en boucle les développeurs : « keep it Simple », « faisons Simple ! ». Voilà, un bon test unitaire, c’est celui qui fait déjà réfléchir avant même d’écrire du code.

Les tests unitaires ne doivent s'écrire qu'en quelques lignes. Il faut donc faire simple

Ensuite, ce n’est plus qu’une question de bête syntaxe, celle du framework de tests choisis. Il fournit des classes et des fonctions pour écrire les tests. Il s’agit d’écrire des fichiers de tests que l’on place dans un dossier spécifique du projet, et qui sont des « compagnons » des autres fichiers de code source, en l’occurrence les classes techniques ou métiers à tester. Rien de compliqué, en quelques heures de lecture d’une documentation on comprend très vite comment faire fonctionner les tests unitaires dans son projet.

On comprend notamment comment écrire des assertions qui sont les éléments basiques d’un test unitaire. Une assertion c’est une « vérité » que l’on énonce et que le code source doit vérifier dans tous les cas où il est exécuté. L’art d’écrire des tests unitaires c’est surtout celui d’énoncer toutes les assertions attendues. Un développeur est capable d’être très exhaustif, car depuis des spécifications détaillées d’une fonctionnalité, il dispose de fait de toutes les assertions qui doivent se vérifier par le test. Mais personne n’étant parfait, si un bug survient un jour, il sera toujours temps d’ajouter l’assertion qui correspond à ce cas problématique et de compléter ainsi le test unitaire.

Écrire des bons tests unitaires, c’est aussi avoir le soin de tester intégralement chaque classe et chaque méthode du projet. C’est la garantie d’une couverture exhaustive du projet et donc d’un contrôle maximum. Les environnements de développement permettent d’industrialiser la couverture de test des classes, surtout pour certains aspects fastidieux (comme tester les getters et setters d’une classe). Si tester certains éléments du code peut paraitre inutile, car trivial, il faut garder à l’esprit que « le bug se cache dans les détails ».

La notion de mock object est essentielle aussi. Elle permet de créer des conditions de tests alors même que le projet ne fonctionne pas réellement. Cela permet d’alimenter le test en données, comme si elle provenait d’une base de données ou d’une saisie utilisateur. Une fois encore, l’apprentissage et la maîtrise de ces mocks sont à la portée de tous.

 

Une « nouvelle » façon de développer

Mettre en place des tests unitaires à toutefois un impact majeur sur la façon dont les développeurs travaillent. Cet impact n’est pourtant pas « douloureux » et n’implique pas une surcharge de travail. L’impact est uniquement méthodologique. En effet, la pratique veut que le développeur écrive le test unitaire avant d’écrire le code à tester. Le Test Driven Development (TDD) est ainsi une méthodologie de développement parfaitement formalisée dans ce but.

C’est souvent ici que certains perdent le fil et abandonnent avant même d’avoir essayé. On doit le concéder, cette fois encore ce n’est pas très intuitif. Ou plutôt, ça prive le développeur de son enthousiasme habituel. Et de sa capacité à entrer dans le vif du sujet en alignant au plus vite beaucoup de lignes de code qui vont (peut-être) produire un livrable, une fonctionnalité, un écran, un point d’API, opérationnels (peut-être) en un temps record (peut-être).

Non, avec les tests unitaires, il faut calmer ses ardeurs, prendre le temps de la réflexion et passer du temps à construire un échafaudage et à bien positionner les étais et les fils à plomb avant de poser la première brique du mur. Oui, on écrit le test, avant la fonctionnalité. Oui, il y a du temps à passer sur du code qui ne produira pas tout de suite un effet « concret ».

Certainement mieux vaut-il considérer cette démarche moins comme l’écriture d’un test (qui vérifie une conformité en bout de chaîne de production) plutôt que comme la transcription en code des spécifications fonctionnelles (et/ou techniques). Écrire un test unitaire c’est écrire une spécification. A laquelle le code source technique ou métier à implémenter va devoir être conforme.

Comprendre la valeur ajoutée des tests unitaires dans une pratique de développement, c’est comprendre que l’écriture d’un test permet au développeur de poser ses idées et sa compréhension de la fonctionnalité qu’il s’apprête à réaliser. Écrire un test unitaire permet par exemple de faire l’analyse très fine (à l’échelle de la ligne de code) des structures des données, de l’algorithmie, etc… et certainement plus concrètement qu’en posant cette même analyse sur un tableau blanc. Les tests unitaires sont immédiatement utilisables. Leur écriture est un temps utile de réflexion qui évite de nombreux problèmes par la suite.

 

Des coûts, des bénéfices, des faits

Les détracteurs des tests unitaires insistent sur les coûts de leur mise en place. S’il est vrai qu’ils nécessitent un temps d’écriture (et un temps de paramétrage de la plateforme de test), il faut pousser la réflexion jusqu’au bout. Malgré ces coûts, les tests unitaires n’ont-ils pas des bénéfices à la hauteur de l’investissement qu’ils représentent ?

La pratique démontre que leur mise en œuvre, au sein d’une équipe moyennement expérimentée mais maîtrisant les concepts fondamentaux (assertions et mocks), implique une vélocité moindre sur environ les 10 % à 20 % premiers pourcents du planning d’un projet. La vélocité devient ensuite équivalente à celle d’une équipe qui ne pratique pas les teste. Et, fait remarquable, la vélocité « de croisière » devient environ 20 %(02) plus rapide sur plus de 50 % du temps de développement(03). Pourquoi ?

Parce que la pratique des tests unitaires induit une nouvelle façon, pour le développeur, de faire l’analyse des spécifications du projet. La pratique des tests unitaires libère la réflexion et l’appuie sur une pratique cette fois très familière pour un dev : l’écriture de code, l’écriture du test unitaire. L’analyse se fait cette fois « les mains sur le clavier ». Quel développeur ne serait-pas totalement à l’aise dans cette position ? Cet effet sur la vélocité du développement est une vraie valeur ajoutée dans la phase d’implémentation.

L’effet bénéfique des tests unitaires sur la phase de recette est encore plus remarquable. C’est entre 30 et 50 % de bénéfices sur les temps autrement consacrés à des non-qualité ou non-conformités à la sortie de « l’atelier » des développeurs. Et des temps consacrés aux régressions (réduits parfois à un temps insignifiant). On peut consulter ici quelques faits intéressants sur le coût d’un bug à chaque étape d’un projet… L’illustration ci-dessous en est directement tirée.

Graphique des coûts des tests unitaires selon les étapes d'un projet

Au final, si l’on cumule les bénéfices tant en phase d’implémentation, de recette, puis de maintenance, ceux-ci sont bien supérieurs au coût qu’ils représentent, en termes d’investissement, en temps-hommes, et donc en euro. Leur valeur ajoutée est avérée dès les premières phases techniques d’un projet et ils deviennent alors un élément clé d’un retour sur investissement positif dans la globalité.

 

Conclusion : un premier pas dans un projet guidé par les tests

Les tests unitaires ont un aspect très technique. Toutefois, leur mise en œuvre ne requière pas de compétences particulières. Ils sont à la portée des tous les développeurs. Seule leur pratique peut paraitre déconcertante, et l’essentiel de l’apprentissage est méthodologique.

Dans une vision rationnelle, ils ne représentent pas de surcharge de travail pour une équipe de production. Ils peuvent avoir un impact sur la vélocité de développement à l’échelle d’un sprint. Mais à l’échelle du projet, ils n’ont pas d’impact (ou un impact bénéfique) dans la mesure où ils augmentent drastiquement la conformité de livraison, évitent les régressions, et garantissent donc les délais de recette d'un projet Web.

Résultat d'un test unitaire

«Unitaires », ces tests sont parmi les éléments fondamentaux d’un projet de développement réussi. Couplés avec d’autres types de tests (notamment les tests d’acceptance), ils garantissent le succès d’un projet sous bien des aspects : conformité, maintenabilité, évolutivité, délais de livraison et budget.

 

Sources :

  • (01) On peut citer Codeception pour l’environnement technologique PHP.
  • (02) Ce chiffre est variable selon l’évolution des fonctionnalités, des besoins clients et des users stories, qui nécessitent selon le cas de faire évoluer (et donc d’écrire) les tests unitaires en conséquence des évolutions techniques ou métiers.
  • (03) Une bonne introduction et vue générale du sujet - ici

 

Crédits photos :

  • Deagreez
  • Harmpeti
  • Ognianm
  • IvelinRadkov
Image mise en avant pour l'article
Arnaud Pagnier
Directeur Avant-Vente @ADIMEO
Webinar
Les tests : une garantie pour le ROI de vos projets web !
Voir le webinar !
Besoin d'une expertise technique pour votre projet digital ?
Adimeo et ses experts vous accompagnent dans votre projet.
Parlez-nous de votre projet !
Pourquoi s'abonner à
notre newsletter ?
Pour recevoir tous les mois, un condensé de contenus utiles et pertinents pour votre transformation digitale !