Modificateur d'accès «interne» C # lors des tests unitaires

469

Je suis nouveau dans les tests unitaires et j'essaie de savoir si je devrais commencer à utiliser plus de modificateur d'accès «interne». Je sais que si nous utilisons 'internal' et définissons la variable d'assemblage 'InternalsVisibleTo', nous pouvons tester des fonctions que nous ne voulons pas déclarer publiques à partir du projet de test. Cela me fait penser que je devrais toujours utiliser «interne» car au moins chaque projet (devrait?) A son propre projet de test. Pouvez-vous me dire pourquoi je ne devrais pas faire ça? Quand dois-je utiliser «privé»?

Hertanto Lie
la source
1
Il convient de mentionner - vous pouvez souvent éviter d'avoir à tester vos méthodes internes en utilisant System.Diagnostics.Debug.Assert()les méthodes elles-mêmes.
Mike Marynowski

Réponses:

1195

Les classes internes doivent être testées et il existe un attribut assemby:

using System.Runtime.CompilerServices;

[assembly:InternalsVisibleTo("MyTests")]

Ajoutez-le au fichier d'informations sur le projet, par exemple Properties\AssemblyInfo.cs.

EricSchaefer
la source
70
Ajoutez-le au projet sous test (par exemple dans Properties \ AssemblyInfo.cs). "MyTests" serait l'assemblage de test.
EricSchaefer
86
Cela devrait vraiment être la réponse acceptée. Je ne sais pas pour vous les gars, mais quand les tests sont "trop ​​loin" du code qu'ils testent, j'ai tendance à devenir nerveux. Je suis tout pour éviter de tester quoi que ce soit marqué comme private, mais trop de privatechoses pourraient très bien pointer vers une internalclasse qui a du mal à être extraite. TDD ou pas TDD, je préfère avoir plus de tests qui testent beaucoup de code, que d'avoir peu de tests qui exercent la même quantité de code. Et éviter de tester des internalchoses n'aide pas vraiment à atteindre un bon ratio.
sm
7
Il y a une grande discussion en cours entre @DerickBailey et Dan Tao concernant la différence sémantique entre interne et privé et la nécessité de tester les composants internes . Il vaut bien la lecture.
Kris McGinnes
31
L'encapsulation dans et #if DEBUG, #endifbloc n'activera cette option que dans les versions de débogage.
The Real Edward Cullen
17
Ceci est la bonne réponse. Toute réponse qui dit que seules les méthodes publiques devraient être testées unitairement manque le point de tests unitaires et fait une excuse. Les tests fonctionnels sont orientés boîte noire. Les tests unitaires sont orientés boîte blanche. Ils devraient tester des «unités» de fonctionnalités, pas seulement des API publiques.
Gunnar
127

Si vous souhaitez tester des méthodes privées, consultez PrivateObjectet PrivateTypedans l' Microsoft.VisualStudio.TestTools.UnitTestingespace de noms. Ils offrent des wrappers faciles à utiliser autour du code de réflexion nécessaire.

Docs: PrivateType , PrivateObject

Pour VS2017 et 2019, vous pouvez les trouver en téléchargeant le nuget MSTest.TestFramework

Brian Rasmussen
la source
15
Lorsque vous votez, veuillez laisser un commentaire. Merci.
Brian Rasmussen
35
C'est stupide de voter contre cette réponse. Il indique une nouvelle solution et une très bonne solution non mentionnée auparavant.
Ignacio Soler Garcia
Apparemment, il y a un problème avec l'utilisation de TestFramework pour le ciblage d'applications .net2.0 ou plus récent: github.com/Microsoft/testfx/issues/366
Johnny Wu
41

Ajout à la réponse d'Eric, vous pouvez également configurer cela dans le csprojfichier:

<ItemGroup>
    <AssemblyAttribute Include="System.Runtime.CompilerServices.InternalsVisibleTo">
      <_Parameter1>MyTests</_Parameter1>
    </AssemblyAttribute>
</ItemGroup>

Ou si vous avez un projet de test par projet à tester, vous pouvez faire quelque chose comme ceci dans votre Directory.Build.propsfichier:

<ItemGroup>
    <AssemblyAttribute Include="System.Runtime.CompilerServices.InternalsVisibleTo">
      <_Parameter1>$(MSBuildProjectName).Test</_Parameter1>
    </AssemblyAttribute>
</ItemGroup>

Voir: https://stackoverflow.com/a/49978185/1678053
Exemple: https://github.com/gldraphael/evlog/blob/master/Directory.Build.props#L5-L12

gldraphael
la source
2
Cela devrait être la meilleure réponse imo. Toutes les autres réponses sont très obsolètes car .net s'éloigne des informations d'assemblage et déplace la fonctionnalité dans les définitions csproj.
mBrice1024
12

Continuez à utiliser privé par défaut. Si un membre ne doit pas être exposé au-delà de ce type, il ne doit pas être exposé au-delà de ce type, même dans le même projet. Cela maintient les choses plus sûres et plus ordonnées - lorsque vous utilisez l'objet, il est plus clair quelles méthodes vous êtes censé utiliser.

Cela dit, je pense qu'il est raisonnable de rendre parfois internes des méthodes naturellement privées à des fins de test. Je préfère cela à l'utilisation de la réflexion, qui est refactoring-hostile.

Une chose à considérer pourrait être un suffixe "ForTest":

internal void DoThisForTest(string name)
{
    DoThis(name);
}

private void DoThis(string name)
{
    // Real implementation
}

Ensuite, lorsque vous utilisez la classe dans le même projet, il est évident (maintenant et à l'avenir) que vous ne devriez pas vraiment utiliser cette méthode - elle n'est là qu'à des fins de test. C'est un peu hacky, et ce n'est pas quelque chose que je fais moi-même, mais ça vaut au moins la peine d'être considéré.

Jon Skeet
la source
2
Si la méthode est interne, cela n'empêche-t-il pas son utilisation de l'ensemble de test?
Ralph Shillington
7
J'utilise de temps en temps l' ForTestapproche mais je la trouve toujours laide (ajouter du code qui ne fournit aucune valeur réelle en termes de logique métier de production). Habituellement, je trouve que j'ai dû utiliser l'approche parce que la conception est quelque peu malheureuse (c'est-à-dire avoir à réinitialiser les instances singleton entre les tests)
ChrisWue
1
Tenté de déprécier ceci - quelle est la différence entre ce hack et le simple fait de rendre la classe interne plutôt que privée? Eh bien, au moins avec les conditions de compilation. Ensuite, ça devient vraiment désordonné.
CAD bloke
6
@CADbloke: Voulez-vous dire de rendre la méthode interne plutôt que privée? La différence est qu'il est évident que vous voulez vraiment que ce soit privé. Tout code dans votre base de code de production qui appelle une méthode avec ForTestest manifestement erroné, alors que si vous faites juste la méthode interne, il semble que c'est bien à utiliser.
Jon Skeet
2
@CADbloke: Vous pouvez exclure des méthodes individuelles dans une version de construction aussi facilement dans le même fichier que l'utilisation de classes partielles, IMO. Et si vous le faites, cela suggère que vous n'effectuez pas vos tests par rapport à votre version, ce qui me semble une mauvaise idée.
Jon Skeet
11

Vous pouvez également utiliser des méthodes privées et vous pouvez appeler des méthodes privées avec réflexion. Si vous utilisez Visual Studio Team Suite, il dispose de fonctionnalités intéressantes qui généreront un proxy pour appeler vos méthodes privées pour vous. Voici un article de projet de code qui montre comment vous pouvez effectuer vous-même le travail de test unitaire des méthodes privées et protégées:

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

En ce qui concerne le modificateur d'accès que vous devez utiliser, ma règle générale est de commencer par privé et de remonter au besoin. De cette façon, vous exposerez aussi peu de détails internes de votre classe que nécessaire et cela permet de garder les détails d'implémentation cachés, comme ils devraient l'être.

Steven Behnke
la source
3

J'utilise Dotnet 3.1.101et les .csprojajouts qui ont fonctionné pour moi étaient:

<PropertyGroup>
  <!-- Explicitly generate Assembly Info -->
  <GenerateAssemblyInfo>true</GenerateAssemblyInfo>
</PropertyGroup>

<ItemGroup>
  <AssemblyAttribute Include="System.Runtime.CompilerServices.InternalsVisibleToAttribute">
  <_Parameter1>MyProject.Tests</_Parameter1>
  </AssemblyAttribute>
</ItemGroup>

J'espère que cela aide quelqu'un là-bas!

Sunfish flottant
la source
1
L'ajout de la génération explicite d'informations sur l'assemblage a finalement été le résultat pour moi. Merci d'avoir posté ceci!
thevioletsaber