Comment éviter la nécessité de tester unitairement les méthodes privées


15

Je sais que vous n'êtes pas censé tester des méthodes privées, et s'il semble que vous en ayez besoin, il pourrait y avoir une classe en attente de sortie.

Mais, je ne veux pas avoir de classes gazillion juste pour pouvoir tester leurs interfaces publiques et je trouve que pour de nombreuses classes si je teste juste les méthodes publiques, je finis par devoir me moquer de beaucoup de dépendances et les tests unitaires sont énorme et difficile à suivre.

Je préfère de beaucoup se moquer des méthodes privées lors du test des méthodes publiques et se moquer des dépendances externes lors du test des méthodes privées.

Suis-je fou?


Un test unitaire approfondi devrait implicitement couvrir tous les membres privés d'une classe donnée, car bien que vous ne puissiez pas les appeler directement, leur comportement aura toujours un effet sur la sortie. S'ils ne le font pas, alors pourquoi sont-ils là en premier lieu? Rappelez-vous lors des tests unitaires que vous vous souciez du résultat, et non de la manière dont le résultat a été obtenu.
GordonM

Réponses:


24

Vous avez partiellement raison - vous ne devriez pas tester directement des méthodes privées. Les méthodes privées d'une classe doivent être appelées par une ou plusieurs des méthodes publiques (peut-être indirectement - une méthode privée appelée par une méthode publique peut invoquer d'autres méthodes privées). Par conséquent, lors du test de vos méthodes publiques, vous testerez également vos méthodes privées. Si vous disposez de méthodes privées qui n'ont pas été testées, vos cas de test sont insuffisants ou les méthodes privées sont inutilisées et peuvent être supprimées.

Si vous adoptez une approche de test en boîte blanche, vous devez considérer les détails d'implémentation de vos méthodes privées lors de la construction de tests unitaires autour de vos méthodes publiques. Si vous adoptez une approche de boîte noire, vous ne devez pas tester les détails d'implémentation dans les méthodes publiques ou privées, mais contre le comportement attendu.

Personnellement, je préfère une approche en boîte blanche aux tests unitaires. Je peux créer des tests pour mettre les méthodes et les classes sous test dans différents états qui provoquent un comportement intéressant dans mes méthodes publiques et privées, puis affirmer que les résultats sont ce que j'attends.

Alors - ne vous moquez pas de vos méthodes privées. Utilisez-les pour comprendre ce que vous devez tester afin de fournir une bonne couverture des fonctionnalités que vous fournissez. Cela est particulièrement vrai au niveau du test unitaire.


1
"ne vous moquez pas de vos méthodes privées" ouais, je peux comprendre les tests, quand vous vous moquez d'eux, vous avez peut-être franchi la ligne fine pour devenir fou
Ewan

Oui, mais comme je l'ai dit, mon problème avec les tests en boîte blanche est que le fait de se moquer généralement de toutes les dépendances pour les méthodes publiques et privées crée de très grandes méthodes de test. Avez-vous des idées pour y remédier?
Fran Sevillano

8
@FranSevillano Si vous devez vous moquer ou vous moquer autant, je regarderais votre conception globale. Quelque chose se sent mal.
Thomas Owens

Un outil de couverture de code aide à cela.
Andy

5
@FranSevillano: Il n'y a pas beaucoup de bonnes raisons pour qu'une classe fasse une chose d'avoir des tonnes de dépendances. Si vous avez des tonnes de dépendances, vous avez probablement une classe Dieu.
Mooing Duck

4

Je pense que c'est une très mauvaise idée.

Le problème avec la création de tests unitaires de membres privés, c'est qu'elle s'intègre mal avec le cycle de vie du produit.

La raison pour laquelle vous avez CHOISI de rendre ces méthodes privées est qu'elles ne sont pas au cœur de ce que vos classes essaient de faire - juste des aides sur la façon dont vous implémentez actuellement cette fonctionnalité. En refactorisant, ces détails privés sont susceptibles de changer et provoqueront alors des frictions avec le refactoring.

En outre, une différence clé entre les membres publics et privés est que vous devez bien réfléchir à votre API publique, la documenter correctement et la vérifier correctement (assertions, etc.). Mais avec une API privée, il serait inutile de réfléchir aussi soigneusement (un effort inutile puisque son utilisation est tellement localisée). Mettre des méthodes privées dans des tests unitaires revient à créer des dépendances externes sur ces méthodes. Cela signifie que l'API doit être stable et bien documentée (car quelqu'un doit comprendre pourquoi ces tests unitaires ont échoué si / quand ils le font).

Je vous suggère:

  • REPENSER si ces méthodes doivent être privées
  • Écrivez des tests ponctuels (juste pour vous assurer que c'est correct maintenant, rendez-le public temporairement pour pouvoir tester, puis supprimez le test)
  • Construit des tests conditionnels dans votre implémentation à l'aide de #ifdef et d'assertions (les tests ne sont effectués que dans les versions de débogage).

A appréciez votre envie de tester cette fonctionnalité et qu'il est difficile de la tester via votre API publique actuelle. Mais je préfère personnellement la modularité à la couverture des tests.


6
Je ne suis pas d'accord. OMI, c'est 100% correct pour les tests d'intégration. Mais avec les tests unitaires, les choses sont différentes; Le but des tests unitaires est de déterminer où se trouve un bogue, suffisamment étroit pour que vous puissiez le corriger rapidement. Je me retrouve souvent dans cette situation: j'ai très peu de méthodes publiques car c'est vraiment la valeur fondamentale de mes cours (comme vous l'avez dit, il faut le faire). Cependant, j'évite également d'écrire des méthodes de 400 lignes, ni publiques ni privées. Donc, mes quelques méthodes publiques ne peuvent atteindre leur objectif qu'avec l'aide de dizaines de méthodes privées. C'est trop de code pour "le réparer rapidement". Je dois démarrer le débogueur, etc.
marstato

6
@marstato: suggestion: commencez par écrire des tests et changez d'avis sur les tests non effectués: ils ne trouvent pas de bugs, mais vérifient que le code fonctionne comme le développeur le souhaitait.
Timothy Truckle

@marstato Merci! Oui. Les tests passent toujours la première fois, lorsque vous enregistrez des éléments (ou vous ne les auriez pas enregistrés!). Ils sont utiles, car vous faites évoluer le code, vous donnant un avertissement lorsque vous cassez quelque chose, et si vous avez de BONS tests de régression, alors ils vous donnent CONFORT / GUIDAGE sur où chercher les problèmes (pas dans le truc qui est stable) et bien testé par régression).
Lewis Pringle

@marstato "Le but des tests unitaires est de déterminer où se trouve un bug" - C'est exactement le malentendu qui mène à la question du PO. L'objectif des tests unitaires est de vérifier le comportement prévu (et de préférence documenté) d'une API.
StackOverthrow

4
@marstato Le nom "test d'intégration" vient du fait de tester que plusieurs composants fonctionnent ensemble (c'est-à-dire qu'ils s'intègrent correctement). Les tests unitaires testent un composant unique qui fait ce qu'il est censé faire de manière isolée, ce qui signifie essentiellement que son API publique fonctionne comme documentée / requise. Aucun de ces termes ne dit rien de savoir si vous incluez des tests de la logique de mise en œuvre interne une partie de veiller à ce que l'intégration fonctionne, ou que les simples travaux unitaires.
Ben

3

Les tests unitaires testent le comportement public observable , pas le code, où "public" signifie: valeurs de retour et communication avec les dépendances.

Une "unité" est n'importe quel code, qui résout le même problème (ou plus précisément: a la même raison de changer). Cela peut être une méthode unique ou un groupe de classes.

La raison principale pour laquelle vous ne voulez pas tester private methodsest: ce sont des détails d'implémentation et vous voudrez peut-être les changer pendant le refactoring (améliorez votre code en appliquant les principes OO sans changer la fonctionnalité). C'est exactement lorsque vous ne voulez pas que vos tests ne changent pas afin qu'ils puissent garantir que le comportement de votre CuT n'a pas changé pendant le refactoring.

Mais, je ne veux pas avoir de classes gazillion juste pour pouvoir tester leurs interfaces publiques et je trouve que pour de nombreuses classes si je teste juste les méthodes publiques, je finis par devoir me moquer de beaucoup de dépendances et les tests unitaires sont énorme et difficile à suivre.

J'expérimente généralement le contraire: plus les classes sont petites (moins elles ont de responsabilités), moins elles ont de dépendances, et plus il est facile à la fois d'écrire et de lire.

Idéalement, vous appliquez le même niveau de modèle d' abstraction à vos classes. Cela signifie que vos classes fournissent soit une logique métier (de préférence sous forme de "fonctions pures" travaillant uniquement sur leurs paramètres sans maintenir un état propre) (x), soit des méthodes d'appel sur d'autres objets, pas les deux en même temps.

De cette façon , il n'est pas nécessaire de tester le comportement de l'entreprise et les objets de «délégation» sont généralement trop simples pour échouer (pas de branchement, pas de changement d'état) de sorte qu'aucune interruption ne soit nécessaire et que leurs tests puissent être laissés à des tests d' intégration ou de module.


1

Les tests unitaires ont une certaine valeur lorsque vous êtes le seul programmeur à travailler sur le code, surtout si l'application est très grande ou très complexe. Lorsque les tests unitaires deviennent essentiels, c'est lorsque vous avez un plus grand nombre de programmeurs travaillant sur la même base de code. Le concept de tests unitaires a été introduit pour résoudre certaines des difficultés de travail dans ces grandes équipes.

La raison pour laquelle les tests unitaires aident les équipes plus importantes est liée aux contrats. Si mon code effectue des appels à du code écrit par quelqu'un d'autre, je fais des hypothèses sur ce que le code de l'autre personne va faire dans diverses situations. À condition que ces hypothèses soient toujours vraies, mon code fonctionnera toujours, mais comment puis-je savoir quelles hypothèses sont valides et comment savoir quand ces hypothèses ont changé?

C'est là qu'interviennent les tests unitaires. L'auteur d'une classe crée des tests unitaires pour documenter le comportement attendu de leur classe. Le test unitaire définit toutes les manières valides d'utiliser la classe et l'exécution du test unitaire valide le fonctionnement de ces cas d'utilisation comme prévu. Un autre programmeur qui souhaite utiliser votre classe peut lire vos tests unitaires pour comprendre le comportement qu'ils peuvent attendre de votre classe, et l'utiliser comme base pour leurs hypothèses sur le fonctionnement de votre classe.

De cette façon, les signatures de méthode publique de la classe et les tests unitaires forment ensemble un contrat entre l'auteur de la classe et les autres programmeurs qui utilisent cette classe dans leur code.

Dans ce scénario, que se passe-t-il si vous incluez des tests de méthodes privées? De toute évidence, cela n'a aucun sens.

Si vous êtes le seul programmeur à travailler sur votre code et que vous souhaitez utiliser les tests unitaires comme moyen de déboguer votre code, je ne vois aucun inconvénient à cela, c'est juste un outil, et vous pouvez l'utiliser de toute façon qui fonctionne pour vous, mais ce n'était pas la raison pour laquelle les tests unitaires ont été introduits, et ne fournit pas les principaux avantages des tests unitaires.


Je lutte avec cela car je moque les dépendances de telle sorte que si une dépendance change son comportement, mon test n'échouera pas. Cet échec est-il censé se produire dans un test d'intégration et non dans le test unitaire?
Todd

La maquette est censée se comporter de manière identique à la mise en œuvre réelle, mais n'a pas de dépendances. Si l'implémentation réelle change de sorte qu'ils sont maintenant différents, cela apparaîtra comme un échec du test d'intégration. Personnellement, je considère que le développement de simulations et la mise en œuvre réelle à faire sont une tâche. Je ne crée pas de maquette dans le cadre de l'écriture de tests unitaires. De cette façon, lorsque je modifie le comportement de mes classes et modifie la maquette pour qu'elle corresponde, l'exécution des tests unitaires identifiera toutes les autres classes qui ont été rompues par ce changement.
bikeman868

1

Avant de répondre à une telle question, vous devez décider de ce que vous voulez réellement réaliser.

Vous écrivez du code. Vous espérez qu'il remplit son contrat (en d'autres termes, il fait ce qu'il est censé faire. Écrire ce qu'il est censé faire est un grand pas en avant pour certaines personnes).

Pour être raisonnablement convaincu que le code fait ce qu'il est censé faire, soit vous le regardez suffisamment longtemps, soit vous écrivez du code de test qui teste suffisamment de cas pour vous convaincre "si le code passe tous ces tests, alors il est correct".

Souvent, vous n'êtes intéressé que par l'interface définie publiquement d'un code. Si j'utilise votre bibliothèque, je ne me soucie comment vous l' avez fait correctement, seulement qu'il fait correctement. Je vérifie que votre bibliothèque est correcte en effectuant des tests unitaires.

Mais vous créez la bibliothèque. Le faire fonctionner correctement peut être difficile à réaliser. Disons que je me soucie seulement que la bibliothèque fasse correctement l'opération X, j'ai donc un test unitaire pour X. Vous, le développeur responsable de la création de la bibliothèque, implémentez X en combinant les étapes A, B et C, qui sont chacune totalement non triviales. Pour faire fonctionner votre bibliothèque, vous ajoutez des tests pour vérifier que A, B et C fonctionnent correctement. Vous voulez ces tests. Dire "vous ne devriez pas avoir de tests unitaires pour les méthodes privées" est tout à fait inutile. Vous voulez des tests pour ces méthodes privées. Peut-être que quelqu'un vous dit que les tests unitaires de méthodes privées sont faux. Mais cela signifie seulement que vous pourriez ne pas les appeler des "tests unitaires" mais des "tests privés" ou tout ce que vous voulez les appeler.

Le langage Swift résout le problème que vous ne voulez pas exposer A, B, C en tant que méthodes publiques juste parce que vous voulez le tester en donnant aux fonctions un attribut "testable". Le compilateur permet d'appeler des méthodes testables privées à partir de tests unitaires, mais pas à partir de code non test.


0

Oui, vous êtes fou .... COMME UN RENARD!

Il existe plusieurs façons de tester des méthodes privées, dont certaines dépendent de la langue.

  • réflexion! les règles sont faites pour être enfreintes!
  • les faire protéger, hériter et remplacer
  • ami / InternalsVisibleTo classes

Dans l'ensemble, cependant, si vous souhaitez tester des méthodes privées, vous souhaiterez probablement les déplacer vers des méthodes publiques sur une dépendance et tester / injecter cela.


Je ne sais pas, à mes yeux, vous avez expliqué que ce n'était pas une bonne idée mais avez continué de répondre à la question
Liath

Je pensais que j'avais toutes les bases couvertes :(
Ewan

3
J'ai voté positivement, car l'idée d'exposer vos méthodes d'assistance privée à l'API publique juste pour les tester est folle, et j'ai toujours pensé cela.
Robert Harvey

0

Restez pragmatique. Tester des cas spéciaux dans des méthodes privées en définissant l'état de l'instance et les paramètres d'une méthode publique de sorte que ces cas se produisent là-bas, est souvent beaucoup trop compliqué.

J'ajoute un internalaccesseur supplémentaire (avec le flag InternalsVisibleTol'assembly de test), clairement nommé DoSomethingForTesting(parameters), afin de tester ces méthodes "privées".

Bien sûr, l'implémentation peut changer quelquefois, et ces tests, y compris les accesseurs de test, deviennent obsolètes. C'est encore mieux que les cas non testés ou les tests illisibles.

En utilisant notre site, vous reconnaissez avoir lu et compris notre politique liée aux cookies et notre politique de confidentialité.
Licensed under cc by-sa 3.0 with attribution required.