web-dev-qa-db-fra.com

Test unitaire des méthodes privées en C #

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

//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 - target est de type TypeA_Accessor. Mais au moment de l'exécution, il est de type TypeA et, par conséquent, la liste ajoutée échoue.

Y at-il un moyen que je puisse arrêter cette erreur? Ou, peut-être plus probablement, quels autres conseils ont les autres personnes (je prédis que "ne testez pas les méthodes privées" et "ne laissez pas les tests unitaires manipuler l'état des objets").

246
junichiro

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

Si vous constatez que vous devez tester de nombreux comportements privés, vous avez probablement une nouvelle "classe" qui se cache dans la classe que vous essayez de tester, extrayez-la et testez-la à l'aide de son interface publique.

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

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

254
Keith Nicholas

Vous pouvez utiliser PrivateObject Class

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

"Rien n’appelle de pratique standard ou exemplaire, c’est probablement qu’il s’agit d’opinions populaires".

Il en va de même pour cette discussion.

enter image description here

Tout dépend de ce que vous pensez être une unité. Si vous pensez que UNIT est une classe, vous n'utiliserez que la méthode publique. Si vous pensez que UNIT est une ligne de code frappant 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 détaillée ( http://www.youtube.com/watch?v=Vq6Gcs9LrPQ ) qui montre comment utiliser "PrivateObject" et indique également si le test de méthodes privées est logique ou ne pas.

85
Shivprasad Koirala

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

En combinaison avec scellé classe, vous pouvez aborder une telle encapsulation que la méthode de test ne sont visibles que de la part 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
}
}
40
Jeff

La réflexion est un moyen de tester des méthodes privées. Ceci 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);
25
Jack Davidson

Ermh ... Je suis venu ici avec exactement le même problème: Tester un simple , mais pivotant privé méthode. Après avoir lu ce fil de discussion, cela ressemble à "Je veux percer ce simple trou dans ce simple morceau de métal et je veux être sûr que la qualité est conforme aux spécifications", puis vient "OK , ce n’est pas facile. Tout d’abord, il n’existe pas d’outil approprié pour le faire, mais vous pouvez créer un observatoire des ondes gravitationnelles dans votre jardin. Lisez mon article sur http: //foobar.brigther-than -einstein.org/ D'abord, bien sûr, vous devez suivre des cours avancés de physique quantique, ensuite, vous avez besoin de tonnes d'azote ultra-froid, puis, bien sûr, mon livre disponible sur Amazon "...

En d'autres termes...

Non, les premières choses 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 les cérémonies présentées ici.

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

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 politique et constitue en quelque sorte une attitude. Mais. Même 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 rendre plus facile la familiarisation avec une certaine API. Vous cacheriez, par exemple, les calculs fondamentaux de votre bloc de construction de thermomètres numériques sur le marché - non pas pour dissimuler les calculs derrière la courbe réelle mesurée aux lecteurs de code curieux, mais pour empêcher votre code de devenir dépendant de certains, peut-être soudainement d’importants utilisateurs qui ne pourraient pas résister à l’utilisation de votre code précédemment privé, interne et protégé pour mettre en œuvre leurs propres idées.

De quoi est-ce que je parle?

privé double TranslateMeasurementIntoLinear (double actualMesurement);

Il est facile de proclamer l'ère du Verseau ou ce que l'on appelle aujourd'hui, mais si mon 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, et donc je casserais le code de l'autre. Pourquoi? Parce qu'ils ne comprenaient pas les principes mêmes du codage logiciel, pas même le KIS.

Pour que ce conte de fées soit bref: Nous avons besoin d’un moyen simple de tester des méthodes privées - sans plus 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.

9
CP70

Vous pouvez créer une classe wrapper autour de la classe contenant la méthode privée que vous souhaitez tester. Cette classe wrapper contient une méthode publique appelée Call_MyPrivateFunction et qui à son tour appelle la fonction privée de sa classe de base.

Veuillez noter que le niveau d'accès de la méthode devrait passer à [protected]

Exemple de code:

public class Order
{
    public int Quantity { get; set; }

    protected bool OrderIsBig ()
    {
        //This is the method we want to test. It needs to be protected in stead of private. Else... no cigar
        return Quantity > 100;
    }
}

//Use this wrapper class in your unit test.
public class FakeOrder : Order
{

    public bool Call_OrderIsBig()
    {
        //This makes the actual call to the protected method "OrderIsBig"
        return OrderIsBig();
    }
}

Le code de test unitaire pourrait ressembler à:

FakeOrder order = new FakeOrder();
order.Quantity = 200;

bool isBig = order.Call_OrderIsBig();   //Make a call to a public method of the FakeOrder class which in turn makes a call to the protected method.
6
HerbalMart

TL; DR: Extrait une méthode privée dans une autre classe, teste cette classe; En savoir plus sur le principe SRP (principe de responsabilité unique)

Il semble que vous ayez besoin d'extraire à la méthode private vers une autre classe; dans ceci devrait être public. Au lieu d'essayer de tester la méthode private, vous devriez tester la méthode public 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 Class A joue plus de rôle que nécessaire (enfreint le principe du PÉR); refactor juste en deux classes

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

De cette façon, someLogic pourrait être testé sur A2; dans A1, créez simplement de faux A2 puis injectez dans le constructeur pour vérifier que A2 est appelé à la fonction nommée someLogic.

2
o0omycomputero0o

Extrait du livre Travailler efficacement avec le code hérité:

"Si nous devons tester une méthode privée, nous devrions 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 sous la forme 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 véritable option est de rendre la méthode public, dans la classe actuelle ou dans une nouvelle classe.

1
Kai Hartmann