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


479

Je construis une bibliothèque de classe qui aura des méthodes publiques et privées. Je veux pouvoir tester de manière unitaire les méthodes privées (principalement pendant le développement, mais cela pourrait également être utile pour une refactorisation future).

Quelle est la bonne façon de procéder?


3
Il me manque peut-être quelque chose, ou peut-être est-ce simplement que cette question est, eh bien ... pre-historicen termes d'années Internet, mais les tests unitaires des méthodes privées sont maintenant à la fois simples et directs, Visual Studio produisant les classes d'accesseurs nécessaires en cas de besoin et pré-remplir la logique des tests avec des extraits de code sacrément proches de ce que l'on peut souhaiter pour des tests fonctionnels simples. Voir par exemple. msdn.microsoft.com/en-us/library/ms184807%28VS.90%29.aspx
mjv

3
Cela semble être un double de stackoverflow.com/questions/34571/… .
Raedwald

Le questionneur n'utilise peut-être pas Visual Studio
Dave

3
Ne pas tester les éléments internes: blog.ploeh.dk/2015/09/22/unit-testing-internals
Mark Seemann

Réponses:


122

Si vous utilisez .net, vous devez utiliser InternalsVisibleToAttribute .


86
Beurk. Cela est compilé dans vos assemblys publiés.
Jay

14
@Jay - ne pourrait-on pas utiliser #if DEBUGl' InternalsVisibleToattribut pour qu'il ne s'applique pas au code de version?
mpontillo

20
@Mike, vous pourriez, mais alors vous ne pouvez exécuter que des tests unitaires sur le code de débogage, pas le code de publication. Étant donné que le code de version est optimisé, vous pouvez voir un comportement différent et des horaires différents. Dans le code multithread, cela signifie que vos tests unitaires ne détecteront pas correctement les conditions de concurrence. Il vaut mieux utiliser la réflexion via la suggestion de @ AmazedSaint ci-dessous ou utiliser le PrivateObject / PrivateType intégré. Cela vous permet de voir les données privées dans les versions Release en supposant que votre faisceau de test fonctionne en toute confiance (ce que MSTest exécute localement)
Jay

121
Qu'est-ce que je rate? Pourquoi serait-ce la réponse acceptée alors qu'elle ne répond pas réellement à la question spécifique des tests de méthodes privées? InternalsVisibleTo expose uniquement les méthodes marquées comme internes et non celles marquées privées comme demandé par l'OP (et la raison pour laquelle j'ai atterri ici). Je suppose que je dois continuer à utiliser PrivateObject comme l'a répondu Seven?
Darren Lewis

6
@Jay, je sais que c'est un peu tard, mais une option consiste à utiliser quelque chose comme #if RELEASE_TESTautour InternalsVisibleTocomme Mike le suggère, et à faire une copie de la configuration de votre version qui définit RELEASE_TEST. Vous pouvez tester votre code de version avec des optimisations, mais lorsque vous construisez réellement pour la version, vos tests seront omis.
Shaz

349

Si vous souhaitez tester unitaire une méthode privée, quelque chose ne va peut-être pas. Les tests unitaires sont (en général) destinés à tester l'interface d'une classe, c'est-à-dire ses méthodes publiques (et protégées). Vous pouvez bien sûr "pirater" une solution à cela (même si ce n'est qu'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 plus de tests aux méthodes publiques qui appellent la méthode privée, 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.

38
L'option 2 oblige les tests unitaires à connaître l'implémentation sous-jacente de la fonction. Je n'aime pas faire ça. Je pense généralement que les tests unitaires devraient tester la fonction sans rien supposer de l'implémentation.
Herms

15
Les inconvénients du test d'implémentation sont que les tests seront fragiles à casser si vous introduisez des modifications dans l'implémentation. Et cela n'est pas souhaitable car le refactoring est aussi important que l'écriture des tests en TDD.
JtR

30
Eh bien, les tests sont censés se casser si vous modifiez l'implémentation. TDD signifierait d'abord changer les tests.
sleske

39
@sleske - Je ne suis pas tout à fait d'accord. Si la fonctionnalité n'a pas changé, il n'y a aucune raison pour que le test échoue, car les tests devraient vraiment être un test de comportement / état, pas d'implémentation. C'est ce que jtr voulait dire, rendant vos tests fragiles. Dans un monde idéal, vous devriez pouvoir refactoriser votre code et faire passer vos tests, vérifiant que votre refactoring n'a pas changé la fonctionnalité de votre système.
Alconja

26
Toutes mes excuses pour la naïveté (pas encore beaucoup d'expérience avec les tests), mais l'idée des tests unitaires n'est-elle pas de tester chaque module de code seul? Je ne comprends pas vraiment pourquoi les méthodes privées devraient être exclues de cette idée.
OMill

118

Il peut 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 propose 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 accessoire privé"
  • Choisissez le projet dans lequel l'accesseur doit être créé => Vous vous retrouverez avec une nouvelle classe avec le nom foo_accessor. Cette classe sera générée dynamiquement lors de la compilation et rendra tous les membres accessibles au public.

Cependant, le mécanisme est parfois un peu insoluble quand il s'agit de changements de l'interface de la classe d'origine. Donc, la plupart du temps, j'évite de l'utiliser.

Classe PrivateObject L'autre méthode consiste à 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[] {/* ... */} );

2
Comment invoquez-vous des méthodes statiques privées?
StuperUser

18
Les accesseurs privés sont déconseillés dans Visual Studio 2012 .
Ryan Gates,

3
La méthode d'accesseur pour tester les méthodes privées est obsolète à partir de VS 2011. blogs.msdn.com/b/visualstudioalm/archive/2012/03/08/…
Sanjit Misra

1
En lisant les documents trouvés sur le site Web de Microsoft ici, je ne vois aucune mention de la classe PrivateObject dépréciée. J'utilise MSVS 2013 et cela fonctionne comme prévu.
stackunderflow

3
@RyanGates La première solution sur le site auquel vous avez fait référence indique que la solution pour accéder aux membres privés consiste à "utiliser la classe PrivateObject pour faciliter l'accès aux API internes et privées dans votre code. Cela se trouve dans Microsoft.VisualStudio.QualityTools.UnitTestFramework. dll assembly. "
stackunderflow

78

Je ne suis pas d'accord avec la philosophie "vous ne devriez être intéressé qu'à tester 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 suis intéressé par le comportement externe, mais j'aime que mes propres tests internes privés soient un peu plus spécifiques et précis. Oui, si je refactorise, je devrai peut-être changer certains des tests, mais à moins que ce soit un refactorisé massif, je n'aurai qu'à en changer quelques-uns et le fait que les autres tests internes (inchangés) fonctionnent toujours est un excellent indicateur que la refactorisation a 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 celles qui comptent) entièrement en utilisant l'interface publique, mais vous devrez peut-être vous retrouver debout sur la tête pour atteindre cela et la connexion entre les cas de test exécutés via l'interface publique et la partie interne de la solution qu'ils sont conçus pour tester peut être difficile, voire impossible à discerner. Cela dit, les tests individuels qui garantissent le bon fonctionnement de la machine interne valent bien les changements de test mineurs qui résultent de la refactorisation - du moins, c'est mon expérience. Si vous devez apporter d'énormes changements à vos tests pour chaque refactoring, alors cela n'a peut-être pas de sens, mais dans ce cas, vous devriez peut-être repenser entièrement votre conception.


20
J'ai bien peur d'être toujours en désaccord avec vous. Le fait de traiter chaque composant comme une boîte noire permet aux modules d'être échangés in / out sans problème. Si vous en avez un FooServiceà faire X, tout ce dont vous devez vous soucier, c'est qu'il le fait effectivement sur Xdemande. La façon dont cela fonctionne ne devrait pas avoir d'importance. S'il y a des problèmes dans la classe qui ne sont pas discernables via l'interface (peu probable), c'est quand même valide FooService. Si c'est un problème qui est visible à travers l'interface, un test sur les membres du public devrait le détecter. Le point devrait être que tant que la roue tourne correctement, elle peut être utilisée comme roue.
Basic

5
Une approche générale est que si votre logique interne est suffisamment compliquée pour que vous estimiez qu'elle nécessite des tests unitaires, elle doit peut-être être extraite dans une sorte de classe d'assistance avec une interface publique qui peut être testée unitaire. Ensuite, votre classe «parent» peut simplement utiliser cet assistant, et tout le monde peut être testé de manière appropriée.
Mir

9
@Basic: Logique complètement fausse dans cette réponse. Le cas classique lorsque vous avez besoin d'une méthode privée est lorsque vous avez besoin de réutiliser du code par des méthodes publiques. Vous mettez ce code à une certaine PrivMethod. Cette méthode ne doit pas être exposée au public, mais doit être testée pour s'assurer que les méthodes publiques, qui s'appuient sur PrivMethod, peuvent vraiment s'y fier.
Dima

6
@Dima Sûrement alors s'il y a un problème avec PrivMethod, un test sur PubMethodlequel les appels PrivMethoddevraient l'exposer? Que se passe-t-il lorsque vous changez votre SimpleSmtpServiceen un GmailService? Tout d'un coup, vos tests privés pointent sur du code qui n'existe plus ou qui fonctionne peut-être différemment et qui échouerait, même si l'application peut fonctionner parfaitement comme prévu. S'il y a un traitement complexe qui s'appliquerait aux deux expéditeurs d'e-mails, peut-être devrait-il l'être dans un EmailProcessorqui peut être utilisé par les deux et testé séparément?
Basic

2
@miltonb Nous pouvons considérer cela à partir de différents styles de développement. WRT les internes, je n'ai pas tendance à les tester à l'unité. S'il y a un problème (identifié par les tests d'interface), il est soit facile de le localiser en attachant un débogueur, soit la classe est trop complexe et doit être divisée (avec l'interface publique de la nouvelle unité de classes testée) IMHO
Basic

51

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
}

...

1
Vous pouvez même mettre cette classe enfant dans votre fichier de test unitaire au lieu d'encombrer la vraie classe. +1 pour la ruse.
Tim Abell

Si possible, je mets toujours tout le code lié aux tests dans le projet de tests unitaires. C'était juste du pseudo-code.
Jason Jackson

2
Cette fonction n'est pas privée, elle est protégée, le résultat net ... vous avez rendu votre code moins sécurisé / exposé aux fonctionnalités privées des types enfants
War

22

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 ne devrait se préoccuper que 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 alors assurez-vous qu'elle ne fait pas partie d'une API publique). Je peux ensuite tester son comportement de manière isolée.

Le produit de la refactorisation 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 ensuite me moquer de son comportement lorsque j'essaie de tester la classe d'origine afin de pouvoir me concentrer sur le comportement de l'interface publique de cette classe plutôt que d'avoir à 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. Lorsque je conduis une voiture, je ne conduis pas avec le capot relevé, je peux donc voir que le moteur fonctionne. Je me fie à l'interface fournie par la voiture, à savoir le compte-tours et le compteur de vitesse pour savoir que le moteur fonctionne. Je compte sur le fait que la voiture bouge réellement lorsque j'appuie sur la pédale d'accélérateur. Si je veux tester le moteur, je peux le vérifier isolément. :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 hérité soit remanié 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


16
Logique complètement fausse dans cette réponse. Le cas classique lorsque vous avez besoin d'une méthode privée est lorsque vous avez besoin de réutiliser du code par des méthodes publiques. Vous mettez ce code à une certaine PrivMethod. Cette méthode ne doit pas être exposée au public, mais doit être testée pour s'assurer que les méthodes publiques, qui s'appuient sur PrivMethod, peuvent vraiment s'y fier.
Dima

Cela a du sens lors du développement initial, mais voulez-vous des tests pour les méthodes privées dans votre combinaison de régression standard? Si c'est le cas, si l'implémentation change, elle peut casser la suite de tests. OTOH, si vos tests de régression se concentrent uniquement sur les méthodes publiques visibles de l'extérieur, alors si la méthode privée se casse plus tard, la suite de régression devrait toujours détecter l'erreur. Ensuite, si nécessaire, vous pouvez dépoussiérer l'ancien test privé si nécessaire.
Alex Blakemore

9
Pas d'accord, vous devriez tester l'interface publique uniquement, sinon pourquoi la nécessité de méthodes privées. Rendez-les tous publics dans ce cas et testez-les tous. Si vous testez des méthodes privées, vous rompez l'enscapulation. Si vous souhaitez tester une méthode privée et qu'elle est utilisée dans plusieurs méthodes publiques, elle doit être déplacée dans sa propre classe et testée de manière isolée.Toutes les méthodes publiques doivent ensuite être déléguées à cette nouvelle classe. l'interface de la classe d'origine et vous pouvez vérifier que le comportement n'a pas changé et que vous disposez de tests distincts pour la méthode privée déléguée.
Big Kahuna

@Big Kahuna - Si vous pensez qu'il n'y a aucun cas où vous devez tester des méthodes privées, vous n'avez jamais travaillé avec un projet assez grand / complexe. Souvent, une fonction publique comme les validations spécifiques au client se termine par 20 lignes appelant simplement des méthodes privées très simples pour rendre le code plus lisible, mais vous devez toujours tester chaque méthode privée individuelle. En testant 20 fois la fonction publique, il sera très difficile de faire ses débuts lorsque les tests unitaires échoueront.
Pedro.The.Kid

1
Je travaille pour une entreprise FTSE 100. Je pense avoir vu plusieurs projets complexes de mon temps, merci. Si vous avez besoin de tester à ce niveau, chaque méthode privée en tant que collaborateurs séparés doit être testée de manière isolée, car cela implique un comportement individuel qui doit être testé. Le test de l'objet médiateur principal devient alors simplement un test d'interaction. Il suffit de tester la stratégie correcte appelée. Votre scénario semble que la classe en question ne suit pas SRP. Il n'a pas une seule raison de changer mais 20 => violation SRP. Lisez le livre du GOOS ou l'oncle Bob. YMWV
Big Kahuna

17

Les types privés, les internes et les membres privés le sont pour une raison quelconque, et souvent vous ne voulez pas les toucher directement. Et si vous le faites, il est probable que vous vous cassiez plus tard, car il n'y a aucune garantie que les gars qui ont créé ces assemblys conserveront les implémentations privées / internes en tant que telles.

Mais, parfois, lorsque je fais des hacks / explorations 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 traite des bibliothèques héritées précompilées que je ne peux pas changer - je finis par écrire des tests par rapport à une méthode privée.

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

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();

12

Eh bien, vous pouvez tester la méthode privée de deux manières

  1. vous pouvez créer une instance de PrivateObjectclasse la syntaxe est la suivante

    PrivateObject obj= new PrivateObject(PrivateClass);
    //now with this obj you can call the private method of PrivateCalss.
    obj.PrivateMethod("Parameters");
  2. Vous pouvez utiliser la réflexion.

    PrivateClass obj = new PrivateClass(); // Class containing private obj
    Type t = typeof(PrivateClass); 
    var x = t.InvokeMember("PrivateFunc", 
        BindingFlags.InvokeMethod | BindingFlags.NonPublic | BindingFlags.Public |  
            BindingFlags.Instance, null, obj, new object[] { 5 });

Bonne réponse, mais pour # 1, votre syntaxe est incorrecte. Vous devez d'abord déclarer une instance PrivateClasset l'utiliser. stackoverflow.com/questions/9122708/…
SharpC

10

J'ai également utilisé la méthode InternalsVisibleToAttribute. Il convient également de mentionner que si vous ne vous sentez pas à l'aise de rendre vos méthodes précédemment privées internes pour y parvenir, alors elles ne devraient peut-être pas faire l'objet de tests unitaires directs de toute façon.

Après tout, vous testez le comportement de votre classe, plutôt que son implémentation spécifique - vous pouvez changer la dernière sans changer la première et vos tests devraient toujours réussir.


J'adore l'idée de tester le comportement plutôt que la mise en œuvre. Si vous liez vos tests unitaires à l'implémentation (méthodes privées), alors les tests deviendront fragiles et devront changer lorsque l'implémentation change.
Bob Horn

9

Il existe 2 types de méthodes privées. Méthodes privées statiques et méthodes privées non statiques (méthodes d'instance). Les 2 articles suivants expliquent comment tester les méthodes privées à l'aide d'exemples.

  1. Test unitaire des méthodes privées statiques
  2. Test unitaire des méthodes privées non statiques

Donnez quelques exemples, pas seulement un lien
vinculis

Ça a l'air moche. Pas d'intellisense. Mauvaise solution de MS. Je suis sous le choc !
alerya

Manière la plus simple de tester des méthodes statiques privées
Accordez

8

MS Test a une belle fonctionnalité intégrée qui rend les membres privés et les méthodes disponibles dans le projet en créant un fichier appelé VSCodeGenAccessors

[System.Diagnostics.DebuggerStepThrough()]
    [System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.VisualStudio.TestTools.UnitTestGeneration", "1.0.0.0")]
    internal class BaseAccessor
    {

        protected Microsoft.VisualStudio.TestTools.UnitTesting.PrivateObject m_privateObject;

        protected BaseAccessor(object target, Microsoft.VisualStudio.TestTools.UnitTesting.PrivateType type)
        {
            m_privateObject = new Microsoft.VisualStudio.TestTools.UnitTesting.PrivateObject(target, type);
        }

        protected BaseAccessor(Microsoft.VisualStudio.TestTools.UnitTesting.PrivateType type)
            :
                this(null, type)
        {
        }

        internal virtual object Target
        {
            get
            {
                return m_privateObject.Target;
            }
        }

        public override string ToString()
        {
            return this.Target.ToString();
        }

        public override bool Equals(object obj)
        {
            if (typeof(BaseAccessor).IsInstanceOfType(obj))
            {
                obj = ((BaseAccessor)(obj)).Target;
            }
            return this.Target.Equals(obj);
        }

        public override int GetHashCode()
        {
            return this.Target.GetHashCode();
        }
    }

Avec des classes dérivées de BaseAccessor

tel que

[System.Diagnostics.DebuggerStepThrough()]
[System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.VisualStudio.TestTools.UnitTestGeneration", "1.0.0.0")]
internal class SomeClassAccessor : BaseAccessor
{

    protected static Microsoft.VisualStudio.TestTools.UnitTesting.PrivateType m_privateType = new Microsoft.VisualStudio.TestTools.UnitTesting.PrivateType(typeof(global::Namespace.SomeClass));

    internal SomeClassAccessor(global::Namespace.Someclass target)
        : base(target, m_privateType)
    {
    }

    internal static string STATIC_STRING
    {
        get
        {
            string ret = ((string)(m_privateType.GetStaticField("STATIC_STRING")));
            return ret;
        }
        set
        {
            m_privateType.SetStaticField("STATIC_STRING", value);
        }
    }

    internal int memberVar    {
        get
        {
            int ret = ((int)(m_privateObject.GetField("memberVar")));
            return ret;
        }
        set
        {
            m_privateObject.SetField("memberVar", value);
        }
    }

    internal int PrivateMethodName(int paramName)
    {
        object[] args = new object[] {
            paramName};
        int ret = (int)(m_privateObject.Invoke("PrivateMethodName", new System.Type[] {
                typeof(int)}, args)));
        return ret;
    }

8
Les fichiers gen'd existent uniquement dans VS2005. EN 2008, ils sont générés en coulisses. Et ils sont une abomination. Et la tâche Shadow associée est instable sur un serveur de build.
Ruben Bartelink

Les accesseurs ont également été déconseillés dans VS2012-2013.
Zephan Schroeder

5

Sur CodeProject, il y a un article qui discute brièvement les avantages et les inconvénients des tests de méthodes privées. Il fournit ensuite un code de réflexion pour accéder aux méthodes privées (similaire au code fourni par Marcus ci-dessus.) Le seul problème que j'ai trouvé avec l'exemple est que le code ne prend pas en compte les méthodes surchargées.

Vous pouvez trouver l'article ici:

http://www.codeproject.com/KB/cs/testnonpublicmembers.aspx



4

J'ai tendance à ne pas utiliser les directives du compilateur car elles encombrent rapidement les choses. Une façon de l'atténuer si vous en avez vraiment besoin est de les placer dans une classe partielle et de laisser votre build ignorer ce fichier .cs lors de la création de la version de production.


Vous devez inclure les accesseurs de test dans une version de production (pour tester les optimisations du compilateur, etc.) mais les exclure dans une version de version. Mais je divise les cheveux et j'ai voté de toute façon parce que je pense que c'est une bonne idée de mettre ces choses en un seul endroit. Merci pour l'idée.
CAD bloke

4

Vous ne devriez pas tester les méthodes privées de votre code en premier lieu. Vous devriez tester l '«interface publique» ou l'API, les choses publiques de vos classes. Les API sont toutes les méthodes publiques que vous exposez aux appelants externes.

La raison en est qu'une fois que vous commencez à tester les méthodes privées et internes de votre classe, vous couplez l'implémentation de votre classe (les choses privées) à vos tests. Cela signifie que lorsque vous décidez de modifier vos détails d'implémentation, vous devrez également modifier vos tests.

Pour cette raison, vous devez éviter d'utiliser InternalsVisibleToAtrribute.

Voici un excellent discours d'Ian Cooper qui couvre ce sujet: Ian Cooper: TDD, où tout s'est-il mal passé?


3

Parfois, il peut être bon de tester des déclarations privées. Fondamentalement, un compilateur n'a qu'une seule méthode publique: Compile (chaîne outputFileName, params chaîne [] sourceSFileNames). Je suis sûr que vous comprenez qu'il serait difficile de tester une telle méthode sans tester chaque déclaration "cachée"!

C'est pourquoi nous avons créé Visual T #: pour faciliter les tests. C'est un langage de programmation .NET gratuit (compatible C # v2.0).

Nous avons ajouté l'opérateur '.-'. Il se comporte simplement comme "." , sauf que vous pouvez également accéder à toute déclaration cachée de vos tests sans rien changer dans votre projet testé.

Jetez un œil à notre site Web: téléchargez- le gratuitement .


3

Je suis surpris que personne ne l'ait encore dit, mais une solution que j'ai employée est de créer une méthode statique à l'intérieur de la classe pour se tester. Cela vous donne accès à tout ce qui est public et privé pour tester.

De plus, dans un langage de script (avec des capacités OO, comme Python, Ruby et PHP), vous pouvez faire le test du fichier lui-même lors de son exécution. Belle façon rapide de vous assurer que vos modifications n'ont rien cassé. Cela fait évidemment une solution évolutive pour tester toutes vos classes: exécutez-les toutes. (vous pouvez également le faire dans d'autres langues avec un void main qui exécute toujours ses tests également).


1
Bien que ce soit pratique, ce n'est pas très élégant. Cela peut créer un peu de gâchis d'une base de code et ne vous permet pas non plus de séparer vos tests de votre vrai code. La possibilité de tester en externe ouvre la possibilité de script automatiser les tests au lieu d'écrire des méthodes statiques à la main.
Darren Reid

Cela ne vous empêche pas de tester en externe ... appelez simplement la méthode statique comme vous le souhaitez. La base de code n'est pas non plus salissante ... vous nommez la méthode en conséquence. J'utilise "runTests", mais tout ce qui fonctionne fonctionne.
rube

Votre droit, cela n'empêche pas de tester en externe, mais il génère beaucoup plus de code, c'est-à-dire rend la base de code désordonnée. Chaque classe peut avoir beaucoup de méthodes privées pour tester qui initialise ses variables dans un ou plusieurs de ses constructeurs. Pour tester, vous devrez écrire au moins autant de méthodes statiques qu'il y a de méthodes à tester, et les méthodes de test devront peut-être être grandes pour initialiser les bonnes valeurs. Cela rendrait la maintenance du code plus difficile. Comme d'autres l'ont dit, tester le comportement d'une classe est une meilleure approche, le reste devrait être suffisamment petit pour déboguer.
Darren Reid

J'utilise le même nombre de lignes pour tester que n'importe qui (en fait moins que vous lirez plus tard). Vous n'avez pas à tester TOUTES vos méthodes privées. Juste ceux qui ont besoin de tests :) Vous n'avez pas non plus besoin de tester chacun dans une méthode distincte. Je le fais avec un seul appel. Cela rend en fait la maintenance du code MOINS difficile, car toutes mes classes ont la même méthode de test unitaire parapluie, qui exécute tous les tests unitaires privés et protégés ligne par ligne. L'ensemble du harnais de test appelle alors cette même méthode sur toutes mes classes et la maintenance réside dans ma classe - tests et tout.
rube

3

Je veux créer ici un exemple de code clair que vous pouvez utiliser sur n'importe quelle classe dans laquelle vous souhaitez tester une méthode privée.

Dans votre classe de cas de test, incluez simplement ces méthodes, puis utilisez-les comme indiqué.

  /**
   *
   * @var Class_name_of_class_you_want_to_test_private_methods_in
   * note: the actual class and the private variable to store the 
   * class instance in, should at least be different case so that
   * they do not get confused in the code.  Here the class name is
   * is upper case while the private instance variable is all lower
   * case
   */
  private $class_name_of_class_you_want_to_test_private_methods_in;

  /**
   * This uses reflection to be able to get private methods to test
   * @param $methodName
   * @return ReflectionMethod
   */
  protected static function getMethod($methodName) {
    $class = new ReflectionClass('Class_name_of_class_you_want_to_test_private_methods_in');
    $method = $class->getMethod($methodName);
    $method->setAccessible(true);
    return $method;
  }

  /**
   * Uses reflection class to call private methods and get return values.
   * @param $methodName
   * @param array $params
   * @return mixed
   *
   * usage:     $this->_callMethod('_someFunctionName', array(param1,param2,param3));
   *  {params are in
   *   order in which they appear in the function declaration}
   */
  protected function _callMethod($methodName, $params=array()) {
    $method = self::getMethod($methodName);
    return $method->invokeArgs($this->class_name_of_class_you_want_to_test_private_methods_in, $params);
  }

$ this -> _ callMethod ('_ someFunctionName', tableau (param1, param2, param3));

Émettez simplement les paramètres dans l'ordre dans lequel ils apparaissent dans la fonction privée d'origine


3

Pour tous ceux qui veulent utiliser des méthodes privées sans les tracas. Cela fonctionne avec n'importe quel framework de test unitaire n'utilisant rien d'autre qu'une bonne vieille réflexion.

public class ReflectionTools
{
    // If the class is non-static
    public static Object InvokePrivate(Object objectUnderTest, string method, params object[] args)
    {
        Type t = objectUnderTest.GetType();
        return t.InvokeMember(method,
            BindingFlags.InvokeMethod |
            BindingFlags.NonPublic |
            BindingFlags.Instance |
            BindingFlags.Static,
            null,
            objectUnderTest,
            args);
    }
    // if the class is static
    public static Object InvokePrivate(Type typeOfObjectUnderTest, string method, params object[] args)
    {
        MemberInfo[] members = typeOfObjectUnderTest.GetMembers(BindingFlags.NonPublic | BindingFlags.Static);
        foreach(var member in members)
        {
            if (member.Name == method)
            {
                return typeOfObjectUnderTest.InvokeMember(method, BindingFlags.NonPublic | BindingFlags.Static | BindingFlags.InvokeMethod, null, typeOfObjectUnderTest, args);
            }
        }
        return null;
    }
}

Ensuite, dans vos tests réels, vous pouvez faire quelque chose comme ceci:

Assert.AreEqual( 
  ReflectionTools.InvokePrivate(
    typeof(StaticClassOfMethod), 
    "PrivateMethod"), 
  "Expected Result");

Assert.AreEqual( 
  ReflectionTools.InvokePrivate(
    new ClassOfMethod(), 
    "PrivateMethod"), 
  "Expected Result");

2

MbUnit a obtenu un joli emballage pour ce appelé Reflector.

Reflector dogReflector = new Reflector(new Dog());
dogReflector.Invoke("DreamAbout", DogDream.Food);

Vous pouvez également définir et obtenir des valeurs à partir des propriétés

dogReflector.GetProperty("Age");

Concernant le "test privé" je suis d'accord que .. dans le monde parfait. il est inutile de faire des tests unitaires privés. Mais dans le monde réel, vous pourriez finir par vouloir écrire des tests privés au lieu de refactoriser le code.


4
Juste pour info, Reflectora été remplacé par le plus puissant Mirrorde Gallio / MbUnit v3.2. ( gallio.org/wiki/doku.php?id=mbunit:mirror )
Yann Trevin

2

Voici un bon article sur les tests unitaires des méthodes privées. Mais je ne sais pas quoi de mieux, pour vous faire une application spécialement conçue pour les tests (c'est comme créer des tests pour les tests uniquement) ou utiliser la réflexion pour les tests. Je suis sûr que la plupart d'entre nous choisiront la deuxième voie.


2

À mon avis, vous ne devriez tester que l'API publique de votre classe.

Rendre une méthode publique, afin de la tester unitaire, rompt l'encapsulation et expose les détails de l'implémentation.

Une bonne API publique résout un objectif immédiat du code client et résout complètement cet objectif.


Cela devrait être la bonne réponse OMI. Si vous avez beaucoup de méthodes privées, c'est probablement parce que vous avez une classe cachée que vous devez utiliser dans sa propre interface publique.
sunefred

2

J'utilise PrivateObject classe. Mais comme mentionné précédemment, il vaut mieux éviter de tester des méthodes privées.

Class target = new Class();
PrivateObject obj = new PrivateObject(target);
var retVal = obj.Invoke("PrivateMethod");
Assert.AreEqual(retVal);

2
CC -Dprivate=public

"CC" est le compilateur de ligne de commande sur le système que j'utilise. -Dfoo=barfait l'équivalent de #define foo bar. Donc, cette option de compilation transforme efficacement tous les trucs privés en public.


2
Qu'est-ce que c'est? Est-ce que cela s'applique à Visual Studio?
YeahStu

"CC" est le compilateur de ligne de commande sur le système que j'utilise. "-Dfoo = bar" fait l'équivalent de "#define foo bar". Donc, cette option de compilation transforme efficacement tous les trucs privés en public. ha-ha!
Mark Harrison

Dans Visual Studio, définissez une définition dans votre environnement de génération.
Mark Harrison

1

Voici un exemple, d'abord la signature de la méthode:

private string[] SplitInternal()
{
    return Regex.Matches(Format, @"([^/\[\]]|\[[^]]*\])+")
                        .Cast<Match>()
                        .Select(m => m.Value)
                        .Where(s => !string.IsNullOrEmpty(s))
                        .ToArray();
}

Voici le test:

/// <summary>
///A test for SplitInternal
///</summary>
[TestMethod()]
[DeploymentItem("Git XmlLib vs2008.dll")]
public void SplitInternalTest()
{
    string path = "pair[path/to/@Key={0}]/Items/Item[Name={1}]/Date";
    object[] values = new object[] { 2, "Martin" };
    XPathString xp = new XPathString(path, values);

    PrivateObject param0 = new PrivateObject(xp);
    XPathString_Accessor target = new XPathString_Accessor(param0);
    string[] expected = new string[] {
        "pair[path/to/@Key={0}]",
        "Items",
        "Item[Name={1}]",
        "Date"
    };
    string[] actual;
    actual = target.SplitInternal();
    CollectionAssert.AreEqual(expected, actual);
}

1

Un moyen de le faire est d'avoir votre méthode protectedet d'écrire un montage de test qui hérite de votre classe à tester. De cette façon, vous ne tournez pas votre méthode public, mais vous activez le test.


Je ne suis pas d'accord avec celui-ci, car vous autoriserez également vos consommateurs à hériter de la classe de base et à utiliser les fonctions protégées. C'était quelque chose que vous vouliez éviter en premier lieu en rendant ces fonctions privées ou internes.
Nick N.

1

1) Si vous avez un code hérité, la seule façon de tester des méthodes privées est par réflexion.

2) S'il s'agit d'un nouveau code, vous disposez des options suivantes:

  • Utiliser la réflexion (compliquée)
  • Écrire le test unitaire dans la même classe (rend le code de production laid en ayant également le code de test)
  • Refactorisez et rendez la méthode publique dans une sorte de classe util
  • Utilisez l'annotation @VisibleForTesting et supprimez private

Je préfère la méthode d'annotation, la plus simple et la moins compliquée. Le seul problème est que nous avons accru la visibilité, ce qui, je pense, n'est pas une grande préoccupation. Nous devrions toujours coder pour l'interface, donc si nous avons une interface MyService et une implémentation MyServiceImpl, nous pouvons alors avoir les classes de test correspondantes qui sont MyServiceTest (méthodes d'interface de test) et MyServiceImplTest (tester les méthodes privées). Tous les clients devraient de toute façon utiliser l'interface, donc d'une certaine manière, même si la visibilité de la méthode privée a été augmentée, cela ne devrait pas vraiment avoir d'importance.


1

Vous pouvez également le déclarer public ou interne (avec InternalsVisibleToAttribute) lors de la construction en mode débogage:

    /// <summary>
    /// This Method is private.
    /// </summary>
#if DEBUG
    public
#else
    private
#endif
    static string MyPrivateMethod()
    {
        return "false";
    }

Il gonfle le code, mais il sera privatedans une version.


0

Vous pouvez générer la méthode de test pour la méthode privée à partir de Visual studio 2008. Lorsque vous créez un test unitaire pour une méthode privée, un dossier Références de test est ajouté à votre projet de test et un accesseur est ajouté à ce dossier. L'accesseur est également mentionné dans la logique de la méthode de test unitaire. Cet accesseur permet à votre test unitaire d'appeler des méthodes privées dans le code que vous testez. Pour plus de détails, consultez

http://msdn.microsoft.com/en-us/library/bb385974.aspx


0

Notez également que InternalsVisibleToAtrribute a une exigence que votre assembly soit nommé fort , ce qui crée son propre ensemble de problèmes si vous travaillez dans une solution qui n'avait pas eu cette exigence auparavant. J'utilise l'accesseur pour tester des méthodes privées. Voir cette question pour un exemple de cela.


2
Non, le InternalsVisibleToAttributene nécessite pas que vos assemblys soient fortement nommés. Je l'utilise actuellement sur un projet où ce n'est pas le cas.
Cody Gray

1
Pour clarifier ceci: "L'assembly actuel et l'assembly ami doivent être non signés, ou les deux doivent être signés avec un nom fort." - De MSDN
Hari
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.