TestUnitaire

Un article de Agora2ia.

Cette pratique XP est de plus en plus répandue, bien au delà des projets agiles.


Ces tests sont au niveau de la classe, et incombent au développeur.


En pratique, les TestUnitaires sont :

  • Entièrement conçus et réalisés par le binôme qui développe la tâche la classe et la tâche associée,
  • Ecrits dans le même langage que l'application,
  • Constitués d'une classe de test par classe de l'application (qu'on appellera par la suite « classe applicative »),
  • Et d'une méthode de test pour chaque méthode publique de la classe testée (voir « Paul et Mick » plus bas?).

Ainsi la classe Family aura sa grande soeur FamilyTest : la première portant notamment la méthode prepareChristmas(), la seconde aura donc une méthode de test test_prepareChristmas. Le test peut se compliquer lorsque la classe Family a besoin d'une classe Doctor, qui elle a besoin d'une classe Hospital, qui elle a besoin de la classe HealthMinister, qui elle? Il est donc pratique de mettre de « fausses » classes qui « bouchonnent » ces dépendances. On parle de MockObject... Ici, notre classe Family serait utilisée avec une classe DoctorMock, qui ne tirerait aucune autre dépendance.


La réalisation d'un TestUnitaire fait entièrement partie de la tâche du binôme. C'est tout autant du développement que la réalisation de la classe applicative elle-même. Dans l'idéal, c'est-à-dire en TestDrivenDevelopment, le test sera même réalisé avant la classe applicative. L'impact du test sur la conception est alors prépondérant, et tant mieux.


Ces tests doivent passer à 100% continuellement et sont donc automatisés. Pourquoi ? Car il est inconcevable de remonter sous le gestionnaire de version si l'un de ces tests unitaires échoue. En effet, de par la pratique de la PropriétéCollectiveDuCode, n'importe quel binôme peut intervenir à tout instant sur n'importe quelle portion du code présente dans le GestionnaireDeVersion. Il faut donc que l'intégralité du code soit valide, c'est-à-dire que tous les tests unitaires passent avec succès. Cela implique de jouer fréquemment ces tests, donc de conserver facilité et rapidité dans leur exécution.


De plus, ils sont joués tôt dans le cycle d'IntégrationContinue : juste après la compilation (des classes et des classes de test). Ils permettent ainsi d'avoir une feedback précis sur l'implémentation de l'application.


Cette pratique se place à l'opposé de processus qui préconisent une conception détaillée en amont du projet et une phase de tests en fin de projet.

On part du problème : le TestDeRecette de la fonctionnalité. On écrit un premier test unitaire qui simule un appel à la première méthode de la première classe. Puis en développant cette classe on s'aperçoit qu'il serait judicieux d'avoir à disposition cette « autre fonctionnalité » (une autre classe). On écrit donc le test unitaire de cette autre classe puis cette autre classe elle-même? Mais... mais... On vient de piloter notre conception par les tests ou TestDrivenDevelopment? donc par les cas d'utilisation ! Ce qui n'est pas si aberrant que cela au final !


Je ne suis pas inquiet outre mesure sur la pertinence et la qualité de ce que j'ai écrit car :

  • Tout d'abord je l'ai écrit en collaboration avec un binôme vigilant,
  • A tout moment j'ai pu interpeller un membre de l'équipe qui connaissait bien la problématique pour lui demander son avis
  • La PropriétéCollective du code me garantit que si ce code ne convient pas, il sera amélioré.

Au final je suis sûre d'une chose, c'est que ma classe fait ce que je lui demande : n'est-ce pas le vrai besoin. A moyen terme le module fera ce qu'il faut, et à la fin, se sera l'application elle-même qui fera ce qu'il faut.


Le dernier point sur la PropriétéCollective met en évidence un autre intérêt des tests unitaires : l'aspect documentaire.

Lorsque l'on intervient sur une portion de code que l'on n'a pas écrit on est content de trouver de la documentation sur son fonctionnement. Je ne parle pas d'un document Word, qui pourrait aisément avoir des défauts :

  • pas à jour,
  • volumineux,
  • incomplet (ce qui n'est absolument pas incompatible avec le précédant point !),
  • écrit dans une langue que je ne comprends pas, incompréhensible !

Un TestUnitaire, qui compile et passe avec succès, est forcément à jour. De plus, si l'on applique rigoureusement le TestDrivenDevelopment on a également des garanties quand au fait le test couvre de façon complète les fonctionnalités, ni plus ni moins. Enfin, avec les conventions de code et les binômes tournant qui homogénéisent les compétences, avec le temps, quand j'intervient sur du code que je n'ai pas écrit j'ai l'impression de me relire.

Par expérience, quand on arrive sur une portion qu'on ne connaît pas, on commence par aller voir les tests unitaires. On a très souvent une présentation claire, rapide et précise de ce que fait le code.


De plus, cette documentation à l'inestimable avantage d'être dynamique. Une pratique saine lorsque l'on vient nous voir pour nous demander « Combien ça coûterait de faire cela ? », est de modifier sommairement une partie clef du code et de rejouer les tests unitaires. En constatant les dégâts, à savoir les tests unitaires qui échouent, on a un indicateur sur les parties impactées par la modification, et donc une première estimation du chantier.


Au final, le test unitaire est un outil polyvalent et donc indispensable dans :

  • la conception de la classe
  • l'identification précise de l'état actuel de l'application
  • l'évaluation des impacts de toute modification dans le code
  • la documentation et la spécification d'une classe.


Vous comprenez donc que la réalisation de tests unitaires est une pratique importante, qui prend du temps : ce temps doit donc être pris en compte au moment de l'estimation des tâches au cours du PlanningGame. Lors des premiers PlanningGame, il est sain de rappeler : « Ah oui, mais c'est vrai qu'il y a aussi les tests unitaires : je rajouterais un peu plus de temps à l'estimation ! ».


Paul et Mick dans l'extrême

Selon les interprétations on ne teste que les méthodes publiques (et protégées), ou au contraire on teste l'intégralité des méthodes, mêmes privées, lorsque le langage le permet.


Pour ma part je partage la première solution. Un test unitaire, tout comme un test de recette, simule une utilisation par un client. Dans le cas du test unitaire, le client, c'est-à-dire une autre classe, n'utilisera pas de méthode privée de la classe testée. Par contre, les autres méthodes de la classe testée, qui elles sont testées, utilisent les méthodes privées qui sont donc indirectement testée.


Ressources