đŽ Vos tests unitaires Ă©chouent dĂšs que vous bougez le petit doigt...
Pourquoi ? Comment y remédier ?
Cet article fait partie dâun groupe dâarticles Ă propos des tests automatisĂ©s. Si vous avez manquĂ© le prĂ©cĂ©dent article, je vous invite Ă le lire ici : Quels tests automatisĂ©s Ă©crire et pourquoi ?
âOn nâĂ©crit plus de tests unitaires, car Ă chaque fois que lâon modifie le moindre petit bout de code, en apparence sans importance, plein de tests Ă©chouent ! On perd beaucoup trop de temps Ă les corriger ou on finit simplement par les supprimer.â
Jâentends cette critique Ă propos des tests unitaires depuis 2013, date Ă laquelle jâai parlĂ© des tests unitaires pour la premiĂšre fois en entreprise au cours dâun stage.
La raison numĂ©ro 1 de lâabandon des tests unitaires
La raison principale pour laquelle les équipes ne veulent pas / plus écrire de tests unitaires est donc cette critique injustifiée.
Injustifiée, car elle repose sur une mauvaise pratique, une incompréhension, un quiproquo.
Le sens du mot âunitaireâ
Ce quiproquo vient de lâinterprĂ©tation du mot âunitaireâ.
La comprĂ©hension que beaucoup en ont est que âunitaireâ = âfonctionâ (ou classe par exemple).
Cette dĂ©finition galvaudĂ©e a de grosses consĂ©quences. Des consĂ©quences nĂ©fastes pour un projet puisquâelle implique lâabandon des tests unitaires.
Rappelons tout dâabord lâobjectif dâun bon test unitaire : pouvoir refactorer son code lâesprit tranquille.
Refactorer son code, câest en modifier les dĂ©tails dâimplĂ©mentation, sans en changer le comportement.
Par extension, un test unitaire ne doit pas ĂȘtre couplĂ© Ă ces dĂ©tails dâimplĂ©mentation. âCouplerâ signifie que si un dĂ©tail dâimplĂ©mentation change, le test doit changer.
CrĂ©er un test pour chaque fonction revient Ă avoir une suite de tests dont le couplage est donc maximum ! Il y a un rapport de 1 pour 1 entre les tests et les fonctions de notre code. NĂ©cessairement, dĂšs quâune fonction va changer le test devra changer lui aussiâŠ
Pour Ă©viter ces Ă©cueils, le mot âunitaireâ doit ĂȘtre compris comme suit :
âunitaireâ pour unitĂ© de comportement. On teste un comportement au sein de lâapplication, une feature, issue dâune user story par exemple.
âunitaireâ pour âisolationâ. Un test unitaire doit pouvoir ĂȘtre exĂ©cutĂ© en isolation des autres tests. Ils peuvent ainsi ĂȘtre tous exĂ©cutĂ©s en parallĂšle.
Comment tester les fonctions privées ?
Les fonctions privĂ©es sont, par dĂ©finition, limitĂ©es au contexte de la classe (ou du module) dans lequel elles Ă©voluent. Comme ces fonctions ne sont pas accessibles depuis lâextĂ©rieur de la classe ou du module, ce sont donc des dĂ©tails dâimplĂ©mentation !
Mais ces fonctions existent, câest bien parce quâelles sont appelĂ©es Ă un moment donnĂ©e. Elles vont ĂȘtre appelĂ©es par des fonctions / mĂ©thodes publiques, qui font office dâAPI publique de notre module / classe.
En testant ces fonctions publiques, on teste donc par transitivité les fonctions privées.
Que faire si ma fonction privĂ©e est complexe et que jâaimerais ĂȘtre guidĂ© dans son dĂ©veloppement ?
Lâavantage des tests unitaires, notamment en Test Driven Development (TDD) est aussi de se sentir guidĂ© dans lâimplĂ©mentation des fonctionnalitĂ©s.
Avoir comme seule point dâentrĂ©e une fonction publique pour guider lâimplĂ©mentation dâune fonction privĂ©e complexe peut parfois ĂȘtre laborieux.
Heureusement, il existe quelques solutions :
Extraire la fonction privée dans sa propre classe / son propre module
Lâune des possibilitĂ©s est de constater que la complexitĂ© de cette fonction privĂ©e est telle que câest probablement le signe quâelle fait beaucoup de choses, et nâa donc pas une seule responsabilitĂ© au sens du principe de responsabilitĂ© unique (SRP).
Cela peut ĂȘtre une bonne indication pour sortir la fonction privĂ©e de sa classe / son module pour en faire une autre classe / un autre module. Ainsi cette fonction devient âpubliqueâ et est donc testable unitairement.
âPourquoi ne pas avoir rendu directement cette fonction publique en premier lieu au lieu de la garder privĂ©e ?â
Parce que ce faisant nous aurions cassĂ© lâencapsulation de la classe / du module. Une fonction privĂ©e reprĂ©sente un dĂ©tail dâimplĂ©mentation. En dĂ©cidant de sortir cette fonction dans son propre module, nous avons fait le choix de considĂ©rer quâil existait un nouveau concept Ă part entiĂšre. Cette fonction sera probablement divisĂ©e en plus petites fonctions, privĂ©es, qui feront office de dĂ©tails dâimplĂ©mentation de ce nouveau concept.
Shift Gear Down : changer de vitesse en rendant temporairement la fonction publique
Si lâon considĂšre que lâalgorithme de cette fonction privĂ©e nâa pas de sens en dehors du module / de la classe, on peut temporairement rendre cette fonction publique pour faire un test unitaire dessus, qui va donc tester le dĂ©tail dâimplĂ©mentation.
âMais je croyais justement quâil ne fallait pas tester les dĂ©tails dâimplĂ©mentation directement ?â
Câest tout Ă fait juste ! Il faut garder en tĂȘte que le plus important en TDD câest dâavoir un feedback rapide par rapport Ă lâavancĂ©e de lâimplĂ©mentation. Si ce feedback commence Ă ĂȘtre trop lent, on peut descendre dâune vitesse âshift gear downâ, en crĂ©ant un test unitaire prĂ©cisĂ©ment pour le dĂ©tail dâimplĂ©mentation.
Attention toutefois ! Ce genre de test est uniquement destinĂ© Ă amĂ©liorer le feedback instantanĂ©. Ce sont des tests fragiles : ils testent des dĂ©tails dâimplĂ©mentation, si ces dĂ©tails changent alors le test va Ă©chouer.
Plusieurs options sâoffrent alors Ă nous :
on peut supprimer le test fragile une fois la fonction rendue privée à nouveau, le test initial va de toute façon tester par transitivité cette fonction
on peut laisser le test si on considĂšre quâil apporte de la valeur pour un futur dĂ©veloppeur afin de mieux comprendre lâalgorithme (auquel cas il eut Ă©tĂ© plus judicieux de sortir cette fonction dans son propre module / sa propre classe en premier lieu plutĂŽt que de la laisser publique).
Happy Coding (et Testing) :)
TrĂšs bon article. Le genre qui m'aurait fait gagner beaucoup temps quand j'Ă©tais Ă©tudiant/junior.