Tout a commencé il y a maintenant quelques mois, lorsque l'un des membres actuels de l'équipe de développement d'Atoum m'a contacté pour me poser une question sur la façon d'écrire des tests unitaires.
À l'époque, il ne participait pas encore au développement du projet, et s'il connaissait parfaitement l'aspect théorique des tests unitaires et qu'il prônait leur utilisation, c'était la première fois qu'il devait effectivement les mettre en pratique.
Et comme la plupart des débutants dans les tests unitaires, il s'est demandé comment il était possible de tester une méthode de classe privée ou protégée.
J'avoue avoir répondu sans réfléchir, tellement la réponse me semblait évidente.
À mon sens, il n'est pas nécessaire de tester ces méthodes.
Et pourquoi donc me direz-vous ?
Tout d'abord, pour en finir immédiatement avec mon argument le plus subjectif, je pense que les solutions permettant de tester ces méthodes ne sont pas propres ou bien relativement lourdes à mettre en œuvre.
Ensuite, l'encapsulation, qui se traduit en PHP par l'utilisation des mots clefs protected
et private
affectés à une propriété ou une méthode de classe, a pour but de masquer l'implémentation technique de la classe à son utilisateur.
Or, de mon point de vue, un test unitaire valide l'implémentation de la classe du point de vue de son utilisateur, et non du point de vue de son développeur.
C'est pour moi avant tout un contrat passé entre le développeur et l'utilisateur, qui garanti à ce dernier que l'interface publique de la classe se comportera comme elle le doit, ni plus ni moins.
Il est donc logique que je ne me soucis pas de tester les méthodes protégées ou privées de mes classes, puisque seul compte à mes yeux le fait que les méthodes publiques de ma classe se comportent correctement.
De plus, lorsque l'on fait du développement piloté par les tests, ce qui est mon cas, le processus à suivre pour créer une classe est le suivant :
- le développeur écrit un test pour une méthode publique de la classe.
- Le développeur écrit le code le plus simple possible qui permet à la classe de réussir le test écrit précédemment.
- Le développement se poursuit de cette manière tant que le comportement des fonctionnalités devant être assurées par la classe n'est pas intégralement défini par les tests.
Et le mot le plus important dans ce qui précède, outre le mot publique, est simple.
Par simple, j’entends même naïf, dans le sens ou il est par exemple permis de faire de la duplication de code, d'utiliser des algorithmes peu efficaces, et surtout de ne pas se soucier de l'encapsulation.
L'encapsulation n’apparaît que lors de l'étape de refactorisation, qui intervient à l'issue du processus décrit ci-dessous, et qui a pour but de passer d'une implémentation naïve à une implémentation faite dans les règles de l'art, avec la garantie que la classe continuera à se comporter comme elle le doit grâce aux tests écrits précédemment.
Comme vous pouvez le constater, il n'y a pas de place pour des tests relatifs aux méthodes protégées ou privées dans ce processus, puisqu'elles sont une conséquence de la refactorisation et n'apparaissent aucunement lors de l'écriture des tests.
Enfin, J'ai un autre argument pour justifier le fait qu'il est inutile de tester les méthodes protégées ou privées d'une classe.
L'encapsulation ayant pour but de masquer l'implémentation d'une classe, elle permet de faire de la refactorisation à tout moment au cours de la vie de la classe sans impacter l'utilisateur de la classe.
Il est donc tout à fait possible de modifier de manière très radicale le fonctionnement interne de la classe sans que l'utilisateur final en ait conscience.
Or, si les méthodes privées et protégées sont testées, il faut que le développeur, lorsqu'il effectue la refactorisation, modifie également les tests, ce qui représente un travail supplémentaire non négligeable.
De plus, lors de ce processus, les tests ne sont pas censés être modifiés puisqu'ils sont là pour garantir que le comportement de la classe ne change pas malgré les modifications effectuées sur l'implémentation.
Évidement, le camp adverse a également des arguments lui permettant de justifier la définition de tests pour les méthodes privées et protégées, et celui qui revient le plus souvent est qu'il est difficile de localiser précisément un bug ou une régression si ces méthodes ne sont pas testées.
Je répond à cela qu'un test unitaire n'est pas un débugger et qu'il ne remplace pas un cerveau, mais vous vous doutez bien qu'il est assez mal reçu.
L'autre argument récurrent est que, selon eux, je pense que l'unité dans mes tests est la classe, alors qu'ils considèrent que l'unité est la méthode.
J'avoue que ce n'est pas entièrement faux, même si je me plaît à préciser que pour moi, l'unité est la méthode publique et non la classe.
Évidemment, ce débat est une bataille de chapelles, et en tant que tel, il n'aura jamais de réponse absolu, d'autant que le monde des tests unitaires évolue.
Il y a en effet des gens comme Ivan Enderlin, le créateur du framework PHP Hoa, dont c'est le métier de réfléchir à ces problématiques.
Et à force de réfléchir, il a trouvé un moyen novateur de réaliser des tests, qui n'est pas une autre façon de faire des tests unitaires, mais plutôt une technique complémentaire qui permet entre autre de ne pas se préoccuper de l'encapsulation.
Je ne vous donnerais aucun détail sur ce qu'il propose, car je considère que c'est à lui de le présenter, et de plus, je pense que cela mériterait bien plus qu'un simple billet.
J'ajouterais de plus que je ne suis pas entièrement convaincu par ce qu'il propose, et je suis donc assez mal placé pour parler de son travail.
Je considère cependant qu'il a trouvé une nouvelle voie qu'il est intéressant de suivre un minimum, ne serait-ce que pour voir ou elle peut mener.
Vous l'aurez compris, le débat sur l'intérêt de tester les méthodes privées et protégées n'est pas prêt de se terminer, la preuve en étant que ce débat a, depuis la question d'origine, débordé du cadre d'Atoum, puisque j'ai eu l'occasion de discuter de ce sujet sur twitter ainsi qu'au forum PHP 2010 avec des gens totalement externes au projet.
Et pour ceux qui se poseraient éventuellement la question, Atoum est potentiellement capable de gérer les deux philosophies, et je n'ai aucun problème pour travailler avec quelqu'un qui défend un point de vue opposé au mien de manière constructive, bien au contraire.
Je considère en effet que c'est de la différence, du débat, qu'émerge les idées novatrices, car cela nous force à voir les choses sous un autre angle.
Je suis donc extrêmement heureux d'avoir une force contradictoire au sein de l'équipe de développement !
30 réactions
1 De Martin - Webaaz - 18/11/2010, 09:53
Comme j'aime des conclusions ouvertes comme celle-ci ! Ça donne envie de suivre et de participer au débat pour améliorer la méthodologie, d'autant que je me positionne entre ton point de vue et celui de tes
.Il y a sûrement à prendre des deux côtés pour faire évoluer les solutions !
Il va falloir que je teste Atoum !
2 De neolao - 18/11/2010, 11:30
Pour moi, la question ne se pose même pas, on ne teste pas les méthodes privées pour toutes les raisons que tu as invoqué.
Je ne comprend pas comment il peut y avoir débat. C'est contre productif de créer ces tests.
3 De jeremyFreeAgent - 18/11/2010, 15:20
Pour moi l'étape 2 est plus :
"Le développeur écrit le code le plus simple possible qui permet à la classe de NE PAS réussir le test écrit précédemment."
Sinon on a une méthode qui passe le test alors qu'elle ne fait pas le travail !
Mais après ce n'est qu'une question de méthode !
4 De mageekguy - 18/11/2010, 15:39
@jeremyFreeAgent : J'ai du mal à te suivre, mais si tu te comprend, c'est l'essentiel ;).
5 De Vincent - 18/11/2010, 15:40
Ou on interdit les méthodes privées ? (non ce n'est pas une blague)
6 De NiKo - 18/11/2010, 15:42
Au delà de ce débat-là, je me pose de plus en plus la question de l'intérêt des propriétés privés et protégées. D'experience et dans ma pratique personnelle, elles ont la majeure partie du temps constitué un frein à l'utilisation pratique de beaucoup de librairies que j'ai été amené à utiliser, même si je peux concevoir leur application technique /théorique/. On pourra objecter que les implémentations en question faisaient peu de cas des "bonnes pratiques", mais je n'ai jamais vraiment trouvé de référentiel sur la question ; autrement dit, quand et comment utiliser des méthodes privées ou protégées, et par dessus-tout, pourquoi ?
PS: je sens que je vais me faire basher la tronche avec ce commentaire.
7 De mageekguy - 18/11/2010, 15:55
@NiKo : Personnellement, j'encapsule tout ce qui pourrait permettre à l'utilisateur de la classe de casser la cohérence de l'instance de la classe si c'était publique et donc modifiable sans contrôle.
Ainsi, si ma classe est dépendante d'une connexion à une base de données, je l'encapsule afin d'éviter que l'utilisateur puisse remplacer cette connexion par une chaîne de caractères ou tout autre chose qui n'est pas une connexion.
À mon sens, ce n'est pas définir ce qui doit être publique ou privé le plus délicat.
C'est de définir ce qui doit être privé ou protégé.
Dans les faits, j'utilise assez peu
private
, car il est réservé à ce qui est exclusif au fonctionnement de la classe, spécifique à son implémentation.8 De usul - 18/11/2010, 16:24
Ce que veut dire @jeremyFreeAgent c'est que normalement en test first, on écrit un code qui foire volontairement pour vérifier que le test détecte bien les erreurs (enfin je pense que c'est ça qu'il a voulu dire).
Sinon, on en a déjà longuement parlé sur twitter, on ne se place pas dans la même optique, je comprend tout a fait ton point de vue mais il n'en reste pas moins que des fois on peut avoir besoin/envie de mettre en place des TU non pas pour faire debugger mais pour juste vérifier qu'il n'y a pas de regressions dans le code.
Et ce, dans n'importe quelle partie du code, public, protégé ou privé.
Je suis d'accord avec toi, pour tester/mocker du protégé/privé, c'est souvent du 'ninjatage'.
Pour le faire avec phpunit, j'ai du modifier les templates de génération de mocks et ma solution ne marche que pour du php 5.3 car j'utilise setAccessible de l'api de reflection.
Autre moyen utilisé, runkit avec là aussi une petite modif maison ...
D'ou ma question l'autre jour au sujet de Atoum dont j'attend énormément :D
9 De mageekguy - 18/11/2010, 16:29
@usul : Vas-y, met la pression !
10 De MathRobin - 18/11/2010, 20:37
Hello!
(tu t'es lâché là, un joli petit pavé)
<hs>
Je t'avouerai avec honte que je ne me suis jamais penché sur Atoum, même si ça me semble vachement intéressant.
Je viens mettre mon grain de sel, n'ayant pas pu résister à ton appel à la bagarre sur Twitter
</hs>
Perso, je voyage plus ou moins entre les deux. Je suis d'accord avec toi que la cuisine interne de la classe, ça la regarde, pas besoin de la tester tant qu'en extérieur tout roule.
Cependant, pour moi la "cuisine interne", ça se limite aux méthodes private, les protected sont exposées à l'héritage. Si une fille demande à sa fille un retour d'une méthode protected, elle est en droit d'attendre le bon résultat.
Donc les tests unitaires sur les private, non, sur les publics, oui, sur les protected, ou aussi.
Allez, je suis prêt, envoyez les pierres
11 De mageekguy - 18/11/2010, 22:46
@MathRobin : Des méthodes protégées qui ne sont pas utilisées par des méthodes publiques de la classe n'ont pas de raison d'exister.
Donc ton argument ne tient pas :D.
12 De metagoto - 19/11/2010, 00:38
Franchement, je ne sais pas quoi penser de tout ça. Les deux écoles me paraissent avoir des arguments recevables. Tout dépend des facilités offertes par la langage pour accéder aux entités "protégées"? Je parle d'entités, car en plus de méthodes privées, il peut s'agir de fonctions, de classes complètes ou de tout autre truc qui serait "interne" à un module ou un package (notion qui varie selon le langage). Par étapes successives, je comprends qu'on puisse vouloir tester directement une méthode privée tout comme on pourrait/voudrait le faire pour une classe interne à un module.
13 De ratibus - 19/11/2010, 09:16
Une méthode
protected
, ses utilisateurs ce sont ses classes fille notamment, donc tu dois garantir à tes classes filles que l'API que tu leur proposes est testée.D'autre part, quand tu dis :
, justement tu pré-supposes de l'utilisation de ta classe mère.A mon sens le fait de mettre une méthode en
protected
c'est pas parce que t'en as besoin dans l'immédiat pour une classe fille, c'est surtout parce que tu proposes un point d'entrée dans ta classe mère, qu'il soit utilisé ou non, c'est pas ton problème.C'est le même raisonnement avec les méthodes publiques.
Tu peux rendre une méthode publique pour ton API.
Tu ne sais pas à l'avance si elle va être utilisée et pourtant tu la testes.
Il faut donc faire pareil avec les méthodes
protected
.C'est comme avec le design pattern du singleton.
Ce n'est pas à la classe de savoir qu'il ne va y avoir qu'une seule instance à la fois, c'est au code qui s'occupe de l'instancier de s'occuper de ce genre de chose.
14 De MathRobin - 19/11/2010, 09:42
@mageekguy : et que fais tu justement des méthodes protected d'une mère qui sont appellées par les méthodes publiques d'une fille? Tu ne testes que la publique?
Cependant, je ne comprends pas pourquoi pour toi une méthode protected qui n'est pas appellée par des méthodes publiques n'a pas lieu d'exister. Je sais que c'est un autre débat mais je t'avoue ne pas saisir. J'ai pas d'exemple en tête mais je ne comprends pas.
15 De Luc - 19/11/2010, 09:45
Un collègue m'a dit un truc hier à ce sujet, c'est que si une méthode privée ou protégée avait à être testée, c'est qu'elle devrait alors plutôt être publique, ou alors déplacée ailleurs car elle n'est pas à sa place (mais donc du coup être rendue publique également).
Si l'argument est considéré comme recevable (ce dont je ne suis pas totalement sûr), je rejoins alors la question de Niko : quel est l'intérêt en pratique des méthodes privées et protégées ?
16 De usul - 19/11/2010, 09:45
En fait dans mon cas c'est pas trop que je veuille tester des méthodes non publiques, mais plutôt que veut pourvoir les mocker afin de pouvoir tester unitairement le code de ma méthode publique.
Je veut être sur que mon algo tienne la route et pourvoir détecter au plus vite une régression et savoir qui l'a introduite.
En général c'est là que tu me dit que les TU ne sont pas des debugger et je suis d'accord avec toi mais pour moi ce n'est pas me servir d'un test comme debugger mais juste m'assurer que mon contrat est rempli en dehors de toute dépendance.
17 De mageekguy - 19/11/2010, 11:29
@ratibus et @MathRobin: L'encapsulation d'une méthode via
protected
ouprivate
a pour but de masquer l'implémentation de la classe.Donc si une classe ne propose que des méthodes protégées, cela veut dire qu'elle ne fait rien publiquement, donc qu'elle n'a pas besoin d'implémentation et qu'en conséquence, elle n'a pas besoin de méthodes protégée, puisque ces dernières sont destinées à contenir l'implémentation.
Une classe qui ne contient que des méthodes protégées ou privées, c'est un paradoxe ou une aberration, au choix.
En effet, pour enfoncer un peu plus le clou, lorsque vous concevez cette coquille vide, vous ne pouvez pas anticiper les besoins, en terme d'implémentation, des classes filles (dans le cas contraire, vous avez le don de divination et je vous invite à changer de métier fissa pour devenir milliardaire dans les meilleurs délais).
Donc si vous définissez une classe qui ne contient que des méthodes protégées, vous vous donnez du mal pour rien car vous ne pouvez pas connaître les besoins des classes filles à l'avance.
En conséquence, une classe ne doit implémenter des classes protégées ou privées que si elle a des méthodes publiques, qui sont testables et qui permettent donc par transitivité de tester les méthodes encapsulées.
Et l'encapsulation a pour but d'empêcher l'utilisateur de la classe de briser la cohérence de l'objet lorsqu'il le manipule/utilise.
Donc tout ce qui peut permettre à l'utilisateur de hacker ou manipuler des données primordiales pour le bon fonctionnement d'une instance de la classe doit être encapsulé.
18 De Laurentj - 19/11/2010, 15:03
Je suis d'accord avec toi que tester les méthodes publiques, ça suffit. Mais pas tout le temps en fait.
Imagine une classe qui fait un traitement assez complexe. Genre elle parse un format précis. Elle a une seule méthode publique parse(). Et comme l'algo est assez complexe, elle possède une multitude de méthode protégée/privée, qui s'occupent de parser un truc précis dans le format, ou effectuent un traitement sur des données précises issue d'un parsing.
Lors du développement de telles classes, j'aime bien pouvoir tester individuellement certaines méthodes non publiques, pour être sûr que le traitement qu'elles font (un bout de parsing ou autre), elles le font bien. Et donc cela me rassure plus sur le fonctionnement de la méthode publique qui les appelle.
Ainsi, si mes tests "globaux" sur ma méthode publique échouent, il y a de fortes chances que c'est à cause d'une des méthodes protégées. Et dans ce cas, j'ai forcément des tests qui échouent sur l'un de ces méthodes, ce qui me permet de moins chercher le bug, ou si j'ai pas de tests qui échouent sur les méthodes non publique, il y a probablement un manque de tests
Maintenant, je ne fais pas des tests sur toutes les méthodes protégées de toutes mes classes. Seulement là où il y a de fortes chances de régressions ou des trucs complexes. Je n'aime pas passer trop de temps sur les tests
Et sinon, pour tester des méthodes protégées, c'est simple : je créer une classe qui dérive de la classe à tester, en créant des méthodes publiques qui ne feront qu'appeler les méthodes protégées, et je teste cette classe.
Pour ton histoire de classe qui ne contiendrait que des méthodes non publiques, je crois que tu as oublié les classes abstraites
On peut très bien avoir une classe abstraite, qui définisse certaines méthodes publiques abstraites, et implémentent des méthodes protégées qui seraient utilisées par les classes filles. (ou dans la même idée, une classe de base ne contenant que des méthodes non publique, que l'on hérite, en plus d'implémenter une interface publique).
Donc non, des classes qui ne contiennent que des méthodes protégées, ce n'est pas une aberration. Ce n'est ni un signe de divination. D'abord parce que ce sont des choses auxquelles on peut arriver à la suite d'une factorisation par exemple, mais en plus il n'y a personne qui interdit de réfléchir sur le papier sur la manière dont on va hiérarchiser les classes, organiser son code, ce qui permet alors de déterminer quelles méthodes protégées il va falloir implémenter dans la classe de base, et qui seront utiles à plusieurs classes filles.
Les méthodes agiles n'empêchent pas de réfléchir sur la conception, sur l'architecture du code, avant le codage proprement dit
19 De Ivan Enderlin - 20/11/2010, 12:03
Hey Frédéric :-),
J'ai réfléchis avec encore plus d'attention à ton point de vue et voici ce que je suis capable d'en ressortir.
Tout d'abord, désolé si mes derniers propos t'ont choqué, ce n'était pas le but. Ton point de vue a évolué et je suis plus apte à le comprendre maintenant.
On ne va pas rentrer dans un troll (ou querelle de chappelles comme tu le dis si bien), on va simplement réfléchir de manière scientifique.
Si on se réfère à la définition d'un test unitaire pour un langage adoptant un paradigme orienté-objet, l'unité est la méthode. C'est un fait et je ne pense pas qu'on puisse le contester. Tu l'admets toi-même avec un nuance sur la visibilité.
Réfléchissons deux minutes au but d'un test. L'objectif n'est pas de montrer que tout va bien, mais que quelque chose va mal. C'est une nuance très très importante. C'est simplement le complémentaire de la démarche que tu présentes. Un test est là pour détecter que quelque chose va mal, qu'une exécution ne fait pas ce qu'on veut, ce qu'on en attend. Autrement dit : que le contrat que l'on a passé avec notre algorithme est corrompu.
J'apporte comme preuve quelques définitions ou citations influentes : « Le test est l'exécution ou l'évaluation d'un système ou d'un composant par des moyens automatiques ou manuels, pour vérifier qu'il répond à ses spécifications ou identifier les différences entre les résultats attendus et les résultats obtenus » — IEEE Glossary of Standard Software Engineering Terminology.
Les deux dernières abondent dans mon idée que les tests doivent détecter une anomalie et pas montrer que tout va bien : « Tester, c'est exécuter le programme dans l'intention d'y trouver des anomalies ou des défauts » — G. Myers, The Art of Software Testing, personne très importante.
Mais également (et la plus importante) : « Testing can reveal the presence of errors but never their abscence » — Edsgar G. Dijkstra, Notes on Structured Programming (oui, le célèbre Dijkstra lui-même).
Reprenons la dernière : un test peut révéler la présence d'erreur mais jamais leur absence. C'est très important. Ce qu'on peut en comprendre, c'est que si un test est vert (image d'un succès), ça ne veut pas dire que le code est sans erreur.
L'objectif change alors. On va étendre nos tests partout pour tout tester, chercher toutes les erreurs.
On arrive alors à la notion de testabilité.
La testabilité est une problématique actuelle très compliqué, et très facile à comprendre qui plus est. Par exemple : comment tester un singleton ? comment tester une ressource ? comment tester des méthodes privées ? protégées ? comment tester des classes ou méthodes finales ? comment tester des classes internes, des classes abstraites, des closures ? etc. La plupart ont déjà des réponses (bonnes ou mauvaises) mais pas toutes.
Alors, si on étend nos tests, on a une plus forte probabilité de détecter des erreurs, et donc, améliorer la qualité de notre code.
Mais on va aller un peu plus loin. Quand on parle de test, on parle également de couverture. La couverture est une des métriques capable d'exprimer la pertinence de nos tests. On trouve différentes couvertures, comme : toutes-les-instructions, toutes-les-conditions, toutes-les-décisions, toutes-les-n-boucles etc.
Restons dans le général : toutes-les-instructions ; c'est quand même un minimum non ? On aimerait bien que nos tests puissent exécuter toutes les instructions de notre algorithme afin de détecter le maximum d'erreurs (pas toutes les erreurs, car il faut aller plus loin dans la converture avec toutes-les-conditions ou toutes-les-décisions par exemple).
Si on ne teste que les méthodes publiques, la couverture de ta classe sera très faible. Toutes tes méthodes protégées ou privées ne sont pas appelées depuis des méthodes publiques, tu ne peux donc pas assurer une détection d'erreur confortable (je ne dis pas maximale ou optimale, on va se contenter de confortable, ce qui serait déjà très très bien).
On revient alors à la testabilité.
Ce que j'ai compris au début, c'est que si tu ne testes pas ce qui est protégé ou privé, c'est que tu ne sais pas le tester. Ça reléverait plus d'un problème technique d'après moi. Attention, je ne critique pas ton travail ! C'est un véritable problème, assez compliqué. En Java, c'est un problème très dur à résoudre qui force à reconsidérer la façon dont on écrit le code car le problème est très très dur à résoudre.
Je résume mes propos et je conclus :
• le test permet de détecter la présence d'erreurs et non pas de conclure de l'absence d'erreurs ;
• la couverture de code permet de qualifier et quantifier la pertinence des tests ;
• ne tester que ce qui est publique n'offre pas une bonne couverture de code et ne peut donc pas révéler autant d'erreurs que souhaiter ;
• on ne teste pas uniquement ce que le client va utiliser mais tout ce sur quoi repose le système du client (ça implique donc entre autre ce qui est protégé et privé).
Ma réponse n'est pas complète, j'aimerais en dire plus, mais j'attends déjà ta réponse :-).
PS : je n'ai pas pris d'exemple pour lesquels ne tester que le publique est dangereux, mais les effets de bords sont faciles à trouver, je peux t'en fournir.
20 De mageekguy - 20/11/2010, 12:46
@Ivan Enderlin : Je te rejoins totalement sur la définition du test et sur la testabilité et ses problèmatiques.
Je dis toujours à ceux qui découvre les tests unitaires qu'écrire du code testable est une philosophie, et j'en via à l'usage par me dire que c'est peut être même un métier, au même titre qu'intégrateur html, puisque dans les deux cas, il faut avoir consciences des problèmatiques posée par l'environnement et les concepts manipulés pour pouvoir faire le travail demandé au mieux.
La preuve en est que cette fameuse testabilité impose parfois d'ajouter du code dédié aux tests au code
pour lui ajouter des points d'entrés afin qu'il puisse être testé, et je peux te dire que cela choque, j'en ai encore eu la preuve dernièrement.D'ailleurs, même avec ta méthode, cela reste nécessaire, même si c'est caché par l'étape de compilation, mais là n'est pas la question.
Je ne teste pas les méthodes privées et protégées non pas parce que je ne sais pas le faire, mais parce que je ne veux pas le faire parce que je pense que cela va à l'encontre du concept d'encapsultation qui est tout de même l'un des piliers de la programmation orientée objet.
De plus, comme je pense qu'une méthode privée ou protégée n'a pas lieu d'être si elle n'est pas utilisée par au moins une méthode publique de la classe, j'aime à penser que la couverture des tests est implicitement complète.
J'ajouterais que le 100% de couverture sur la méthode protégé ne m'intéresse pas forcément du moment que la méthode publique qui l'utilise se comporte comme elle le doit (et je pense que c'est précisément ce point qui te hérisse le poil, ainsi qu'à d'autre).
Cependant, je pense que tu oublis un point important : l'écriture du test et du code n'est pas ponctuelle.
Je veux dire que la fiabilité de la classe ne repose pas uniquement sur l'écriture des tests, mais bien sur un processus créatif global, que je décris dans mon billet en partie dans mon billet, et il faut prendre cette méthode dans son ensemble pour comprendre mon point de vue.
Tu soulignes par exemple que la couverture du code par les tests est une notion très importante.
Et effectivement, tester les méthodes privées/protégées uniquement via les méthodes publiques peut induire le fait que la totalité du code des méthodes encapsulées ne soit pas exécuté à 100% par les tests.
Cependant, il y a des outils pour détecter cela, et associé à de l'intégration continue, la détection de tests incomplets au niveau de la couverture de code devient très facile, et compléter les tests également.
Quand aux fameux effets de bord, je ne nie par leur existence.
Mais encore une fois, je pense qu'ils sont révélateurs de tests incomplets au niveau des méthodes publiques, et non induit par le manque de tests sur les méthodes protégées.
Mais je suis d'accord, si ces dernières étaient testées directement, cela n'arriverait peut être pas.
Sauf que ma religion me l'interdit ;).
21 De mikaelrandy - 20/11/2010, 13:20
"Sauf que ma religion me l'interdit ;)."
Pourquoi débattre dans ce cas là ?
Il s'avère que je me situe comme d'autre entre les 2 avis.
Autant tester l'encapsulation complique grandement les tests, et leur évolution (changer l'implémentation d'une classe sans changer l'API) demande au moins autant de travail dans les tests que dans la classe elle-même.
Mais, et cet argument a déjà été avancé, le principe du test unitaire est de tester les blocs, et pour les classes compliquées, il est nécessaire de tester les sous-parties d'un traitement publique, donc les méthodes privées et/ou protégées.
Mais cela viens de ma définition des tests, à savoir que je ne considère pas ça comme un contrat de fonctionnement (la doc assure ça. Je ne demande pas à mes utilisateurs de lire mes tests pour comprendre comment marche mon API) mais comme un test de non-regression, et il faut donc que j'assure que la moindre modification sur une portion de code n'a pas de conséquence non désirée.
Et pour ton argument sur le fait que les méthodes protégées/privées qui ne sont pas utilisées par les méthodes publiques, je répondrais simplement que lorsque je code, je prévois toujours plus que ce que j'ai besoin (je prévois l'évolution) et mes méthodes protégées/privées font souvent plus que ce que j'ai besoin.
Si je ne les testes pas, je perds une partie de la couverture de mon code.
Et, pour moi, ne pas faire une méthode évolutive sous prétexte qu'on ne l'utilise pas de suite va à l'encontre de ma religion
22 De mageekguy - 20/11/2010, 13:24
@mikaelrandy : Pourquoi débattre ? Parce que je ne développe pas Atoum que pour moi et comme il a la vocation d'être un outil qui simplifie la vie du développeur, au contraire des outils actuels, je pense qu'il est important que je prenne en compte les désirs et les différentes perceptions de la communauté, même si je ne suis pas 100% en phase avec elle.
Il faut donc que je fasse connaissance avec cette communauté, avec sa façon de penser, que je sache si j'ai une opinion partagée par la majorité, ou bien minoritaire, etc.
De plus, c'est un sujet qui revient régulièrement sur le tapis, mais à ma connaissance, il n'y a jamais eu aucune formalisation écrite, et je pense que rien que cela est intéressant.
Enfin, je suis intimement que le débat construit et argumenté est un outil d'amélioration continue phénoménal.
J'espère avoir répondu à ta question.
Et le reste de ton commentaire illustre parfaitement ma réponse.
Nous n'avons clairement pas la même perception de ce qu'est un test unitaire, ni la même façon de concevoir le développement.
En tant qu'agiliste, j'essaye, et c'est parfois difficile, les vieux démons ayant la peau dure, de ne développer que le strict minimum afin tout simplement de ne pas perdre inutilement du temps.
Il n'est pas possible de savoir de quoi demain sera fait, alors à quoi bon essayer de l'anticiper en développant du code qui ne répond pas au besoin du moment, qui sera potentiellement inadapté dans l'avenir ainsi qu'inutile, source de complexité et de bugs maintenant ?
Je trouve beaucoup plus facile et productif de faire évoluer un code le plus simple possible en fonction des besoins, et effectivement dans ce cas, se servir des tests déjà écrits comme outils de non régression, puisqu'ils devront continuer à passer même suite à l'évolution, et écrire de nouveaux tests formalisant cette évolution et ainsi boucler la boucle.
23 De Savageman - 20/11/2010, 14:57
Je n'avais jamais vraiment réfléchi à la question, mais à première vue, la position de mageekguy me semble plus pertinente (et non, je ne dis pas ça parce que c'est son blog).
Si toutes les méthodes publiques se comportent comme il le faut, alors il n'y aura pas de bug. Un des problèmes principaux étant sans doute de bien les tester, en incluant tous les cas limites auxquels on aura pu penser.
Et en "s'affranchissant" de tester les méthodes internes, cela nous donne justement plus de temps pour écrire tous les tests pertinents sur les méthodes publiques. On reste concentrés sur ce qui est vraiment important !
On peut par contre avoir envie d'écrire des tests unitaires pour des méthodes privées pour vérifier nos algorithmes et se réconforter. D'autant plus si ces méthodes privées sont réutilisées à plusieurs endroits sensibles.
Mais je pense que ce travail fait partie du travail "interne", non visible.
24 De mikaelrandy - 20/11/2010, 18:04
@mageekguy tu te méprends sur mon commentaire.
Tout d'abord, je me demandais pourquoi débattre si tu avoues explicitement que ta méthodologie de test est "ta religion" ? Tu dis implicitement que tu auras du mal à en changer ;).
Sinon, concernant la "méthode qui en fait plus", mon idée était de dire que je blinde toujours mes méthodes privées, et je prévois même des cas qui ne sont pas sensé arriver, parce que la seule méthode publique qui l'utilise ne la met pas dans ce cas de figure.
Plutôt que d'attendre qu'une autre méthode publique la mette dans ce cas de figure, de ne plus me rappeler que la méthode privée n'est pas résistante à ce cas de figure, qui était pourtant largement prévisible dès le départ, et bien je préfère l'implémenter proprement, et j'ai donc un cas de figure où je doit tester une méthode privée/protégée.
25 De Ivan Enderlin - 20/11/2010, 19:01
Je pense que le débat se ferme. Si tu dis que c'est ta religion, alors c'est fini, aucun débat n'est possible.
Tu dis qu'aucune formalisation n'a été donnée. Je serai tenté de continuer : « aucune formalisation dans ton sens » ! Comme je te l'ai dit, c'est un problème de testabilité. Il faut couvrir le maximum de code pour éliminer la majorité des erreurs. Si tu ne testes pas tout, tu ne couvres pas tout, donc tes tests ne sont pas suffisants et ne sont finalement pas si utiles à part pour faire passer ta petite lumière au vert. Mais rappelons-le, ce n'est pas le but. Le but est de la faire passer au rouge !
Je suis également un agiliste et je teste pourtant tout. Sauf qu'il faut avoir les outils adaptés (là encore, je ne tiens pas de comparatif :-)). Exemple : quand je travaille en Java, tester le privé et le protégé devient une galère sans fin et j'arrête tout, car l'outil n'est pas adapté. Je préfère de loin JML à JUnit. Ce qui nous conduit à un autre argument que je ne voulais pas aborder dans mon premier message (on y va doucement).
À travers toutes ces discussions, on remarque que ce qui l'emporte en ta faveur chez tes partisants c'est la flemme. Il va bien falloir l'admettre :-). En résumant : « On ne teste pas le protégé et le privé car ça change trop la conception de notre code et ça demande trop de travail ». Note, je suis tout à fait d'accord avec ça.
C'est pour ça qu'il faut passer à autre chose que le test manuel. On peut par exemple aller vers le test par contrat (pour ceux qui font du Java : JML, pour ceux qui font du PHP : Praspel, présent dans Hoa_Test, mes recherches actuelles, pour ceux qui font du C : ACSL etc.).
Un contrat exprime des pré-conditions, des post-conditions, des invariants etc. sur un bloc de code, ici des méthodes. En général, les contrats sont présents sous forme d'annotations.
Ensuite, une fois le contrat écrit, on en fait ce qu'on veut. Tu as deux façons de voir les choses (en réalité, tu as presque 5 ou 7 familles, mais on va se réduire à 2 pour l'instant, pour schématiser).
Tu as ton contrat. Tu peux donc instrumenter ton code et écrire une exécution type à côté. Ton code instrumenté sera strictement le même et aura strictement le même comportement, sauf que tes contrats seront vérifiés à l'exécution (c'est un exemple, car on peut les vérifier statiquement mais c'est très dur en PHP car il est en partie nécessaire de l'inférer). Ça va permettre de détecter TOUTES les erreurs de ton programme que tu as écrit (attention, ça ne testera pas ce que tu n'as pas écrit !). C'est une première chose.
Sinon, tu as toujours ton contrat. Tu peux donc toujours instrumenter ton code. Mais cette fois-ci, au lieu d'écrire un programme qui va utiliser ton code, tu vas demander à générer des données pour tester ton code automatiquement. C'est ce qu'on peut appeler un générateur de tests du coup. Ces générateurs peuvent être aléatoire, ou respecter des contraintes. Une contrainte sur quoi ? Sur la métrique dont on a parlé par exemple : la couverture. Hop petit générateur, génère moi des valeurs pour couvrir toutes-les-branches, toutes-les-conditions, toutes-les-instructions etc. Ça revient à un (super-) solveur de contraintes.
En tant qu'agiliste, je peux t'assurer qu'un contrat est bien moins long à écrire qu'un test, il évolue avec le code et il peut déjà même être écrit dans la spécification. Si tu travailles à partir de modèles (voir le paradigme de la programmation orienté modèle, ou conduite par les modèles), ton modèle peut déjà porter les contraintes sous forme de contrats. Ainsi, tu es capable de vérifier ton modèle ET ton code (car dans ce type de paradigme, associé à l'agilité, on développe le code en parallèle du modèle, à partir des mêmes spécifications ; bref). Ça assure encore une meilleure qualité.
Mais j'ai cité deux utilisations des contrats. Et elles ne sont pas exclusives. Si tu n'arrives pas à atteindre un état de ton code avec un générateur automatique, tu peux écrire ton propre générateur (mais c'est pas simple) ou alors écrire ton propre programme pour atteindre un état très précis (ce qui est plus simple que d'écrire tes tests manuellement comme on le ferait avec une bibliothèque xUnit).
Je donne un exemple. Si on a C::f() qui prend des flottants, si pour une valeur très précise, on retourne une booléen ; pour un générateur, ce sera dur de l'atteindre (imagineons), alors on aura juste à écrire $c = new C(); $c->f(4.2); sur notre code instrumenté, et ce sera fini. Pas besoin d'assertion comme dans les bibliothèques xUnit (assertTrue, assertFalse, assertThat …). C'est plus naturel. Les contrats sont vérifiés à l'exécution et l'affaire est dans le sac.
Pour l'utilisateur, il doit apprendre à écrire un contrat, ce qui est bien moins lourd et compliqué que d'apprendre à utiliser une bibliothèque xUnit et tous les trucs et astuces qui vont avec (exemple : un contrat en JML revient à écrire du Java, rien à apprendre, mais JML n'est pas synthétique du coup, c'est un autre problème). Mais il n'a pas écrire de code également pour tester (ou très peu pour des cas très particuliers) !
Et une fois le contrat écrit, on peut l'utiliser pour plusieurs choses : simple vérification/détection des erreurs, générer des valeurs de toutes sortes (réalistes ou pas) pour vérifier automatiquement etc. On mutualise les efforts, et en tant qu'agiliste, c'est top cool.
Je me résume donc.
• Je pense que d'écrire des tests est une perte de temps face à l'écriture d'un contrat.
• Je pense que les contrats sont des outils bien plus puissants que les bibliothèques xUnit.
• Je pense que les contrats peuvent s'écrire partout, même si les méthodes protégées et privées et que c'est au programme d'instrumentalisation de se débrouiller avec (et Hoa_Test le fait).
Sans me faire trop de pub mais pour ceux qui se pose des questions : Hoa_Test est un paquetage de mon framework : Hoa (comme Frédéric l'a dit dans son billet). Ce paquetage est le résultat de mes recherches dans mon laboratoire universitaire, le LIFC (Laboratoire Informatique de Franche-Comté) et l'INRIA (qu'on ne présente plus). Mes travaux ont donné lieux à un rapport de recherche que vous pouvez lire sur le site de Hoa : <http://hoa-project.net/Litterature....
Bien sûr Frédéric, on aura sans doute besoin d'une bibliothèque xUnit pour certains cas particuliers, mais il faut alors qu'elle soit très complète et puisse assurer autant de services que les contrats si tu veux t'en sortir.
Par ailleurs, tu vois que ton opinion fait polémique. Dis toi qu'il y en aura autant dans tes utilisateurs. Il est donc sage de faire le maximum pour combler tout le monde. Rien que ça, ça devrait être suffisant pour te le faire admettre :-).
@Savageman te soutient dans ton idée. Mais il n'a pas pensé aux effets de bord j'imagine. Je vous demande d'y réfléchir simplement. Le cas échéant, je vous donnerais un exemple très simple (mais je ne vais pas poluer mon message avec du code tout de suite) qui montre bien le problème caché de ne tester que le public.
26 De mageekguy - 20/11/2010, 20:07
@mikaelrandy : J'ai hésité avant de sortir cette blague sur la religion car je redoutais ces remarques et j'avais en conséquence ajouté un smiley.
Apparemment, c'est insuffisant.
Je précise donc encore une fois que je ne limite pas ce débat à ma personne.
J'ai mon avis sur la question, que mon expérience ne remet pas en cause, et tant que ce sera le cas, je ne pense pas changer d'avis, même si je suis à l'écouter de tous les arguments de toutes les parties.
Le débat qui a lieu ici est à mes yeux plus globale.
Et pour au sujet, c'est à dire la seconde partie de ton commentaire, mon processus de développement fait que le cas que tu décris ne peut pas se produire, ou du moins, je n'ai jamais eu à gérer ce cas de figure.
J'avoue à avoir du mal à en expliquer la raison.
Je dirais que c'est en grande partie induit par le développement naïf suivi par le remaniement de code ainsi que par ma bonne maîtrise des concepts de la programmation orientée objet, mais c'est une intuition et je n'ai pas d'explication
.J'ai par contre une preuve : ma couverture de code frôle les 100% lorsque je fais du TDD, alors que je n'y fais aucunement attention, preuve que mes tests, appliquées uniquement sur des méthodes publiques, prennent totalement en compte mes méthodes privées/protégées, et cela alors même que, tout comme toi, je sécurise mes méthodes protégées vis à vis des classes filles.
27 De mageekguy - 20/11/2010, 20:20
@Ivan Enderlin : Je vais te répondre à l'inverse.
Il est bien précisé dans mon billet qu'Atoum peut potentiellement faire satisfaire les deux, et ce n'est pas pour rien.
Mais par contre, ce n'est pas parce qu'il y a débat que j'ai tort.
Quand à ton exemple, je le connais, et je t'ai déjà dis ce que j'en pense : ton test sur la méthode publique est incomplet.
Quand au contrat, je pense que tes modèles et les assertions à la xUnit sont deux facettes différentes, deux implémentations différentes, de ce concept, et qu'elles ont toute deux des forces et des faiblesses.
Dans mon cas, les faiblesses sont compensées (en partie ou en totalité, je ne peux pas le dire objectivement) par les outils annexes et la méthodologie de développement.
Et pour cette histoire de flemme, dans mon cas, ça n'a pas lieu d'être.
Maintenant, j'ai bien conscience qu'écrire des tests peut être plus qu'emmerdant pour le développeur, et j'en ai encore plus conscience depuis que j'utilise PHPUnit (sans vouloir polémiquer) professionnellement (je passerais sur l'alternative simpleTest, que j'ai utilisé professionnellement pendant plus de deux ans mais que je n'ai jamais utilisé de manière aussi poussée que PHPUnit), car écrire du test unitaire
est bien trop compliqué, surtout comparer avec la facilité offerte par Atoum, alors même qu'il n'est pas terminé.C'est d'ailleurs la raison pour laquelle j'ai développé Atoum, et la raison pour laquelle je me concentre sur l'ergonomie et la facilité d'utilisation, justement pour éviter le recours aux
dont tu parles, avec raison.Sinon, si tu as des références au sujet du débat présent sur Internet ou ailleurs, je suis preneur, car c'est dans ce sens que je parlais d’absence de formalisme, qu'il soit dans mon sens ou non, d'ailleurs.
28 De Olivier - 08/12/2010, 00:14
J'avoue que je n'ai pas lu tous les commentaires...
Voilà mon avis : les méthodes private ou protected sont censées être appelées dans des méthodes publiques. Donc si on teste les méthodes publiques, on teste bien en même temps les méthodes private/protected, non ?
Donc il n'y a pas de débat possible : dans les tests unitaires, on appelle toutes les méthodes publiques pour tester entièrement une classe.
29 De mageekguy - 08/12/2010, 10:12
@Olivier : Tu devrais lire tout les commentaires, le monde des tests n'est pas en noir et blanc.
30 De Cyrano - 16/12/2010, 00:19
J'arrive tard sur ce débat, mais j'ai le sentiment que c'est le type même de débat infini.
Personnellement je n'ai jamais utilisé les tests unitaires, d'abord parce que je ne sais pas vraiment les construire. Je suis cependant intimement convaincu de leur pertinence. Quant à tester ou non les méthodes privées/protégées, voici sommairement ma vision de néophyte en la matière, vision que je livre en vrac. Il existe à mon avis deux phases dans le développement, d'abord les outils, les classes que je qualifierais de génériques, les librairies. Ensuite, on a la seconde phase de développement des classes métier qui vont s'appuyer sur les premières.
Je conçois tout à fait bien l'idée d'encapsuler et de masquer l'implémentation des classes de librairies. Mais celui qui doit développer une des briques logicielle de ladite librairie ne va-t-il pas en fin de compte gagner du temps en testant tout systématiquement de façon à livrer au développeur de la seconde phase un code complètement testé. Par ailleurs, et c'est peut-être ce qui pourrait offrir un terrain relativement neutre, lors du développement, on peut effectivement avoir à développer des méthodes privées/protégées complexes. On peut attendre de mettre en place les méthodes publiques qui y feront appel et seront de toutes façons testées, mais si le test échoue et qu'on ne trouve pas l'erreur dans la méthode publique, il faudra bien aller voir la méthode privée/protégée qui est fautive pour réaliser qu'un test l'aurait mis en évidence dès le départ.
À première vue, j'en arrive à la conclusion que les deux écoles sont somme toute aussi logiques l'une que l'autre et que le test des méthodes privées/protégées pourrait se faire au cas par cas selon le complexité de la méthode en question. Je ne perlerais pas de «flemme», néanmoins je suis assez partisan du moindre effort et de n'utiliser un élément que si son utilité est réelle, ce qui est peut-être le plus difficile à définir...
My 2¢