5 conseils pour écrire un bon test unitaire

Thomas Zuk
6 min readSep 4, 2022

--

Les processus d’intégration continue (CI) sont devenus omniprésents dans de très nombreux projets au fur et à mesure des années. Ces processus garantissent une certaine qualité ✅ à l’ensemble du code d’une application et préviennent des régressions de façon automatisée avant un quelconque déploiement sur un environnement.

Pour assurer le bon fonctionnement de ce processus, on utilise un ensemble de tests unitaires dédiés à garantir le bon comportement du code de l’application. Il est nécessaire que les pratiques de développement soient communes et cohérentes au sein d’une équipe de développeurs. Je vous livre dans cet article mes conseils en matière de tests unitaires ✅ afin d’en écrire plus et de manière plus correcte, mais sachez qu’il n’existe pas de façon universelle d’en écrire. Le plus important est que l’équipe soit en accord avec l’ensemble des principes définis par chacun de ses membres, en s’inspirant des pratiques qui leur correspondent. Les exemples, lorsqu’il y a besoin d’en donner, sont en Scala (et oui, c’est mon langage de prédilection !), mais certains principes s’appliquent également dans tous les langages !

Le but du test unitaire 🎯

Comme son nom l’indique, un test unitaire se doit de vérifier un scénario très précis sans tester beaucoup de variations du contexte en un seul test. L’idée est de tester unitairement le comportement d’une fonction avec un ensemble de paramètres donnés. Ensuite, écrire de nouveaux tests consistera à faire varier le contexte utilisé en entrée afin de vérifier un nouveau scénario. On peut également tester tous les cas particuliers en retour ou en entrée d’une fonction. Le but d’un test unitaire n’est pas de vérifier le comportement d’une grande chaine d’évènements très complexes, il est nécessaire de se concentrer sur tous les petits éléments qui peuvent être testés.

Avoir beaucoup de tests qui permettent de couvrir l’ensemble des scénarios de votre code est un très bon point de départ afin d’avoir un code fiable dans lequel on peut avoir confiance pour les déploiements. Il va de soi que les tests doivent être écrits avec le plus de précisions possibles afin d’éviter de laisser trop de possibilités à l’application de valider le test.

Le titre du test ✏️

Cela peut paraitre anodin, mais chaque test dispose d’un titre qui le qualifie et il ne faut pas le décider au hasard. L’intitulé doit être choisi avec précision afin que n’importe qui puisse comprendre le but du test. Votre code doit pouvoir être décrit grâce à ses tests unitaires, qui peuvent même servir de documentation. Si un nouveau développeur arrive sur le projet et lit l’ensemble des titres des tests unitaires, il comprendra rapidement ce que fait chaque morceau de code de l’application. Et même pour la personne qui a développé le test, si le titre est explicite, il n’aura aucun mal à comprendre le comportement de l’application dans tel ou tel cas et ce, même plusieurs années après avoir développé ce morceau de code !

Pour exemple, j’utilise généralement une syntaxe proche de celle-ci afin de nommer mes tests (il ne s’agit que d’un modèle bien évidemment, mais il est aisé de s’en inspirer) :

it should return <this result> Given configuration <with parameters>…

Les trois parties d’un test unitaire 📄

Un test unitaire est divisé en trois parties bien identifiables : le GIVEN, le WHEN et le THEN :

  • La section GIVEN constitue votre contexte, tous les paramètres d’entrée que vous allez utiliser pour tester un scénario particulier. Il est utilisé pour préparer le terrain afin que le test s’exécute exactement de la façon demandée. Comme vous pouvez le voir sur l’exemple, j’ai ici créé deux variables qui vont être utilisées comme arguments pour la fonction de la partie WHEN.
  • La section WHEN représente la fonction que vous voulez tester avec l’ensemble de ces paramètres. Je vous conseille de faire en sorte que cette section ne comporte qu’une et une seule ligne, ce qui indique précisément ce que vous souhaitez tester.
  • Enfin, la section THEN représente les assertions, c’est-à-dire le comportement auquel vous vous attendez pour que le test soit considéré comme valide. C’est dans cette partie que vous allez indiquer au programme ce qu’il doit vérifier, généralement en le comparant au résultat généré par la partie WHEN. Vous pouvez par exemple (comme ci-dessus) confirmer le nombre d’éléments contenus dans une collection, valider une chaine de caractère précise en sortie, vérifier que le résultat attendu est conforme à une expression régulière, qu’une méthode sous-jacente a été appelée, qu’un fichier a été créé, etc. Les possibilités ne manquent pas, tant qu’elles sont en mesure de confirmer un comportement précis.

En Scala : les matchers ✅

Le package ScalaTest contient de nombreux objets appelés Matchers qui sont très utiles pour les assertions (la partie THEN). En effet, leur richesse provient de la façon très explicite avec laquelle vous allez pouvoir écrire vos assertions et de la lisibilité que cela procure. Il existe des Matchers pour à peu près tous les cas d’usages :

  • myVal shouldBe myExpectedVal : ici, shouldBe est un Matcher permettant de vérifier une égalité (très explicite comme vous pouvez le voir)
  • myVal should fully match regex “myRegex” : on peut facilement comprendre que l’on vérifie qu’une variable correspond à une expression régulière
  • myList should contain theSameElementsAs List(“a”, “b”) : on vérifie aisément le contenu d’une collection avec ce type de Matcher
  • La liste est bien évidemment non exhaustive, retrouvez en une complète ici.

En Scala : pourquoi j’utilise FunSpec ? 🙊

Il existe plusieurs sous-classes de tests dans ScalaTest, mais celle que j’utilise le plus souvent est FunSpec. Il y a deux principales raisons à cela :

  • Le mot clé “it” permet de déclarer des tests et notamment d’avoir un terme qui s’insère correctement dans le titre du test (exemple : “It should return expected value Given valid configuration”)
  • Le mot clé “describe” permet de grouper des tests similaires entre eux ou de les rassembler par catégorie. Cela favorise notamment l’exécution de certains tests unitaires au sein d’un même fichier, qui sont ciblés sur la portion de code qui vous intéresse pour le développement en cours. Voici un exemple :

Pratiquer le TDD 👌

Le Test Driven Development est une pratique qui vous permettra (entre autres) d’approfondir la rédaction de tests unitaires. En effet, le but est ici de fonctionner par itération successive. Vous commencez par écrire un test décrivant le scénario souhaité et dans la plupart des cas, ce test échouera en premier lieu. Vous devez ensuite rédiger le code minimal de votre application qui permettra de valider le test précédemment écrit. J’insiste bien sur la notion de “minimal” dans le sens où votre code ne doit pas anticiper de scénarios trop compliqués qui pourraient vous induire en erreur et provoquer de “l’over-engineering”. Restez simple dans la résolution d’un test unitaire. Une fois celui-ci valide, vous pourrez passer à la phase de refactoring, afin d’améliorer votre code de production et votre code de test, en les rendant plus clairs, plus précis et plus explicite.

Une fois ce premier test validé, vous devez écrire un deuxième test et répéter la procédure. C’est cette itération dans les tests qui vous permettra de construire l’ensemble de votre fonctionnalité, et même votre application de façon générale.

Ces conseils ont été tirés de mes propres expériences sur le sujet. J’espère qu’ils vous ont apporté des éclaircissements sur la manière d’écrire des tests ! N’hésitez pas à me dire si ce post vous a plu et à me suivre sur Medium (et/ou vous abonner par email ✉️ afin de m’encourager) ! Je vous dis à bientôt dans un prochain article ! 👋

--

--

Thomas Zuk

Développeur Big Data, formateur et rédacteur de contenu sur les bonnes pratiques et le quotidien d'un Développeur Big Data !