Overblog
Editer l'article Suivre ce blog Administration + Créer mon blog

Qu'est-ce qu'un test unitaire?

Publié le

Une question d'apparence simple mais qui peut parfois cacher quelques pièges.

J'ai longtemps travaillé sans TU, mes mentors/gourous de l'époque n'ayant pas été portés sur la pratique (je ne parle même pas de TDD, juste de tests unitaires automatisés). Certains avaient tout de même réussi à me glisser dans un coin du cerveau que ce serait judicieux d'en faire parce que ça ne coute rien à l'exécution et que ça me permettrait de valider que mon code fonctionne sans même lancer mon appli.

Je vais tenter de passer en revue les principales idées(reçues) auxquelles j'ai pu être confronté et les conclusions que j'ai pu en tirer après quelques années d'errements et de pratiques plus ou moins efficientes.

Durant mes premières expériences, vous verrez que je n'ai pas forcément été bien aidé... Je pourrais même dire qu'on m'a poussé régulièrement dans de mauvaises directions. A vous de les lister au fur et à mesure, vous verrez si vous arrivez à des conclusions différentes des miennes au bout du chemin.

Number 1 : un test unitaire teste une méthode/fonction

C'est la première approche qu'on a pu me donner alors que je n'étais encore qu'étudiant/stagiaire.

La finalité du TU n'est donc de tester qu'une seule méthode. Ceci sous-entendait que si la méthode à tester appelait une autre méthode, il me fallait aussi un TU pour cette autre méthode...

Et déjà la sueur commençait à me couler sur les tempes. Il allait donc falloir écrire autant de code pour avoir une bonne couverture de tests? Avant même de commencer, j'étais harassé par l'ampleur de la tâche.

Spoiler : du coup j'ai pas fait de tests, de toute façon, sur mes premiers projets, personne ne me poussait dans cette direction, il y avait juste un consensus "bouh, c'est pas bien, personne ne fait de TU", et c'est tout...

 

Number 2 : je voudrais vraiment m'y mettre, mais ça coince par endroit...

Ben oui, j'ai bien fait ma conception tout comme on m'a dit, des méthodes publiques comme points d'entrée et des méthodes privées quand ça n'a pas à être exposé.

Sauf que dans mes méthodes privés, j'ai du code que je voudrais mettre sous test moi, alors je fais quoi? Je le passe en public alors que je veux pas qu'on puisse appeler ce bout d'algo seul, juste pour le mettre sous test? On m'avait dit aussi que je ne devais pas flinguer ma conception juste pour passer des tests.

Number 3 : ça y est, je m'y mets! Mais je fais comment sur ce projet???

M'y voilà! Une longue TMA, j'ai eu le temps de "dompter" le code et le fonctionnel (enfin je le croyais). On a constaté des problèmes de qualités. Les tests actuels étant manuels et réalisés par les fonctionnels, ils sont couteux et les retours longs à obtenir. C'est donc décidé, j'installe NUnit! J'ai mon évol fraichement développée à tester, je vais pouvoir mettre des tests automatiques! (et ça me semble plus sympa que les tests plans sous Quality Center)

Quelle est donc la première fonction que je veux tester? Une procédure stockée... Et bien on va l'appeler directement et vérifier qu'elle renvoie bien les bonnes données attendues. Ca a l'air de marcher! J'ai mon premier test au VERT du premier coup! Génial!
Deuxième fonction: ah, procédure stockée aussi mais avec un insert... Comment vérifier que les données sont bien insérées? Je n'ai pas de code existant qui les récupère(donnée à destination des batchs nocturnes sur lesquels je n'ai pas la main). J'écris un nouvel appel en base juste pour mon test pour vérifier que la data est bien insérée. Je lance le tout.... VERT! Ca marche! Trop fort!

Tellement fier de ces 2 réussites consécutives, je relance le tout pour voir l'ensemble de mes tests auto passer au vert en même temps... Et là... ROUGE! Plait-il?? Je regarde la trace. Je lance un debug pour confirmer... Le drame! Ma procédure stockée plante à la deuxième insertion, mon id a déjà été inséré au premier passage. Accessoirement, je me rends compte que je vais rapidement pourrir ma base de données si j'insère à chaque passage de test.

Je vais devoir faire un clean de mes données après chaque test? Mais ça va prendre un temps infini de coder tout ça! Et ce code de test, est-ce qu'il va être suffisamment propre et fiable? Si je mets autant de choses dedans, il faudrait que je le teste aussi??? #TestCeption

Number 4 : arriva TDD sur son cheval ailé!

Une formation sur TDD! Durée de la boucle de feedback, écrire les tests avant la code, passer au rouge avant le vert! GE-NIAL!! Je m'y mets immédiatement!

La vache, c'est raide, on a conçu un algo un peu complexe, il va falloir vérifier que le code passe bien par les bonnes étapes en fonction des types d'entrant. On me présente donc mon nouvel ami le Mock et comme notre archi a mis en place de l'injection de dépendances, grâce à tout ça je vais pouvoir générer de la donnée spécifique à chaque cas que le code devra traiter.

Ok, c'est long et fastidieux, mais j'y arrive. Bon le setup des mock est un peu touffu, mais j'ai (a priori) tout mes cas, mon code a l'air robuste! Et c'est le cas! Pour la première fois, je pousse un truc en prod sans la peur au ventre des bugs que ça va générer!

Je sens que j'entre dans un nouvel age de maitrise technique...

Number 5 : TDD, il est parti comme il est v'nu(il f'sait qu'passer)!

TDD sur un projet from scratch, c'était cool, mais sur de l'existant ou même sur du comportement graphique c'est pas trop fait pour.

Fin de l'histoire. Adieu. #PetitAngePartiTropTôt

Number 6 : TDD, il est revenu, moins simple, plus robuste, et cette fois il reste!

Après tout ça, j'ai réappris TDD quasiment de zéro. J'ai revu cette histoire de Red/Green/Refacto (tiens, on ne m'avait jamais parlé de cette troisième étape).

J'ai aussi découvert que c'était le test qui pilotait la conception et pas le test qui devait s'adapter à la conception. Ont suivi de nouvelles notions telles que testabilité, refacto, clean code, SOLID, et puis BDD, DDD et encore d'autres...

Au final c'est quoi un TU?

J'ai eu cette discussion lors d'un after du MUG Lyon. Un test n'est pas unitaire parce qu'il teste une seule méthode ou fonction, mais parce qu'il teste la plus petite unité de logique métier possible.

Tout le long de ma progression, on me présentait les TU comme une façon de tester mon code. Qui se soucie de tester du code? Ce qu'on teste, c'est le service rendu par le code: le métier! Tester le code plutôt que le métier pose 2 soucis majeurs :

  1. 1. 1000 tests sur 2 fonctions n'assureront jamais que ces 2 fonctions cohabitent bien ensembles.
  2. 2. L'étape refacto du TDD: si on teste juste la méthode, le refacto restera limité au code contenu dans cette seule méthode et toute tentative de clean de plus haut niveau verra alors une quantité importante de tests passer au rouge, voir même ne plus compiler.

 

Fort de ces constats, je tâche aujourd'hui de garder à l'esprit ces quelques éléments:

  1. - Postulat précédent tous les autres: Mon code doit produire de la valeur métier (et pas seulement flatter mon égo par sa magnificence)
  2. - C'est de la bonne livraison de cette valeur dont je dois m'assurer
  3. - C'est donc sur cette valeur que je dois mettre en place des tests (sur la livraison aussi, mais c'est un autre sujet)
  4. - Mes tests doivent donc se concentrer sur la valeur métier, pas sur l'implémentation
  5. - Chaque test ne doit être lié qu'à une seule fonction, un unique point d'entrée, lui-même peu soumis à variabilité.
  6. - Ca ne m'empêche pas de passer par des TU à une échelle plus fine si le besoin émerge au moment de l'implémentation(cf approches outside/in et inside/out)
  7. - Le refacto est devenu mon allier le plus puissant, en fin de boucle TDD classique ou en préambule d'une évolution potentiellement structurante.
  8. - Le refacto ne peut être fait sereinement qu'en disposant d'une couverture de test raisonnable.

Commenter cet article