Question

Je construis une bibliothèque de classe qui aura un certain public & amp; méthodes privées. Je veux être en mesure de tester un peu 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 faire cela?

Était-ce utile?

La solution

Si vous utilisez .net, vous devez utiliser InternalsVisibleToAttribute .

Autres conseils

Si vous souhaitez tester à l'unité une méthode privée, il se peut que quelque chose ne va pas. 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 & hacker " une solution à ce problème (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 intéressant de la déplacer dans sa propre classe.
  2. Ajoutez d'autres tests aux méthodes publiques qui appellent la méthode privée, en testant les fonctionnalités 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 qui sont masquées à l'utilisateur (le test unitaire), c'est probablement mauvais).

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

  • Accédez au code source de la définition de classe
  • Cliquez avec le bouton 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 publics disponibles.

Cependant, le mécanisme est parfois un peu compliqué lorsqu'il s'agit de modifier 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[] {/* ... */} );

Je ne suis pas d'accord avec le "vous ne devriez être intéressé que par le test de l'interface externe". philosophie. C'est un peu comme si on disait qu'un atelier de réparation automobile ne devrait subir que des tests pour voir si les roues tournent. Oui, finalement, le comportement externe m'intéresse, mais j'aime que mes propres tests internes et privés soient un peu plus spécifiques et pertinents. Oui, si je refacture, il se peut que je doive changer certains tests, mais à moins que ce soit un refactor massif, je ne devrais en changer que quelques-uns et le fait que les autres tests internes (inchangés) fonctionnent toujours est un excellent indicateur du fait que le refactoring 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 entièrement chaque méthode interne (ou du moins celle qui compte) en utilisant l’interface publique, mais vous devrez peut-être vous retrouver debout. Pour atteindre cet objectif, la connexion entre les scénarios de test exécutés via l’interface publique et la partie interne de la solution pour laquelle ils sont conçus peut être difficile, voire impossible, à discerner. Les tests individuels garantissant le bon fonctionnement de la machine interne valent bien les modifications mineures apportées aux tests lors de la refactorisation - du moins, telle est mon expérience. Si vous devez apporter d’énormes modifications à vos tests à chaque refactorisation, cela n’a peut-être pas de sens, mais dans ce cas, vous devriez peut-être repenser entièrement votre conception. Une bonne conception doit être suffisamment flexible pour permettre la plupart des modifications sans refonte majeure.

Dans les rares cas où j’ai voulu tester des fonctions privées, je les ai généralement modifiées pour les protéger, et j’ai écrit une sous-classe avec une fonction d’enveloppe publique.

La classe:

...

protected void APrivateFunction()
{
    ...
}

...

Sous-classe pour les tests:

...

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

...

Je pense qu'une question plus fondamentale devrait être posée est la suivante: 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 puisqu'il s'agit d'un détail d'implémentation. On ne devrait s'intéresser qu'au comportement de l'interface publique et non à la manière dont elle est mise en œuvre sous les couvertures.

Si je veux tester le comportement de la méthode private 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 que cela 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 séparée qui est devenue un collaborateur de la classe d'origine. Son comportement aura été bien compris via 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 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 tous. ses méthodes privées.

Je vois cela comme une conduite. Quand je conduis une voiture, je ne conduis pas avec le capot, je peux donc 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 que le moteur fonctionne. Je m'appuie sur le fait que la voiture bouge lorsque j'appuie sur la pédale d'accélérateur. Si je veux tester le moteur, je peux effectuer des vérifications isolément. : D

Bien sûr, le test direct des méthodes privées peut constituer un dernier recours si vous utilisez 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

Les types privés, les membres internes et les membres privés le sont pour une raison quelconque et vous ne voulez souvent pas vous perdre directement avec eux. Et si vous le faites, il est probable que vous fassiez une pause plus tard, car rien ne garantit que les auteurs de ces assemblys conservent les implémentations privées / internes en tant que telles.

Mais, parfois, lors de piratages / explorations d'assemblys 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 avec des bibliothèques héritées pré-compilées que je ne peux pas changer - je finis par écrire des tests avec une méthode privée.

Ainsi est né AccessPrivateWrapper - http: //amazedsaint.blogspot .com / 2010/05 / accessprivatewrapper-c-40-dynamic.html - C’est une classe d’emballage rapide qui facilitera la tâche en utilisant des fonctions dynamiques de réflexion et de réflexion C # 4.0.

Vous pouvez créer des types internes / privés tels que

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

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

Vous pouvez utiliser la méthode privée de test unitaire de deux manières

  1. vous pouvez créer une instance de la classe PrivateObject dont 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 });
    

J'ai également utilisé la méthode InternalsVisibleToAttribute. Il convient également de mentionner que, si vous vous sentez mal à l'aise avec vos méthodes précédemment privées internes afin d'y parvenir, 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 modifier cette dernière sans modifier celle-ci et vos tests devraient tout de même passe.

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 à l’unité les méthodes privées avec des exemples.

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

MS Test intègre une fonctionnalité intéressante qui rend les méthodes et les membres privés 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 les 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;
    }

Sur CodeProject, un article décrit brièvement les avantages et les inconvénients du test 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 constaté 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

Déclarez-les internal , puis utilisez la InternalsVisibleToAttribute pour permettre à votre assemblage de test unitaire de les voir.

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

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 éléments publics de vos classes. Les API sont toutes les méthodes publiques que vous exposez aux appelants extérieurs.

La raison en est qu’une fois que vous avez commencé à tester les méthodes privées et les 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 les détails de votre implémentation, vous devez également modifier vos tests.

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

Voici une excellente conférence de Ian Cooper sur ce sujet: Ian Cooper: TDD, où tout cela a mal tourné

Parfois, il peut être intéressant de tester les déclarations privées. Fondamentalement, un compilateur n'a qu'une seule méthode publique: Compile (string outputFileName, params string [] sourceSFileNames). Je suis sûr que vous comprenez qu'il serait difficile de tester une telle méthode sans tester chaque option "masquée". déclarations!

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 "." opérateur, sauf que vous pouvez également accéder à toute déclaration masquée de vos tests sans rien changer à votre projet testé.

Consultez notre site Web: télécharger le gratuitement .

Je suis surpris que personne ne l’ait encore dit, mais une solution que j’ai employée consiste à créer une méthode statique dans 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 d’exploitation, telles que Python, Ruby et PHP), vous pouvez faire que le fichier se teste tout seul lorsqu’il est exécuté. Un moyen rapide et efficace de s’assurer que vos modifications ne cassent rien. Cela constitue évidemment une solution évolutive pour tester toutes vos classes: il suffit de les exécuter toutes. (vous pouvez aussi le faire dans d’autres langues avec un main vide qui exécute toujours ses tests).

Je souhaite créer ici un exemple de code clair que vous pouvez utiliser sur toute 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', array (param1, param2, param3));;

Il suffit d’émettre les paramètres dans l’ordre dans lequel ils apparaissent dans la fonction privée d’origine

Pour tous ceux qui souhaitent utiliser des méthodes privées sans fessée ni gâchis. Cela fonctionne avec n’importe quel framework de tests unitaires n’utilisant que le bon vieux Reflection.

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

MbUnit a eu un bon wrapper pour cela appelé Reflector.

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

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

dogReflector.GetProperty("Age");

À propos du " test private " 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 voudrez peut-être écrire des tests privés au lieu de refactoriser le code.

Voici le bon article sur les tests unitaires de méthodes privées. Mais je ne suis pas sûr de ce qu'il y a de mieux: créer 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. Nous sommes presque sûrs que la plupart d’entre nous choisirons la deuxième méthode.

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);
CC -Dprivate=public

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

Voici un exemple, tout 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);
}

Pour cela, vous devez avoir votre méthode protégée et écrire un fixture de test qui hérite de votre classe à tester. Ainsi, vous ne modifiez pas votre méthode public , mais vous activez le test.

1) Si vous avez un code hérité, le seul moyen de tester les méthodes privées est la réflexion.

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

  • Utiliser la réflexion (compliqué)
  • Ecrivez le test unitaire dans la même classe (rend le code de production moche par ayant également un code de test)
  • Refactor et rendre la méthode publique dans une sorte de classe util
  • Utilisez les annotations @VisibleForTesting et supprimez les données privées

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, à mon avis, n’est pas une préoccupation majeure. Nous devrions toujours coder pour l'interface. Ainsi, si nous avons une interface MyService et une implémentation MyServiceImpl, nous pouvons avoir les classes de test correspondantes, à savoir MyServiceTest (méthodes d'interface de test) et MyServiceImplTest (méthodes privées de test). De toute façon, tous les clients devraient utiliser l’interface. Ainsi, même si la visibilité de la méthode privée a augmenté, cela n’a aucune importance.

Vous pouvez également le déclarer public ou interne (avec InternalsVisibleToAttribute) lors de la création du mode debug:

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

Cela gonfle le code, mais ce sera privé dans une version validée.

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

Rendre une méthode publique, afin de la tester un peu, rompt l’encapsulation en exposant les détails de la mise en oeuvre.

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

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, regardez

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

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

Licencié sous: CC-BY-SA avec attribution
Non affilié à StackOverflow
scroll top