Mon premier billet sur les tests présente le concept et comment, avec peu d’effort, vous pouvez améliorer la validation que vous faites déjà. Mais le code présenté a des défauts et celui sur lequel je souhaite insister en premier relève de la question de quoi tester. En particulier pour le langage Python qui a une manière particulière de gérer la visibilité des attributs.

Notez que si l’exemple présenté ici repose sur une particularité du langage Python, le concept s’applique à tous les langages.

Rappel du contexte

Pour rappel, l’exemple était une gestion de listes. Un des tests concerne la validation que l’ajout d’un élément ajoute bien cet élément. Pour ce faire, l’assertion vérifie l’état de l’attribut _units. Or, cet attribut est privé. Oui, en Python tout est public et on est tenté d’utiliser cette propriété dans un cas comme celui-ci.

Le test d’ajout d’élément était donc le suivant :

Pourquoi un attribut est-il privé ?

Définir un attribut comme privé (je parle ici de Python, langage pour lequel la notion d’attribut concerne aussi bien les attributs que le méthodes qui sont des attributs callable) sert avant tout à mettre en œuvre le principe de l’encapsulation, c’est à dire de masquer l’implémentation. Masquer ne signifie pas dissimuler, surtout pour un langage interprété. Si on souhaite masquer l’implémentation, c’est pour pouvoir faire évoluer cette implémentation sans casser l’usage de notre code.

Ainsi, dans notre implémentation, _units est une liste, la liste des *unités*. Imaginons maintenant une évolution possible : certaines unités peuvent exister en plusieurs exemplaires (on peut avoir plusieurs tactical squads) mais pas toutes (un personnage est unique). Du point de vue signature de la méthode, on peut ajouter une notion d’unicité qui sera un attribut optionnel avec une valeur par défaut à False.

Au sein de notre code, la gestion de la liste peut maintenant se faire non plus par une liste mais deux, stockées dans un dictionnaire. La clef sera un booléen signifiant si l’élément est unique.

Le code de l’objet de gestion de la liste pourrait évoluer dans ce sens (seul le code d’intérêt est produit ici et il n’y a pas de validation de paramètre) :

Si nous rejouons les tests, l’ajout des tâches est toujours d’actualité. Mais la validation, qui vérifie la taille de l’attribut _tasks considère maintenant l’état comme faux…

Vérifiez l’état, pas l’implémentation

Vous voyez que le non respect du principe de l’encapsulation a des effets tout aussi néfastes sur les tests que sur du code fonctionnel. Pour vérifier le bon fonctionnement de la méthode d’ajout, nous avons évidemment besoin d’observer quelque chose. Ce quelque chose peut avoir plusieurs formes : l’objet Roster peut être implanté comme une collection avec une méthode spéciale len, proposer une property units retournant la liste de toutes les unités ou une méthode dédiée.

Personnellement, j’opterai pour la première option, ce qui conduira une implémentation initiale suivante

à devenir

Et non, le sum de listes n’est pas à faire mais pour présenter simplement l’intention il fera l’affaire.

Mais le test aurai aussi été en échec…

Oui, c’est exacte. Mais il y a une différence fondamentale dans ce que cela signifie. Dans le cas original, le test est en échec car l’implémentation a changée. Il faut donc adapter le test à la nouvelle implantation. Le test est maintenant en échec parce que nous avons une régression dans notre code. En modifiant l’attribut, la méthode spéciale doit évoluer et le test remplit son rôle en mettant cela en évidence.

En choisissant correctement vos tests, vous vous protégez donc contre la régression. Pour être efficace, un test doit amener un système dans un certain état et valider que cet état correspond à un attendu à l’aide d’observables.

Attention sur le choix des observables. Je lis régulièrement qu’un code soit être testable. La perversité est d’adapter le code au test. Dans le cas présent, il ne faut pas ajouter d’accesseur parce que vous en ressentez le besoin pour les tests. Tout attribut et méthode doivent être définis pour un besoin fonctionnel. Ce que vous devez tester, c’est le bon fonctionnement de votre code pour les services qu’il doit vous rendre et donc pour l’objet, par les interfaces publiques.

Ceci étant précisé, la prochaine étape concernera l’organisation du code.

À propos de... Darko Stankovski

iT guy, photographe et papa 3.0, je vous fais partager mon expérience et découvertes dans ces domaines. Vous pouvez me suivre sur les liens ci-dessous.