Question Comment testez-vous les méthodes privées?


Je construis une bibliothèque de classes qui aura des méthodes publiques et privées. Je veux être capable de tester les méthodes privées (surtout lors du développement, mais cela pourrait aussi être utile pour le futur refactoring).

Quelle est la bonne façon de faire cela?


452
2017-10-30 15:49


origine


Réponses:


Si vous utilisez .net, vous devez utiliser le InternalsVisibleToAttribute.


113
2017-10-30 15:52



Si vous voulez tester une méthode privée, quelque chose peut être faux. Les tests unitaires sont généralement destinés à tester l'interface d'une classe, c'est-à-dire ses méthodes publiques (et protégées). Vous pouvez bien sûr "bidouiller" une solution (même en rendant les méthodes publiques), mais vous pouvez également envisager:

  1. Si la méthode que vous souhaitez tester vaut vraiment la peine d'être testée, il peut être utile de la déplacer dans sa propre classe.
  2. Ajoutez d'autres tests aux méthodes publiques appelant la méthode privée, en testant la fonctionnalité de la méthode privée. (Comme les commentateurs l’ont indiqué, vous ne devriez le faire que si la fonctionnalité de ces méthodes privées fait vraiment partie de l’interface publique. Si elles exécutent des fonctions cachées à l’utilisateur (test unitaire), cela est probablement mauvais).

335
2017-10-30 15:54



Il pourrait ne pas être utile de tester des méthodes privées. Cependant, j'aime aussi parfois appeler des méthodes privées à partir de méthodes de test. La plupart du temps afin d'éviter la duplication de code pour la génération de données de test ...

Microsoft fournit deux mécanismes pour cela:

Accesseurs

  • Aller au code source de la définition de classe
  • Faites un clic droit sur le nom de la classe
  • Choisissez "Créer un accesseur privé"
  • Choisissez le projet dans lequel l'accesseur doit être créé => Vous allez vous retrouver avec une nouvelle classe avec le nom foo_accessor. Cette classe sera générée dynamiquement lors de la compilation et privera tous les membres du public.

Cependant, le mécanisme est parfois un peu compliqué en ce qui concerne les modifications de l'interface de la classe d'origine. Donc, la plupart du temps, j'évite d'utiliser ça.

Classe PrivateObject L'autre façon est d'utiliser Microsoft.VisualStudio.TestTools.UnitTesting.PrivateObject

// Wrap an already existing instance
PrivateObject accessor = new PrivateObject( objectInstanceToBeWrapped );

// Retrieve a private field
MyReturnType accessiblePrivateField = (MyReturnType) accessor.GetField( "privateFieldName" );

// Call a private method
accessor.Invoke( "PrivateMethodName", new Object[] {/* ... */} );

112
2017-10-29 13:50



Je ne suis pas d'accord avec la philosophie "vous ne devriez être intéressé que par le test de l'interface externe". C'est un peu comme dire qu'un atelier de réparation automobile ne devrait avoir que des tests pour voir si les roues tournent. Oui, en fin de compte je m'intéresse au comportement externe mais j'aime que mes propres tests internes et privés soient un peu plus précis et précis. Oui, si je refactore, il se peut que je doive modifier certains tests, mais à moins que ce ne soit un refactoring massif, je n’en changerai que quelques-uns et le fait que les autres tests internes (inchangés) fonctionnent toujours est un excellent indicateur le refactoring a été réussi.

Vous pouvez essayer de couvrir tous les cas internes en utilisant uniquement l'interface publique et, théoriquement, il est possible de tester toutes les méthodes internes (ou au moins toutes les questions importantes) en utilisant l'interface publique, mais vous devrez peut-être Ceci et la connexion entre les cas de test qui passent par l'interface publique et la partie interne de la solution qu'ils sont conçus pour tester peuvent être difficiles ou impossibles à discerner. Après avoir souligné, les tests individuels qui garantissent que la machinerie interne fonctionne correctement vaut bien les changements de test mineurs qui viennent avec refactoring - du moins c'est mon expérience. Si vous devez apporter d'importants changements à vos tests pour chaque refactoring, alors cela n'a peut-être aucun sens, mais dans ce cas, vous devriez peut-être repenser entièrement votre conception. Un bon design doit être suffisamment flexible pour permettre la plupart des changements sans modifications majeures.


73
2017-10-19 06:34



Dans les rares cas où j'ai voulu tester des fonctions privées, je les ai généralement modifiées pour être protégées à la place, et j'ai écrit une sous-classe avec une fonction wrapper publique.

La classe:

...

protected void APrivateFunction()
{
    ...
}

...

Sous-classe pour les tests:

...

[Test]
public void TestAPrivateFunction()
{
    APrivateFunction();
    //or whatever testing code you want here
}

...

50
2017-10-30 16:04



Je pense qu'une question plus fondamentale devrait être posée: pourquoi essayez-vous de tester la méthode privée en premier lieu. C'est une odeur de code que vous essayez de tester la méthode privée via l'interface publique de cette classe alors que cette méthode est privée pour une raison car c'est un détail d'implémentation. On devrait seulement se préoccuper du comportement de l'interface publique et non de la façon dont elle est implémentée sous les couvertures.

Si je veux tester le comportement de la méthode privée, en utilisant des refactorings communs, je peux extraire son code dans une autre classe (peut-être avec une visibilité au niveau du package afin de s'assurer qu'il ne fait pas partie d'une API publique). Je peux ensuite tester son comportement isolément.

Le produit du refactoring signifie que la méthode privée est maintenant une classe distincte qui est devenue un collaborateur de la classe d'origine. Son comportement sera bien compris grâce à ses propres tests unitaires.

Je peux alors me moquer de son comportement lorsque j'essaie de tester la classe d'origine pour pouvoir ensuite me concentrer sur le test du comportement de l'interface publique de cette classe plutôt que de devoir tester une explosion combinatoire de l'interface publique et le comportement de toutes ses méthodes privées. .

Je vois cela comme conduire une voiture. Quand je conduis une voiture, je ne conduis pas avec le capot en place pour voir que le moteur fonctionne. Je me fie à l'interface fournie par la voiture, à savoir le compte-tours et l'indicateur de vitesse pour savoir si le moteur fonctionne. Je compte sur le fait que la voiture bouge quand j'appuie sur la pédale d'accélérateur. Si je veux tester le moteur, je peux faire des vérifications isolées. :RÉ

Bien sûr, tester directement des méthodes privées peut être un dernier recours si vous avez une application héritée, mais je préférerais que le code existant soit refait pour permettre de meilleurs tests. Michael Feathers a écrit un excellent livre sur ce sujet. http://www.amazon.co.uk/Working-Effectively-Legacy-Robert-Martin/dp/0131177052


22
2017-10-24 16:30



Les types privés, les internes et les membres privés le sont pour une raison ou pour une autre, et souvent vous ne voulez pas les toucher directement. Et si vous le faites, les chances sont que vous vous casserez plus tard, car rien ne garantit que les créateurs de ces assemblées conserveront les implémentations privées / internes en tant que telles.

Mais, parfois, lorsque je fais du piratage / exploration d'assemblages compilés ou tiers, j'ai moi-même fini par vouloir initialiser une classe privée ou une classe avec un constructeur privé ou interne. Ou, parfois, lorsque je ne peux pas changer les bibliothèques héritées pré-compilées, je finis par écrire des tests contre une méthode privée.

Ainsi est né le AccessPrivateWrapper - http://amazedsaint.blogspot.com/2010/05/accessprivatewrapper-c-40-dynamic.html - C'est une classe wrapper rapide qui rendra le travail facile en utilisant les fonctionnalités dynamiques C # 4.0 et la réflexion.

Vous pouvez créer des types internes / privés comme

    //Note that the wrapper is dynamic
    dynamic wrapper = AccessPrivateWrapper.FromType
        (typeof(SomeKnownClass).Assembly,"ClassWithPrivateConstructor");

    //Access the private members
    wrapper.PrivateMethodInPrivateClass();

17
2018-05-25 07:44