Tests unitaires de méthodes privées en C #

291

Visual Studio permet le test unitaire des méthodes privées via une classe d'accesseur générée automatiquement. J'ai écrit un test d'une méthode privée qui se compile avec succès, mais elle échoue au moment de l'exécution. Une version assez minimale du code et du test est:

//in project MyProj
class TypeA
{
    private List<TypeB> myList = new List<TypeB>();

    private class TypeB
    {
        public TypeB()
        {
        }
    }

    public TypeA()
    {
    }

    private void MyFunc()
    {
        //processing of myList that changes state of instance
    }
}    

//in project TestMyProj           
public void MyFuncTest()
{
    TypeA_Accessor target = new TypeA_Accessor();
    //following line is the one that throws exception
    target.myList.Add(new TypeA_Accessor.TypeB());
    target.MyFunc();

    //check changed state of target
}

L'erreur d'exécution est:

Object of type System.Collections.Generic.List`1[MyProj.TypeA.TypeA_Accessor+TypeB]' cannot be converted to type 'System.Collections.Generic.List`1[MyProj.TypeA.TypeA+TypeB]'.

Selon intellisense - et donc je suppose que le compilateur - la cible est de type TypeA_Accessor. Mais au moment de l'exécution, il est de type TypeA, et donc l'ajout de liste échoue.

Est-il possible d'arrêter cette erreur? Ou, peut-être plus probablement, quels autres conseils ont d'autres personnes (je prédis peut-être "ne testez pas les méthodes privées" et "ne faites pas de tests unitaires manipuler l'état des objets").

junichiro
la source
Vous avez besoin d'un accesseur pour la classe privée TypeB. Accessor TypeA_Accessor donne accès aux méthodes privées et protégées de TypeA. Cependant, TypeB n'est pas une méthode. C'est une classe.
Dima
Accessor permet d'accéder aux méthodes, membres, propriétés et événements privés / protégés. Il ne donne pas accès aux classes privées / protégées au sein de votre classe. Et les classes privées / protégées (TypeB) sont destinées à être utilisées uniquement par des méthodes de propriété de classe (TypeA). Donc, fondamentalement, vous essayez d'ajouter une classe privée (TypeB) de l'extérieur de TypeA à "myList" qui est privée. Puisque vous utilisez accesseur, il n'y a aucun problème pour accéder à myList. Cependant, vous ne pouvez pas utiliser TypeB via accesseur. La solution possible serait de déplacer le TypeB en dehors du TypeA. Mais cela peut briser votre conception.
Dima
Sentez-vous que le test des méthodes privées doit être effectué par le stackoverflow.com/questions/250692/…
nate_weldon

Réponses:

274

Oui, ne testez pas les méthodes privées .... L'idée d'un test unitaire est de tester l'unité par son 'API' publique.

Si vous constatez que vous devez tester un grand nombre de comportements privés, il est fort probable que vous ayez une nouvelle `` classe '' cachée dans la classe que vous essayez de tester, extrayez-la et testez-la par son interface publique.

Un conseil / outil de réflexion ... Il y a une idée qu'aucune méthode ne devrait jamais être privée. Cela signifie que toutes les méthodes doivent vivre sur une interface publique d'un objet .... si vous sentez que vous devez le rendre privé, il vit très probablement sur un autre objet.

Ce conseil ne fonctionne pas tout à fait dans la pratique, mais c'est surtout un bon conseil, et souvent il poussera les gens à décomposer leurs objets en objets plus petits.

Keith Nicholas
la source
385
Je ne suis pas d'accord. Dans OOD, les méthodes et propriétés privées sont un moyen intrinsèque de ne pas se répéter ( en.wikipedia.org/wiki/Don%27t_repeat_yourself ). L'idée derrière la programmation et l'encapsulation de la boîte noire est de cacher les détails techniques à l'abonné. Il est donc en effet nécessaire d'avoir des méthodes et propriétés privées non triviales dans votre code. Et si ce n'est pas trivial, il doit être testé.
AxD
8
ce n'est pas intrinsèque, certains langages OO n'ont pas de méthodes privées, les propriétés privées peuvent contenir des objets avec des interfaces publiques qui peuvent être testées.
Keith Nicholas
7
Le point de ce conseil est que si votre objet fait une chose et est SEC, alors il y a souvent peu de raisons d'avoir des méthodes privées. Souvent, les méthodes privées font quelque chose dont l'objet n'est pas vraiment responsable mais est très utile, si ce n'est pas trivial, alors c'est généralement un autre objet car il est susceptible de violer le SRP
Keith Nicholas
28
Faux. Vous souhaiterez peut-être utiliser des méthodes privées pour éviter la duplication de code. Ou pour validation. Ou à bien d'autres fins que le monde public ne devrait pas connaître.
Jorj
37
Lorsque vous avez été vidé sur une base de code OO si horriblement conçue et demandé de "l'améliorer progressivement", j'ai été déçu de constater que je ne pouvais pas avoir de premiers tests pour des méthodes privées. Ouais, peut-être que dans les manuels, ces méthodes ne seraient pas là, mais dans le monde réel, nous avons des utilisateurs qui ont des exigences en matière de produits. Je ne peux pas simplement faire une refactorisation massive "Regardez un code propre" sans obtenir des tests dans le projet pour construire. Se sent comme un autre exemple de forcer les programmeurs pratiquants dans des avenues qui semblent naïvement bonnes, mais ne tiennent pas compte de la vraie merde désordonnée.
dune.rocks
666

Vous pouvez utiliser la classe PrivateObject

Class target = new Class();
PrivateObject obj = new PrivateObject(target);
var retVal = obj.Invoke("PrivateMethod");
Assert.AreEqual(expectedVal, retVal);
Saborder
la source
25
C'est la bonne réponse, maintenant que Microsoft a ajouté PrivateObject.
Zoey
4
Bonne réponse, mais veuillez noter que PrivateMethod doit être "protégé" au lieu de "privé".
HerbalMart
23
@HerbalMart: Je vous comprends peut-être mal, mais si vous suggérez que PrivateObject ne peut accéder qu'aux membres protégés et non aux membres privés, vous vous trompez.
kmote
17
@JeffPearce Pour les méthodes statiques, vous pouvez utiliser "PrivateType pt = new PrivateType (typeof (MyClass));", puis appeler InvokeStatic sur l'objet pt comme vous appelleriez Invoke sur un objet privé.
Steve Hibbert
12
Au cas où quelqu'un se demanderait à propos de MSTest.TestFramework v1.2.1 - les classes PrivateObject et PrivateType ne sont pas disponibles pour les projets ciblant .NET Core 2.0 - Il y a un problème avec github: github.com/Microsoft/testfx/issues/366
shiitake
98

"Il n'y a rien qui soit appelé standard ou meilleure pratique, ce ne sont probablement que des opinions populaires".

Il en va de même pour cette discussion également.

entrez la description de l'image ici

Tout dépend de ce que vous pensez être une unité, si vous pensez que UNIT est une classe, vous ne frapperez que la méthode publique. Si vous pensez que UNIT est une ligne de code qui utilise des méthodes privées, vous ne vous sentirez pas coupable.

Si vous souhaitez appeler des méthodes privées, vous pouvez utiliser la classe "PrivateObject" et appeler la méthode invoke. Vous pouvez regarder cette vidéo youtube approfondie ( http://www.youtube.com/watch?v=Vq6Gcs9LrPQ ) qui montre comment utiliser "PrivateObject" et explique également si les tests des méthodes privées sont logiques ou non.

Shivprasad Koirala
la source
55

Une autre pensée ici est d'étendre les tests aux classes / méthodes "internes", en donnant plus de sens en boîte blanche de ces tests. Vous pouvez utiliser InternalsVisibleToAttribute sur l'assembly pour les exposer à des modules de test unitaires distincts.

En combinaison avec une classe scellée, vous pouvez approcher une telle encapsulation que la méthode de test n'est visible que depuis l'assemblage le plus uniforme de vos méthodes. Considérez que la méthode protégée en classe scellée est de facto privée.

[assembly: InternalsVisibleTo("MyCode.UnitTests")]
namespace MyCode.MyWatch
{
    #pragma warning disable CS0628 //invalid because of InternalsVisibleTo
    public sealed class MyWatch
    {
        Func<DateTime> _getNow = delegate () { return DateTime.Now; };


       //construktor for testing purposes where you "can change DateTime.Now"
       internal protected MyWatch(Func<DateTime> getNow)
       {
           _getNow = getNow;
       }

       public MyWatch()
       {            
       }
   }
}

Et test unitaire:

namespace MyCode.UnitTests
{

[TestMethod]
public void TestminuteChanged()
{
    //watch for traviling in time
    DateTime baseTime = DateTime.Now;
    DateTime nowforTesting = baseTime;
    Func<DateTime> _getNowForTesting = delegate () { return nowforTesting; };

    MyWatch myWatch= new MyWatch(_getNowForTesting );
    nowforTesting = baseTime.AddMinute(1); //skip minute
    //TODO check myWatch
}

[TestMethod]
public void TestStabilityOnFebruary29()
{
    Func<DateTime> _getNowForTesting = delegate () { return new DateTime(2024, 2, 29); };
    MyWatch myWatch= new MyWatch(_getNowForTesting );
    //component does not crash in overlap year
}
}
Jeff
la source
Je ne suis pas sûr de comprendre. InternalsVisibleToAttribute rend accessibles les méthodes et les attributs qui sont marqués comme "internes", mais mes champs et méthodes sont "privés". Suggérez-vous que je change les choses du privé au interne? Je pense que je me méprends.
junichiro
2
Oui, c'est ce que je propose. C'est un peu "hacky", mais au moins ils ne sont pas "publics".
Jeff
27
C'est une merveilleuse réponse juste parce qu'elle ne dit pas "ne testez pas les méthodes privées" mais oui, c'est assez "hacky". Je souhaite qu'il y ait une solution. IMO c'est mauvais de dire "les méthodes privées ne devraient pas être testées" parce que la façon dont je le vois: c'est équivalent à "les méthodes privées ne devraient pas être correctes".
MasterMastic
5
ya ken, je suis également confus par ceux qui prétendent que les méthodes privées ne doivent pas être testées dans le test unitaire. Les API publiques sont la sortie, mais parfois une implémentation incorrecte donne également la bonne sortie. Ou la mise en œuvre a fait quelques mauvais effets secondaires, par exemple en conservant des ressources qui ne sont pas nécessaires, en référençant des objets empêchant sa collecte par gc ... etc. À moins qu'ils ne fournissent d'autres tests pouvant couvrir les méthodes privées plutôt que des tests unitaires, sinon je considérerais qu'ils ne peuvent pas maintenir un code testé à 100%.
mr.Pony
Je suis d'accord avec MasterMastic. Ce devrait être la réponse acceptée.
XDS
27

Une façon de tester des méthodes privées est la réflexion. Cela s'applique également à NUnit et XUnit:

MyObject objUnderTest = new MyObject();
MethodInfo methodInfo = typeof(MyObject).GetMethod("SomePrivateMethod", BindingFlags.NonPublic | BindingFlags.Instance);
object[] parameters = {"parameters here"};
methodInfo.Invoke(objUnderTest, parameters);
Jack Davidson
la source
call methods statique et non statique ?
Kiquenet
2
L'inconvénient des méthodes basées sur la réflexion est qu'elles ont tendance à se casser lorsque vous renommez des méthodes à l'aide de R #. Ce n'est peut-être pas un gros problème sur de petits projets, mais sur d'énormes bases de code, il devient un peu compliqué d'avoir des tests unitaires qui se brisent de cette façon et ensuite de les contourner et de les corriger rapidement. En ce sens, mon argent va à la réponse de Jeff.
XDS
2
@XDS Trop mauvais nameof () ne fonctionne pas pour obtenir le nom d'une méthode privée en dehors de sa classe.
Gabriel Morin
12

Ermh ... Entré ici avec exactement le même problème: Testez une méthode privée simple , mais pivot . Après avoir lu ce fil, il semble que "Je veux percer ce simple trou dans ce simple morceau de métal, et je veux m'assurer que la qualité correspond aux spécifications", puis vient "D'accord, ce n'est pas facile. Tout d'abord, il n'y a pas d'outil approprié pour le faire, mais vous pouvez construire un observatoire des ondes gravitationnelles dans votre jardin. Lisez mon article sur http://foobar.brigther-than-einstein.org/ Tout d'abord, bien sûr, vous devez suivre des cours avancés de physique quantique, alors vous avez besoin de tonnes d'azote ultra-cool, puis, bien sûr, mon livre disponible sur Amazon "...

En d'autres termes...

Non, tout d'abord.

Chaque méthode, qu'elle soit privée, interne, protégée, publique doit être testable. Il doit y avoir un moyen de mettre en œuvre de tels tests sans autant d'agitation comme cela a été présenté ici.

Pourquoi? Exactement à cause des mentions architecturales faites jusqu'à présent par certains contributeurs. Peut-être qu'une simple réitération des principes du logiciel pourrait dissiper certains malentendus.

Dans ce cas, les suspects habituels sont: OCP, SRP et, comme toujours, KIS.

Mais attendez une minute. L'idée de rendre tout accessible au public est plus ou moins politique et une sorte d'attitude. Mais. En ce qui concerne le code, même dans la communauté Open Source, ce n'est pas un dogme. Au lieu de cela, «cacher» quelque chose est une bonne pratique pour faciliter la familiarisation avec une certaine API. Vous cacheriez, par exemple, les calculs de base de votre bloc de construction de thermomètre numérique nouveau sur le marché - non pas pour cacher les mathématiques derrière la vraie courbe mesurée aux lecteurs de code curieux, mais pour empêcher votre code de devenir dépendant de certains, peut-être soudainement des utilisateurs importants qui n'ont pas pu résister à utiliser votre ancien code privé, interne et protégé pour implémenter leurs propres idées.

De quoi je parle?

privé double TranslateMeasurementIntoLinear (double actualMeasurement);

Il est facile de proclamer l'ère du Verseau ou ce qu'on appelle aujourd'hui, mais si mon morceau de capteur passe de 1.0 à 2.0, la mise en œuvre de Translate ... pourrait changer d'une simple équation linéaire facilement compréhensible et utilisable "pour tout le monde, à un calcul assez sophistiqué qui utilise l'analyse ou autre chose, et donc je casserais le code des autres. Pourquoi? Parce qu'ils ne comprenaient pas les principes mêmes du codage logiciel, pas même KIS.

Pour faire court ce conte de fées: Nous avons besoin d'un moyen simple de tester des méthodes privées - sans tarder.

Premièrement: bonne année à tous!

Deuxièmement: répétez vos leçons d'architecte.

Troisièmement: le modificateur «public» est la religion, pas une solution.

CP70
la source
5

Une autre option qui n'a pas été mentionnée consiste simplement à créer la classe de test unitaire en tant qu'enfant de l'objet que vous testez. Exemple NUnit:

[TestFixture]
public class UnitTests : ObjectWithPrivateMethods
{
    [Test]
    public void TestSomeProtectedMethod()
    {
        Assert.IsTrue(this.SomeProtectedMethod() == true, "Failed test, result false");
    }
}

Cela permettrait de tester facilement des méthodes privées et protégées (mais pas héritées privées), et cela vous permettrait de garder tous vos tests séparés du code réel afin de ne pas déployer des assemblys de test en production. Passer de vos méthodes privées à des méthodes protégées serait acceptable dans de nombreux objets hérités, et c'est une modification assez simple à effectuer.

TOUTEFOIS...

Bien qu'il s'agisse d'une approche intéressante pour résoudre le problème de la façon de tester des méthodes cachées, je ne suis pas sûr de recommander que ce soit la bonne solution au problème dans tous les cas. Il semble un peu étrange de tester en interne un objet, et je soupçonne qu'il pourrait y avoir des scénarios où cette approche vous exploserait. (Les objets immuables, par exemple, peuvent rendre certains tests très difficiles).

Bien que je mentionne cette approche, je dirais qu'il s'agit davantage d'une suggestion de remue-méninges que d'une solution légitime. Prenez-le avec un grain de sel.

EDIT: Je trouve vraiment hilarant que les gens votent contre cette réponse, car je décris explicitement cela comme une mauvaise idée. Est-ce à dire que les gens sont d'accord avec moi? Je suis tellement confus.....

Roger Hill
la source
C'est une solution créative mais un peu hacky.
shinzou
3

Extrait du livre Working Effectively with Legacy Code :

"Si nous devons tester une méthode privée, nous devons la rendre publique. Si la rendre publique nous dérange, dans la plupart des cas, cela signifie que notre classe en fait trop et que nous devons y remédier."

Selon l'auteur, la solution consiste à créer une nouvelle classe et à ajouter la méthode as public.

L'auteur explique plus loin:

"Un bon design est testable, et un design qui n'est pas testable est mauvais."

Donc, dans ces limites, votre seule vraie option est de faire la méthode public, soit dans la classe actuelle soit dans une nouvelle classe.

Kai Hartmann
la source
2

TL; DR: Extraire la méthode privée dans une autre classe, tester sur cette classe; en savoir plus sur le principe SRP (principe de responsabilité unique)

Il semble que vous ayez besoin d'extraire la privateméthode vers une autre classe; dans ce devrait être public. Au lieu d'essayer de tester la privateméthode, vous devez tester la publicméthode de cette autre classe.

Nous avons le scénario suivant:

Class A
+ outputFile: Stream
- _someLogic(arg1, arg2) 

Nous devons tester la logique de _someLogic; mais il semble que cela Class Aprenne plus de rôle qu'il n'en a besoin (violer le principe SRP); juste refactoriser en deux classes

Class A1
    + A1(logicHandler: A2) # take A2 for handle logic
    + outputFile: Stream
Class A2
    + someLogic(arg1, arg2) 

De cette façon, someLogicpourrait être testé sur A2; dans A1, il suffit de créer un faux A2 puis d'injecter au constructeur pour tester que A2 est appelé dans la fonction nommée someLogic.

o0omycomputero0o
la source
0

Dans VS 2005/2008, vous pouvez utiliser un accesseur privé pour tester un membre privé, mais cette méthode a disparu dans la version ultérieure de VS

Allen
la source
1
Bonne réponse de 2008 à peut-être début 2010. Veuillez maintenant vous référer aux alternatives PrivateObject et Reflection (voir plusieurs réponses ci-dessus). VS2010 avait un ou plusieurs bogues d'accesseur, MS l'a déconseillé dans VS2012. À moins que vous ne soyez obligé de rester dans VS2010 ou plus (outillage de construction> 18 ans), gagnez du temps en évitant les accesseurs privés. :-).
Zephan Schroeder