Test unitaire: quelle utilité ?

Mammouth du PHP | 1315 Messages

12 juil. 2011, 03:54

Oh la (V|R)ache !

:arrow:
Compte supprimé

Petit nouveau ! | 1 Messages

22 avr. 2014, 21:32

Dans le genre inutile, je réponds 3 ans après =D> ... on ne sait jamais si le fantôme de mario vient lire ma réponse :priere:

En ce qui me concerne, je suis agiliste (non encore tout à fait agile, c'est assez difficile je trouve). En tant que tel, je pratique le TDD et le BDD dès que je le peux : en disant "dès que je le peux", je veux dire par là qu'il n'est pas toujours facile d'avoir un framework de test qui tienne la route, prêt à l'emploi ou assez simple à intégrer dans le workflow déjà existant du code que l'on maintien. Bref.

En TDD, comme en BDD, le développement est guidé par les tests : TDD signifiant Test-Driven-Development et en français Développement-Dirigé-par-les-Tests. En quelques mots simples (et malheureusement simplistes), il s'agit lorsque je veux réaliser quelque chose :
1) d'en écrire le plus petit test me permettant d'avancer dans la solution à mon problème. Le test en question ne peut pas être vert (= fonctionner sans erreur) car je n'ai pas réalisé le travail (que le test me demande de réaliser).
2) Le test étant donc rouge, je fais le nécessaire pour le faire passer au vert au plus vite : en implémentant la fonctionnalité requise par le test.
3) J'ai réussi, mon test est vert et là, je dois revoir mon code pour le rendre plus propre (= refactoring). Là, je prends le temps nécessaire pour faire le travail au mieux... si nécessaire, je rajoute d'autres tests (mais là, ça devient trop complexe pour être expliquer en deux mots).
4) Mon test est vert. Mon code réalisé est refactoré (donc relativement propre). Je peux donc retourner à l'étape 1 pour continuer mon travail.

Le but (difficile) est d'arriver à écrire une suite de tests "unitaires" me permettant de me guider dans l'écriture (l'implémentation) de la fonctionnalité finale que je veux obtenir. Typiquement, il n'est pas rare qu'une "simple" fonctionnalité prennent des dizaines de tests unitaires pour être réalisé complètement (dans un seul cas particulier).

Avec cette manière de faire, il n'est pas question de se demander quand faire des tests et quand ne pas en faire : aucun code ne peut apparaître ni exister avant l'écriture d'un code de test. Notez que quand j'écris un test, je code déjà puisque du code de test, c'est du code !

Les problèmes relever par le fait que "plus y a de code, plus y a de tests" et qu'après ça devient plus ou moins ingérable... sont une réalité. Mais là, on quitte le domaine des tests unitaires uniquement lorsque l'on parle d'une telle complexité car typiquement les tests unitaires sont des tests de fonctions (!) et par extensions, des tests de fonctions de classes. C'est "tout" !
Le nombre de ces tests n'est donc pas bien grand et ne dépend que du nombre de fonctions à tester dans une classe (donc le nombre de fonction, sauf les getters :o~ enfin, on peut les tester aussi pourquoi pas :roll: ). Lorsque je refactore une classe tellement violemment que je doive détuire quasiment la classe en question, il est clair que tous les tests sont à jeter à la poubelle...
Ceci est vrai et faux à la fois ! car il est rare d'avoir à refactorer du code en l'écrivant complètement du début à la fin : ça ne s'appelle pas du refactoring d'ailleurs mais de le réécriture. Et c'est très rare (mais ça arrive... dans le cas notamment du code vieux et relativement difficile à maintenir).

Lorsque l'on développe en TDD :
1) les tests ne sont plus juste des "tests" qui certifient qu'un travail est bien fait : l'ensemble de ces tests constitue une documentation "fonctionnelle" de ce que doit faire chaque fonction ; on appelle ça parfois les "spécifications d'une fonction".
2) les tests permettent de guider dans l'écriture du code d'implémentation : oui, car tant que le test est rouge, le travail n'est pas réalisé. On a donc une idée bien claire d'où on doit arriver ! et on sait qu'on a fini le travail dès que le test devient vert : il faut donc l'exécuter le plus souvent possible, en général une fois chaque fois qu'on écrit quelque chose qui compile (donc à chaque fois qu'une ligne de code est écrite ou modifidiée ! et oui). Il n'est pas rare de lancer les tests unitaires toutes les 10s quand on est vraiment super bon (c'est pas moi hey #-o )
3) les tests consistuent la première documentation par l'exemple du code que l'on développe : oui, car dans les tests, on a une utilisation du code que l'on écrit, les tests sont donc les premiers codes clients des fonctions que l'on développe... plus qu'une doc de l'API seulement, nous avons donc un exemple réel d'utilisation viable des fonctions... de TOUTES les fonctions et dans TOUS les cas requis, c'est-à-dire TOUS les cas testés.

Enfin, dans le BDD, le problème devient autre... BDD voulant dire Behaviour-Driver-Development ce qui signifie Développement-Dirigé-par-le-Comportement (ou plutôt dirigé par le haut comportement fonctionnelle) : là, ce ne sont plus des tests unitaires mais des tests dits "fonctionnelles", on y spécifie donc les résultats finaux que l'on veut obtenir.
Le BDD est donc au logiciel ce que le TDD est à la fonction, des tests du logiciel au niveau le plus haut (utilisateur) du logiciel lui-même, de l'application. Trop long à expliquer, c'est à ce niveau de tests que la gestion devient très compliqué à cause du nombre potentiel de tests, de l'explosion combinatoire qui oblige une stratégie de choix des tests, de leur maintien etc. Et c'est à ce niveau aussi que les tests, très mal écrits, deviennent instables et donc très difficile à gérer.