C # "interner" Zugriffsmodifikator beim Testen von Einheiten

469

Ich bin neu im Unit-Test und versuche herauszufinden, ob ich mehr 'internen' Zugriffsmodifikator verwenden soll. Ich weiß, dass wir, wenn wir 'internal' verwenden und die Assemblyvariable 'InternalsVisibleTo' setzen, Funktionen testen können, die wir aus dem Testprojekt nicht als öffentlich deklarieren möchten. Dies lässt mich denken, dass ich einfach immer 'intern' verwenden sollte, da mindestens jedes Projekt (sollte?) Ein eigenes Testprojekt hat. Könnt ihr mir einen Grund nennen, warum ich das nicht tun sollte? Wann sollte ich "privat" verwenden?

Hertanto Lie
quelle
1
Erwähnenswert - Sie können häufig vermeiden, dass Ihre internen Methoden in Einheiten getestet werden müssen, indem Sie sie System.Diagnostics.Debug.Assert()innerhalb der Methoden selbst verwenden.
Mike Marynowski

Antworten:

1195

Interne Klassen müssen getestet werden und es gibt ein Assemby-Attribut:

using System.Runtime.CompilerServices;

[assembly:InternalsVisibleTo("MyTests")]

Fügen Sie dies der Projektinfodatei hinzu, z Properties\AssemblyInfo.cs .

EricSchaefer
quelle
70
Fügen Sie es dem zu testenden Projekt hinzu (z. B. in Properties \ AssemblyInfo.cs). "MyTests" wäre die Testanordnung.
EricSchaefer
86
Dies sollte wirklich die akzeptierte Antwort sein. Ich weiß nichts über euch, aber wenn die Tests "zu weit" von dem Code entfernt sind, den sie testen, neige ich dazu, nervös zu werden. Ich bin alles dafür, dass ich es vermeide, etwas zu testen, das als gekennzeichnet ist private, aber zu viele privateDinge könnten sehr gut auf eine internalKlasse hinweisen, die Schwierigkeiten hat, extrahiert zu werden. TDD oder kein TDD, ich bevorzuge mehr Tests, die viel Code testen, als wenige Tests, die dieselbe Menge an Code ausführen. Und das Vermeiden, Dinge zu testen, internalhilft nicht gerade dabei, ein gutes Verhältnis zu erreichen.
sm
7
Zwischen @DerickBailey und Dan Tao wird eine großartige Diskussion über den semantischen Unterschied zwischen intern und privat und die Notwendigkeit, interne Komponenten zu testen , geführt. Lohnt sich zu lesen.
Kris McGinnes
31
Durch das Einschließen von und #if DEBUG, #endifblock wird diese Option nur in Debug-Builds aktiviert.
Der echte Edward Cullen
17
Dies ist die richtige Antwort. Jede Antwort, die besagt, dass nur öffentliche Methoden Unit-Tests unterzogen werden sollten, verfehlt den Punkt von Unit-Tests und macht eine Entschuldigung. Funktionstests sind Black-Box-orientiert. Unit-Tests sind White-Box-orientiert. Sie sollten "Funktionseinheiten" testen, nicht nur öffentliche APIs.
Gunnar
127

Wenn Sie private Methoden testen möchten, schauen Sie sich PrivateObjectund PrivateTypein der anMicrosoft.VisualStudio.TestTools.UnitTesting Namespace. Sie bieten einfach zu verwendende Wrapper um den erforderlichen Reflektionscode.

Dokumente : PrivateType , PrivateObject

Für VS2017 und 2019 finden Sie diese, indem Sie das Nuget MSTest.TestFramework herunterladen

Brian Rasmussen
quelle
15
Bei Abstimmungen hinterlassen Sie bitte einen Kommentar. Vielen Dank.
Brian Rasmussen
35
Es ist dumm, diese Antwort abzulehnen. Es weist auf eine neue und eine wirklich gute Lösung hin, die zuvor nicht erwähnt wurde.
Ignacio Soler Garcia
Anscheinend gibt es ein Problem mit der Verwendung von TestFramework für App-Targeting .net2.0 oder neuer: github.com/Microsoft/testfx/issues/366
Johnny Wu
41

Zusätzlich zu Erics Antwort können Sie dies auch in der csprojDatei konfigurieren :

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

Wenn Sie pro zu testendem Projekt ein Testprojekt haben, können Sie in Ihrer Directory.Build.propsDatei Folgendes tun :

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

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

gldraphael
quelle
2
Dies sollte imo die beste Antwort sein. Alle anderen Antworten sind sehr veraltet, da sich .net von Assembly-Informationen entfernt und die Funktionalität in csproj-Definitionen verlagert.
mBrice1024
12

Verwenden Sie standardmäßig weiterhin privat. Wenn ein Mitglied nicht über diesen Typ hinaus verfügbar gemacht werden soll, sollte es nicht über diesen Typ hinaus verfügbar gemacht werden, auch nicht innerhalb desselben Projekts. Dies hält die Dinge sicherer und aufgeräumter - wenn Sie das Objekt verwenden, ist klarer, welche Methoden Sie verwenden sollen.

Trotzdem halte ich es manchmal für sinnvoll, natürlich-private Methoden zu Testzwecken intern zu machen. Ich ziehe das der Verwendung von Reflexion vor, die refactoring-unfreundlich ist.

Eine zu berücksichtigende Sache könnte ein "ForTest" -Suffix sein:

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

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

Wenn Sie dann die Klasse innerhalb desselben Projekts verwenden, ist es (jetzt und in Zukunft) offensichtlich, dass Sie diese Methode nicht wirklich verwenden sollten - sie dient nur zu Testzwecken. Das ist ein bisschen hackig und nicht etwas, das ich selbst mache, aber es ist zumindest eine Überlegung wert.

Jon Skeet
quelle
2
Wenn die Methode intern ist, schließt dies nicht aus, dass sie von der Testbaugruppe verwendet wird?
Ralph Shillington
7
Ich verwende den ForTestAnsatz gelegentlich, finde ihn aber immer absolut hässlich (Hinzufügen von Code, der keinen tatsächlichen Wert in Bezug auf die Produktionsgeschäftslogik bietet). Normalerweise musste ich den Ansatz verwenden, weil das Design etwas unglücklich ist (dh Singleton-Instanzen zwischen den Tests zurücksetzen müssen)
ChrisWue
1
Versuchung, dies abzustimmen - was ist der Unterschied zwischen diesem Hack und der einfachen internen statt privaten Klasse? Zumindest mit Kompilierungsbedingungen. Dann wird es richtig chaotisch.
CAD Kerl
6
@CADbloke: Meinst du damit, die Methode eher intern als privat zu machen? Der Unterschied ist, dass es offensichtlich ist, dass Sie wirklich wollen , dass es privat ist. Jeder Code in Ihrer Produktionscodebasis, der eine Methode mit aufruft, ForTestist offensichtlich falsch. Wenn Sie die Methode jedoch nur intern machen, scheint sie in Ordnung zu sein.
Jon Skeet
2
@CADbloke: Sie können einzelne Methoden innerhalb eines Release-Builds genauso einfach in derselben Datei ausschließen wie die Verwendung von Teilklassen, IMO. Und wenn Sie das tun, deutet dies darauf hin, dass Sie Ihre Tests nicht gegen Ihren Release-Build ausführen, was für mich nach einer schlechten Idee klingt.
Jon Skeet
11

Sie können auch private Methoden verwenden und private Methoden mit Reflektion aufrufen. Wenn Sie Visual Studio Team Suite verwenden, verfügt es über einige nette Funktionen, die einen Proxy generieren, um Ihre privaten Methoden für Sie aufzurufen. Hier ist ein Code-Projektartikel, der zeigt, wie Sie die Arbeit selbst erledigen können, um private und geschützte Methoden zu testen:

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

In Bezug auf den Zugriffsmodifikator, den Sie verwenden sollten, lautet meine allgemeine Faustregel: Beginnen Sie mit privat und eskalieren Sie nach Bedarf. Auf diese Weise legen Sie so wenig interne Details Ihrer Klasse offen, wie wirklich benötigt werden, und es hilft, die Implementierungsdetails so zu verbergen, wie sie sein sollten.

Steven Behnke
quelle
3

Ich benutze Dotnet 3.1.101und die .csprojErgänzungen, die für mich arbeiteten, waren:

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

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

Hoffe das hilft jemandem da draußen!

Schwimmender Mondfisch
quelle
1
Das Hinzufügen der expliziten Generierung von Baugruppeninformationen hat dazu geführt, dass es auch für mich funktioniert hat. Vielen Dank für die Veröffentlichung!
Thevioletsaber