Il y a 15 ans -
Temps de lecture 4 minutes
Les 10 commandements des tests unitaires

Les tests unitaires ne sont pas qu’une bonne pratique des méthodes agiles, ils sont un véritable pré-requis à la mise en place d’un développement itératif.
Le refactoring et la modification d’une base de code existante, bien que facilités par les environnements de développement actuels, comportent un évident risque de régression, en partie couvert par les tests unitaires.
Vous trouverez ci-dessous nos 10 commandements des tests unitaires.
- Un test unitaire doit être véritablement unitaire. La classe testée doit l’être en isolation complète, afin de ne tester qu’une classe (et une méthode) à la fois, le SUT (System Under Test).
- Si une classe est difficile à tester, il est temps de faire du refactoring. Est-elle trop volumineuse ? Présente-t-elle trop de dépendances ? Profitez de l’occasion pour la découper et déplacer du code dans des classes annexes.
- Un test unitaire doit s’exécuter le plus rapidement possible afin d’avoir un retour quasi immédiat. Il faut donc proscrire l’accès à des fichiers, des bases de données ou des services externes dans un test unitaire. Un test qui dialogue avec une base de données n’est pas un test unitaire, c’est un test d’intégration.
- Le code d’un test unitaire fait partie du code applicatif. Il doit donc, à l’image du reste de l’application, respecter des conventions de code. Chouchoutez vos tests unitaires, faites du refactoring sur leur code, respectez les bonnes pratiques, présentez le code à vos collègues, codez les tests en pair programming, sans quoi on trouvera certainement dans votre application des tests smells, des tests unitaires peu lisibles et difficilement maintenables. De bons tests unitaires doivent permettre à leur lecture de comprendre le comportement du SUT.
- Isolez les dépendances de la classe testée grâce à l’injection de dépendances.
- Ne testez qu’un comportement à la fois. Soyez raisonnable et gardez le test simple, lisible et concentré sur ce comportement.
- Pensez à utiliser un framework de mocks pour injecter les dépendances sous forme de bouchons. Ces outils permettent de respecter le commandement n°1. Ne bannissez pas pour autant les bouchons codés à la main ; ils peuvent parfois rendre le test unitaire plus simple et plus lisible qu’avec un mock provenant d’un framework.
- Identifiez précisément les étapes setup, exercise, verify, teardown dans votre code. On retrouve ces quatre étapes dans tout test unitaire.
- Ne vous concentrez pas sur une couverture de code à 100%. Ce n’est qu’un indicateur technique, qui doit être examiné dans le contexte de l’application, et qui ne prouve en rien la qualité du code et des tests unitaires.
- Ne développez pas vos tests unitaires « plus tard ». Si vous n’utilisez pas l’approche TDD, développez-les le plus tôt possible.
Les deux premières règles sont incontournables.
Ne tester qu’une classe à la fois facilite la maintenance et la correction des tests unitaires, et permet de se concentrer sur le comportement de la classe à tester. Quand un test unitaire échoue, on sait exactement d’où provient l’erreur (elle est soit dans le test unitaire si le comportement du SUT a changé, soit une modification du SUT a cassé son comportement attendu).
Si vous développez votre code sans penser aux tests unitaires que vous ferez « plus tard », il sera difficilement testable et vous perdrez un temps précieux pour corriger les erreurs de design. « Design for testability », en particulier, utilisez au maximum l’injection de dépendances, qui est obligatoire pour l’injection des bouchons. Si vous utilisez Spring ou un autre framework d’injection de dépendances, cela ne devrait pas être trop compliqué …
Pour aller plus loin, nous vous recommandons vivement la bible xUnit Test Patterns, de Gerard Meszaros, dans laquelle vous retrouverez une présentation sur l’automatisation des tests, un catalogue de « test smells », et une liste complète de « test patterns », des meilleures pratiques de tests unitaires qui ont émergé ces dernières années.
Enfin, vous trouverez également sur les blogs suivants une liste de commandements:
- 10 rules of (unit) testing de Szczepan Faber, auteur du nouveau framework de Mocks qui monte, Mockito
- Ten T.D.D. Commandments de Cliff
- Ten commandments of unit testing de Merlyn Albery-Speyer
- Ashcroft – Ten Commandments of Unit Tests de Aslak Hellesøy et Shane Duan, une des premières tentatives sur le sujet, hélas interrompue après le quatrième commandement.
Commentaire
4 réponses pour " Les 10 commandements des tests unitaires "
Published by mat , Il y a 15 ans
Bonjour,
pour un projet qui est lié à une base de donnée, comment se passe la partie test avec la base de donnée ?
vous parlez de test d’integration…
pourriez vous en dire un peu plus?
Cordialement,
Mathieu
Published by Guillaume Carre , Il y a 15 ans
Bonjour,
un des intérêts soulignés ici d’avoir des tests réellement unitaires est leur rapidité d’exécution.
Lorsqu’un test unitaire est cassé il est important de le savoir au plus vite, afin de corriger l’anomalie au plus tôt. En particulier lorsque l’on utilise un serveur d’intégration continue, si le build dépasse la dizaine de minutes ça peut devenir rapidement désagréable…
Dès que l’on introduit l’utilisation d’une base de données dans les tests, ils vont prendre plus de temps à s’exécuter (on peut peut-être utiliser une base de données en mémoire, comme hsqldb, mais ça n’élimine pas totalement le problème).
Il est donc recommandé de séparer ces tests des tests unitaires, en les plaçant par exemple dans un module dédié de tests d’intégration. Ce qui permet ensuite de ne les jouer que pour les nightly builds par exemple, ou seulement quelques fois par jour, et non à chaque modification du code.
Pour aller plus loin sur les tests qui dialoguent avec la base de données, il existe plusieurs outils:
– HSQLDB est une base de données 100% Java, qui dispose d’un mode « stockage en mémoire »
– DbUnit permet de replacer la base dans un état connu entre chaque test
– Unitils est une librairie qui facilite l’utilisation de DbUnit (entre autres) avec JUnit
Guillaume Carré (Xebia)
Published by Jean-Christophe Sirot , Il y a 8 ans
J’ajouterais bien un 11e commandement. Alors certes d’une certaine façon il découle des autres mais comme souvent « ça va toujours mieux en le disant » :
11. Un test unitaire doit être reproductible. Le même test passant sur la même base de code doit toujours donner le même résultat.
Cela passe par l’utilisation de mocks/stubs pour les dépendances (appels à des web services, bases de données…) mais cela comprend également des choses auxquelles on ne pense pas immédiatement telles que :
– l’heure à laquelle le test est lancé. Par exemple on peut avoir des calculs faux sur des différences entre deux dates quand le test est lancé un 28 février, ou lors du passage à l’heure d’hiver ou d’été, lors de l’ajout, de la suppression d’une seconde intercalaire
– la timezone
– la locale. J’ai recontré le cas d’un logiciel où la moitié des test échouaient quand la locale était « TH-th »
– l’architecture de la machine. Pensez à l’endianness à la taille des entiers (32 bits/64 bits)
– etc
Published by Mickaël wegerich , Il y a 6 ans
Bonjour,
Je sais que l’article à 10 ans, mais il y a un point qui m’interpelle car il n’est pas tout à fait exacte alors que l’accent est mis dessus.
Je veux parler du 1er commandement:
Un test unitaire doit être véritablement unitaire. La classe testée doit l’être en isolation complète, afin de ne tester qu’une classe (et une méthode) à la fois, le SUT (System Under Test).
Je ne suis pas d’accord. Un test ne test absolument pas une classe, il test un cas d’utilisation ou use case en anglais.
Après peut-être qu’un use case se résume à une classe mais peut-être pas…
Prenons un exemple simple : l’ajout d’un item dans un repository. Perso, je fais une classe repo (je passe sur DIP – ce n’est pas le sujet) et j’ajoute une couche avec des queries et des commandes, ce qui me fait une multitude de petites classes.
Si je suivais le premier commandement, ça n’aurait aucun sens de tester toutes les classes :) Alors que tester les use cases : AddItem, UpdateItem, RemoveItem, FetchByActivedItem, je ne sais quoi d’autre ! Cela à beaucoup plus de sens et on test toutes les classes et méthodes impactées implicitement et sans limiter le pouvoir de refactoring.
Et personnellement, je mock le moins possible (voir pas du tout) car cela rajoute des dépendances en plus. Je préfère stubber manuellement, c’est plus flexible et réutilisable