Préférez-vous faire des trucs privés internes / publics pour les tests, ou utiliser une sorte de hack comme PrivateObject?

26

Je suis tout à fait un débutant dans les tests de code, et j'étais une assertputain avant. Une chose qui m'inquiète dans les tests unitaires est que cela vous oblige souvent à faire public(ou au moins internal) des champs qui auraient été privateautrement, à les défaire readonly, à faire des privateméthodes à la protected virtualplace, etc ...

J'ai récemment découvert que vous pouvez éviter cela en utilisant des choses comme la classe PrivateObject pour accéder à n'importe quoi dans un objet via la réflexion. Mais cela rend vos tests moins maintenables (les choses échoueront à l'exécution plutôt qu'à la compilation, elles seront cassées par un simple renommage, c'est plus difficile à déboguer ...). Quelle est votre opinion à ce sujet ? Quelles sont les meilleures pratiques en matière de tests unitaires concernant la restriction d'accès?

edit: considérez par exemple que vous avez une classe avec un cache dans un fichier sur le disque, et dans vos tests vous voulez écrire en mémoire à la place.

Zonko
la source
2
Les gens de python n'ont que public. Ils sont heureux. Pourquoi s'inquiéter? Pourquoi ne pas tout rendre public?
S.Lott
12
Parce que c'est loin des meilleures pratiques dans la plupart des langues les plus performantes. La vraie réponse est d'utiliser de bonnes interfaces et ainsi vous pouvez utiliser des objets fictifs pour effectuer des tests.
Rig
2
@Rig: Python n'est pas une langue top? Intéressant.
S.Lott
7
«il est loin des meilleures pratiques dans la majorité des langages les plus performants» n'implique pas logiquement (ni même fait allusion à cela) «Python n'est pas un langage top».
Mike Nakis
1
Précisément, c'est un langage top mais la plupart des langages top ne sont pas Python et approuvent la définition de la portée appropriée. Je maintiens ma déclaration. Il existe des modèles conçus pour créer des logiciels hautement testables tout en garantissant que les variables conservent leur portée. Même les programmeurs Python ont tendance à émuler la portée avec des préfixes de ce que j'ai vu.
Rig

Réponses:

36

Vous ne devriez jamais avoir à

faire public(ou au moins internal) des champs qui auraient été privateautrement, pour les détacher readonly, faire des privateméthodes à la protected virtualplace

En particulier, la non-ré-liaison d'un champ peut rendre un objet immuable mutable, et ce serait un désastre.

Vos tests doivent considérer vos objets comme des boîtes noires ( Wikipedia ), ce qui signifie qu'ils ne doivent s'intéresser qu'à l'interface publique des objets, pas aux détails de leur implémentation.

Si un objet ne peut pas être suffisamment testé à l'aide de son interface publique, vous devez trouver des moyens de fournir des extensions formelles et utiles à son interface qui faciliteraient les tests. Par exemple, un système d'enregistrement avec seulement une paire de méthodes Register / Deregister bénéficierait peut-être d'une méthode IsRegistered même si elle n'est pas nécessaire pour l'application en question; néanmoins, il s'agit d'une extension formelle et utile de l'interface, et elle pourra d'ailleurs accueillir des tests.

L'important est que la modification de l'implémentation de l'objet ne devrait pas vous obliger à modifier le test unitaire. En théorie, vous devriez pouvoir écrire le test unitaire une fois, puis demander à plusieurs programmeurs de coder plusieurs implémentations entièrement différentes de l'objet à tester pour fonctionner avec le test unitaire.

Mike Nakis
la source
2
Exactement! Si vous ne pouvez pas le tester à l'unité sans réflexion ni hacks, vous avez probablement une erreur dans votre API ou votre conception.
Falcon
Voulez-vous dire que l'élaboration de privateméthodes protected virtualpour aider à se moquer des tests est considérée comme une mauvaise pratique?
Robula
1
@Robula Dans mon livre, oui. Je n'écris même pas mes tests dans le même package que le code de production, car je n'ai pas la possibilité d'accéder à des membres non publics. Je n'utilise pas non plus de simulacres. Bien sûr, si vous devez utiliser des simulations, vous devrez faire tout ce qu'il faut pour faciliter la simulation, de sorte que le virtuel protégé peut être un compromis pratique dans de nombreuses situations. Mais idéalement, une conception n'a pas besoin de simulations, seulement des implémentations alternatives, et idéalement, les tests sont des tests en boîte noire, pas des tests en boîte blanche, donc les choses sont testées en intégration, sans simulations.
Mike Nakis
13

En C #, vous pouvez utiliser le InternalsVisibleToAttributepour permettre à votre assembly de test de voir les internalclasses dans l'assembly que vous testez. On dirait que vous le savez déjà.

Dans la plupart des cas, je ne souhaite tester que l'API publique de mon assembly. Dans le cas où je veux faire des tests en boîte blanche d'une unité, je déplace le code dans une internalclasse et j'en fais une publicméthode de cette classe. Ensuite, je peux coder un test unitaire pour cette classe.

Cela se prête bien à l'injection de dépendance et au modèle de stratégie. Vous pouvez résumer la méthode dans une interface, injecter l'interface dans le constructeur de votre objet parent et tester simplement que le parent délègue de manière appropriée. Ensuite, vous pouvez tester que l'objet enfant se comporte correctement. Cela rend tout plus modulaire.

Scott Whitlock
la source
8

D'après mon expérience, il est préférable de ne pas exposer les internes de votre classe spécialement pour le test. Même si cela peut sembler faciliter l'écriture du test. Le test est le premier client de votre classe, donc s'il est difficile de tester votre classe via son interface publique, il sera probablement difficile d'utiliser votre classe dans d'autres endroits également.

La facilité avec laquelle la classe est testée via son API publique est un retour sur la facilité d'utilisation de la classe. Considérez les tests partiellement comme un moyen de prototyper l'utilisation de votre classe.

Essayez de déterminer pourquoi votre test doit détecter quelque chose sur la classe qui n'est pas disponible via l'API publique. Peut-être que votre classe en fait trop et doit plutôt être décomposée en un groupe de classes coopérantes? Ensuite, vous pouvez vous moquer de certaines des classes collaboratrices pour détecter ce que fait la classe testée.

Essayez d'appliquer le principe d'inversion de dépendance et introduisez des interfaces entre vos classes que vous pouvez simuler dans vos tests. Vous pouvez fournir les collaborateurs à votre test en utilisant l' inversion de contrôle .

Pensez également à appliquer le principe «ne demandez pas» pour aider à structurer votre interface de classe d'une manière robuste et faiblement couplée, où les bonnes informations sont exposées et le reste est masqué.

pingouin flamant
la source
6

Personnellement, je préfère laisser le code que je teste aussi pur que possible et si le code de test a besoin de hacks (et en pointeurs C ++, etc.), je suis heureux de le faire.

Fourmi
la source
1
Je suis d'accord, c'est la façon de procéder. Gardez la laideur dans le code de test, où il ne peut rien faire de mal.
Alan Delimon
@AlanDelimon Cela ne pourrait-il pas nuire à l'esprit des programmeurs de maintenance ultérieurs?
flamingpenguin
1
Le code laid de @flamingpenguin peut blesser les programmeurs n'importe où; mais un code de test laid fera probablement moins de mal car il n'affectera que les personnes qui modifient la classe et pas aussi les personnes qui la consomment simplement.
Dan Neely
2
@flamingpenguin C'est possible, mais si vous devez mettre du code laid quelque part, il pourrait aussi bien être dans un test unitaire où il est moins susceptible d'avoir besoin de modifications et de faire partie de l'application.
Alan Delimon
5

La visibilité de votre code n'a rien à voir avec vos tests unitaires!

Vous rendez vos méthodes privées et non dans le but de les cacher des tests et vous ne les rendez pas publiques pour les exposer pour le test, non? C'est votre implémentation qui est essentielle ici, pas les tests - ces serveurs comme preuve de votre code, mais ils ne sont pas votre code .

Il existe de nombreuses façons d'activer l'accès aux méthodes et propriétés cachées (privées ou internes). De nombreux frameworks de test et IDE (par exemple, Visual Studio) vous soutiennent ici en générant tout le nécessaire pour l'accès, vous n'avez qu'à créer vos cas de test. Alors, pourquoi t'inquiéter? Mieux vaut garder votre code propre et soigné.

Alexander Galkin
la source