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

104

Je me demande comment utiliser correctement NUnit. Tout d'abord, j'ai créé un projet de test distinct qui utilise mon projet principal comme référence. Mais dans ce cas, je ne suis pas en mesure de tester des méthodes privées. Je suppose que je dois inclure mon code de test dans mon code principal?! - Cela ne semble pas être la bonne façon de procéder. (Je n'aime pas l'idée du code d'expédition avec des tests.)

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

MrFox
la source

Réponses:

74

Généralement, les tests unitaires concernent l'interface publique d'une classe, sur la théorie que l'implémentation est immatérielle, tant que les résultats sont corrects du point de vue du client.

Ainsi, NUnit ne fournit aucun mécanisme pour tester les membres non publics.

harpo
la source
1
+1 Je viens de me heurter à ce problème et dans mon cas, il y a un algorithme de "mappage" qui se produit entre le sens privé et le sens public si je devais tester unitaire le public, ce serait en fait un test d'intégration. Dans ce scénario, je pense qu'il essaie d'écrire les points de test sur un problème de conception dans le code. auquel cas je devrais probablement créer une classe différente qui exécute cette méthode "privée".
andy
140
Je ne suis pas du tout d'accord avec cet argument. Les tests unitaires ne concernent pas la classe, mais le code . Si j'avais une classe avec une méthode publique, et dix méthodes privées utilisées pour créer le résultat de la méthode publique, je n'ai aucun moyen de garantir que chaque méthode privée fonctionne de la manière prévue. Je pourrais essayer de créer des cas de test sur la méthode publique qui atteignent la bonne méthode privée de la bonne manière. Mais je n'ai alors aucune idée si la méthode privée a été réellement appelée, sauf si je fais confiance à la méthode publique pour ne jamais changer. Les méthodes privées sont destinées à être privées pour les utilisateurs de code en production, pas pour l'auteur.
Quango du
3
@Quango, dans une configuration de test standard, «l'auteur» est un utilisateur de production du code. Néanmoins, si vous ne sentez pas un problème de conception, vous avez des options pour tester des méthodes non publiques sans modifier la classe. System.Reflectionvous permet d'accéder et d'appeler des méthodes non publiques en utilisant des indicateurs de liaison, vous pouvez donc pirater NUnit ou configurer votre propre framework. Ou (plus facile, je pense), vous pouvez configurer un indicateur de compilation (#if TESTING) pour changer les modificateurs d'accès, vous permettant d'utiliser les frameworks existants.
harpo
23
Une méthode privée est un détail d'implémentation. Une grande partie de l'intérêt d'avoir des tests unitaires est de permettre la refactorisation de l' implémentation sans changer le comportement . Si les méthodes privées deviennent si compliquées qu'on aimerait les couvrir individuellement de tests, alors ce lemme peut être utilisé: "Une méthode privée peut toujours être une méthode publique (ou interne) d'une classe séparée". Autrement dit, si un élément de logique est suffisamment avancé pour être soumis à des tests, il est probablement candidat pour être sa propre classe, avec ses propres tests.
Anders Forsgren
6
@AndersForsgren Mais le but des tests unitaires , par opposition à l' intégration ou aux tests fonctionnels , est précisément de tester ces détails. Et la couverture de code est considérée comme une métrique de test unitaire importante, donc en théorie chaque élément de logique est "suffisamment avancé pour être soumis à des tests".
Kyle Strand
57

Bien que je convienne que l'objectif des tests unitaires devrait être l'interface publique, vous obtenez une impression beaucoup plus granulaire de votre code si vous testez également des méthodes privées. Le cadre de test MS le permet grâce à l'utilisation de PrivateObject et PrivateType, ce n'est pas le cas de NUnit. Ce que je fais à la place, c'est:

private MethodInfo GetMethod(string methodName)
{
    if (string.IsNullOrWhiteSpace(methodName))
        Assert.Fail("methodName cannot be null or whitespace");

    var method = this.objectUnderTest.GetType()
        .GetMethod(methodName, BindingFlags.NonPublic | BindingFlags.Instance);

    if (method == null)
        Assert.Fail(string.Format("{0} method not found", methodName));

    return method;
}

De cette façon, vous n'avez pas à compromettre l'encapsulation en faveur de la testabilité. Gardez à l'esprit que vous devrez modifier vos BindingFlags si vous souhaitez tester des méthodes statiques privées. L'exemple ci-dessus est juste pour les méthodes d'instance.

user1039513
la source
7
Le test de ces petites méthodes d'aide capture la partie unitaire dans les tests unitaires. Tous ne conviennent pas non plus aux classes d'utilité.
Johan Larsson
12
C'est exactement ce dont j'avais besoin. Merci! Tout ce dont j'avais besoin était de vérifier qu'un champ privé était correctement défini, et je n'avais pas besoin de passer 3 heures pour comprendre comment le déterminer via l'interface publique. Il s'agit d'un code existant qui ne peut pas être refactorisé sur un coup de tête. C'est drôle comme on peut répondre à une question simple avec un dogme idéaliste ...
Droj
5
Cela devrait être la réponse choisie, car c'est la seule qui répond réellement à la question.
bavaza
6
D'accord. La réponse choisie a ses mérites, mais est la réponse à "devrais-je tester des méthodes privées", pas à la question du PO.
chesterbr
2
ça marche. Merci! C'est ce que beaucoup de gens recherchent.Cela devrait être la réponse choisie,
Able Johnson
47

Un modèle courant pour écrire des tests unitaires consiste à tester uniquement les méthodes publiques.

Si vous trouvez que vous avez de nombreuses méthodes privées que vous souhaitez tester, c'est normalement un signe que vous devez refactoriser votre code.

Il serait erroné de rendre ces méthodes publiques sur la classe où elles vivent actuellement. Cela romprait le contrat que vous voulez que cette classe ait.

Il peut être correct de les déplacer vers une classe d'aide et de les rendre publics là-bas. Cette classe peut ne pas être exposée par votre API.

De cette façon, le code de test n'est jamais mélangé avec votre code public.

Un problème similaire est le test des classes privées ie. les classes que vous n'exportez pas de votre assemblage. Dans ce cas, vous pouvez explicitement faire de votre assemblage de code de test un ami de l'assemblage de code de production à l'aide de l'attribut InternalsVisibleTo.

morechilli
la source
1
+ 1 ouais! Je viens d'arriver à cette conclusion en écrivant mes tests, bon conseil!
andy
1
Déplacer les méthodes privées que vous souhaitez tester mais ne pas exposer aux utilisateurs de l'API vers une classe différente qui n'est pas exposée et les rendre publiques là est exactement ce que je recherchais.
Thomas N
merci @morechilli. Je n'avais jamais vu InternalsVisibleTo auparavant. Rendre les tests beaucoup plus faciles.
Andrew MacNaughton
Salut. Récemment reçu des votes négatifs sans commentaires. Veuillez fournir des commentaires pour expliquer vos pensées ou vos préoccupations.
morechilli
6
Donc, si vous avez une méthode privée qui est appelée à plusieurs endroits dans une classe pour faire quelque chose de très spécifique nécessaire uniquement à cette classe et qu'elle ne doit pas être exposée dans le cadre de l'API publique ... vous dites de la mettre dans un assistant classe juste pour tester? Vous pourriez aussi bien le rendre public pour la classe.
Daniel Macias
21

Il est possible de tester des méthodes privées en déclarant votre assembly de test en tant qu'assembly ami de l'assembly cible que vous testez. Voir le lien ci-dessous pour plus de détails:

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

Cela peut être utile car il sépare principalement votre code de test de votre code de production. Je n'ai jamais utilisé cette méthode moi-même car je n'en ai jamais trouvé le besoin. Je suppose que vous pouvez l'utiliser pour essayer de tester des cas de test extrêmes que vous ne pouvez tout simplement pas répliquer dans votre environnement de test pour voir comment votre code le gère.

Comme cela a été dit, vous ne devriez vraiment pas avoir besoin de tester des méthodes privées. Vous souhaitez plus que probablement refactoriser votre code en blocs de construction plus petits. Une astuce qui pourrait vous aider lorsque vous venez de refactoriser est d'essayer de réfléchir au domaine auquel votre système se rapporte et de penser aux objets «réels» qui habitent ce domaine. Vos objets / classes dans votre système doivent se rapporter directement à un objet réel qui vous permettra d'isoler le comportement exact que l'objet doit contenir et également de limiter les responsabilités des objets. Cela signifie que vous refactorisez logiquement plutôt que de simplement permettre de tester une méthode particulière; vous pourrez tester le comportement des objets.

Si vous ressentez toujours le besoin de tester en interne, vous pouvez également envisager de vous moquer de vos tests, car vous voudrez probablement vous concentrer sur un seul morceau de code. La moquerie est l'endroit où vous y injectez des dépendances d'objets, mais les objets injectés ne sont pas les objets «réels» ou de production. Ce sont des objets factices avec un comportement codé en dur pour faciliter l'isolement des erreurs de comportement. Rhino.Mocks est un framework de simulation gratuit populaire qui écrira essentiellement les objets pour vous. TypeMock.NET (un produit commercial avec une édition communautaire disponible) est un framework plus puissant qui peut simuler des objets CLR. Très utile pour se moquer des classes SqlConnection / SqlCommand et Datatable, par exemple lors du test d'une application de base de données.

Espérons que cette réponse vous donnera un peu plus d'informations pour vous informer sur les tests unitaires en général et vous aider à obtenir de meilleurs résultats avec les tests unitaires.

Dafydd Giddins
la source
12
Il est possible de tester des méthodes internes , non privées (avec NUnit).
TrueWill
6

Je suis en faveur de la capacité de tester des méthodes privées. Lorsque xUnit a démarré, il était destiné à tester les fonctionnalités après l'écriture du code. Le test de l'interface suffit à cet effet.

Les tests unitaires ont évolué vers un développement piloté par les tests. Avoir la capacité de tester toutes les méthodes est utile pour cette application.

Mark Glass
la source
5

Cette question est dans ses années avancées, mais j'ai pensé partager ma façon de faire.

Fondamentalement, j'ai toutes mes classes de test unitaire dans l'assembly qu'ils testent dans un espace de noms 'UnitTest' sous le 'default' pour cet assembly - chaque fichier de test est enveloppé dans un:

#if DEBUG

...test code...

#endif

block, et tout cela signifie que a) il n'est pas distribué dans une version et b) je peux utiliser les déclarations internal/ Friendlevel sans sauter de cerceau.

L'autre chose que cela offre, plus pertinente pour cette question, est l'utilisation de partialclasses, qui peuvent être utilisées pour créer un proxy pour tester des méthodes privées, donc par exemple pour tester quelque chose comme une méthode privée qui renvoie une valeur entière:

public partial class TheClassBeingTested
{
    private int TheMethodToBeTested() { return -1; }
}

dans les classes principales de l'assembly, et la classe de test:

#if DEBUG

using NUnit.Framework;

public partial class TheClassBeingTested
{
    internal int NUnit_TheMethodToBeTested()
    {
        return TheMethodToBeTested();
    }
}

[TestFixture]
public class ClassTests
{
    [Test]
    public void TestMethod()
    {
        var tc = new TheClassBeingTested();
        Assert.That(tc.NUnit_TheMethodToBeTested(), Is.EqualTo(-1));
    }
}

#endif

De toute évidence, vous devez vous assurer de ne pas utiliser cette méthode pendant le développement, bien qu'une version Release indiquera bientôt un appel par inadvertance si vous le faites.

Bois de Stuart
la source
4

L'objectif principal des tests unitaires est de tester les méthodes publiques d'une classe. Ces méthodes publiques utiliseront ces méthodes privées. Les tests unitaires testeront le comportement de ce qui est accessible au public.

Maxime Rouiller
la source
3

Toutes mes excuses si cela ne répond pas à la question, mais des solutions telles que l'utilisation de la réflexion, des déclarations #if #endif ou la mise en évidence de méthodes privées ne résolvent pas le problème. Il peut y avoir plusieurs raisons pour ne pas rendre les méthodes privées visibles ... et si c'est du code de production et que l'équipe écrit rétrospectivement des tests unitaires par exemple.

Pour le projet sur lequel je travaille, seul MSTest semble (malheureusement) avoir un moyen, en utilisant des accesseurs, de tester des méthodes privées.

Ritesh
la source
2

Vous ne testez pas les fonctions privées. Il existe des moyens d'utiliser la réflexion pour accéder aux méthodes et propriétés privées. Mais ce n'est pas vraiment facile et je déconseille fortement cette pratique.

Vous ne devriez tout simplement pas tester quoi que ce soit qui n'est pas public.

Si vous avez des méthodes et propriétés internes, vous devriez envisager de changer cela en public, ou d'expédier vos tests avec l'application (ce que je ne vois pas vraiment comme un problème).

Si votre client est capable d'exécuter une suite de tests et de voir que le code que vous avez livré "fonctionne", je ne vois pas cela comme un problème (tant que vous ne donnez pas votre adresse IP à travers cela). Les éléments que j'inclus dans chaque version sont des rapports de test et des rapports de couverture de code.

Tigraine
la source
Certains clients essaient de garder le petit budget et entament des discussions sur la portée des tests. Il est difficile d'expliquer à ces personnes que l'écriture d'un test n'est pas parce que vous êtes un mauvais codeur et que vous ne faites pas confiance à vos propres compétences.
MrFox
1

En théorie des tests unitaires, seul le contrat doit être testé. c'est-à-dire uniquement les membres publics de la classe. Mais dans la pratique, le développeur souhaite généralement tester les membres internes. - et ce n'est pas mal. Oui, cela va à l'encontre de la théorie, mais en pratique, cela peut parfois être utile.

Donc, si vous voulez vraiment tester les membres internes, vous pouvez utiliser l'une de ces approches:

  1. Rendez votre membre public. Dans de nombreux livres, les auteurs suggèrent que cette approche est simple
  2. Vous pouvez faire de vous des membres internes et ajouter InternalVisibleTo à assebly
  3. Vous pouvez protéger les membres de la classe et hériter de votre classe de test de votre classe en cours de test.

Exemple de code (pseudo code):

public class SomeClass
{
    protected int SomeMethod() {}
}
[TestFixture]
public class TestClass : SomeClass{

    protected void SomeMethod2() {}
    [Test]
    public void SomeMethodTest() { SomeMethod2(); }
}
Burzhuy
la source
Il semble que le cafard est caché dans votre exemple de code;) La méthode à l'intérieur de l'appareil de NUnit doit être publique, sinon vous obtenez Message: Method is not public.
Gucu112
@ Gucu112 Merci. Je l'ai corrigé. En général, cela n'a pas d'importance puisque le but est de montrer une approche à partir du point de vue du design.
burzhuy
1

Vous pouvez rendre vos méthodes protégées internes, puis les utiliser assembly: InternalsVisibleTo("NAMESPACE") dans votre espace de noms de test.

Par conséquent, NON! Vous ne pouvez pas accéder aux méthodes privées, mais il s'agit d'une solution de contournement.

Furgalicious
la source
0

Je rendrais le package de méthodes privées visible. De cette façon, vous le gardez raisonnablement privé tout en étant capable de tester ces méthodes. Je ne suis pas d'accord avec les gens qui disent que les interfaces publiques sont les seules qui devraient être testées. Il y a souvent du code vraiment critique dans les méthodes privées qui ne peuvent pas être correctement testées en passant uniquement par les interfaces externes.

Donc, cela revient vraiment à savoir si vous vous souciez davantage du code correct ou du masquage d'informations. Je dirais que la visibilité du package est un bon compromis car pour accéder à ces méthodes, quelqu'un devrait placer sa classe dans votre package. Cela devrait vraiment leur faire réfléchir à deux fois pour savoir si c'est une chose vraiment intelligente à faire.

Je suis un gars de Java, donc la visibilité du package pourrait être appelée quelque chose de complètement différent en C #. Autant dire que c'est lorsque deux classes doivent être dans le même espace de noms pour accéder à ces méthodes.

Fylke
la source