Wie testest du private Methoden mit NUnit?

104

Ich frage mich, wie man NUnit richtig benutzt. Zuerst habe ich ein separates Testprojekt erstellt, das mein Hauptprojekt als Referenz verwendet. In diesem Fall kann ich jedoch keine privaten Methoden testen. Meine Vermutung war, dass ich meinen Testcode in meinen Hauptcode aufnehmen muss?! - Das scheint nicht der richtige Weg zu sein. (Ich mag die Idee des Versandcodes mit Tests nicht.)

Wie testest du private Methoden mit NUnit?

MrFox
quelle

Antworten:

74

Im Allgemeinen beziehen sich Unit-Tests auf die öffentliche Schnittstelle einer Klasse, basierend auf der Theorie, dass die Implementierung unerheblich ist, solange die Ergebnisse aus Sicht des Kunden korrekt sind.

Daher bietet NUnit keinen Mechanismus zum Testen nicht öffentlicher Mitglieder.

Harpo
quelle
1
+1 Ich bin gerade auf dieses Problem gestoßen und in meinem Fall gibt es einen "Mapping" -Algorithmus, der zwischen der privaten und der öffentlichen Bedeutung auftritt. Wenn ich die Öffentlichkeit einem Unit-Test unterziehen würde, wäre dies tatsächlich ein Integrationstest. In diesem Szenario denke ich, dass versucht wird, die Testpunkte auf ein Entwurfsproblem im Code zu schreiben. In diesem Fall sollte ich wahrscheinlich eine andere Klasse erstellen, die diese "private" Methode ausführt.
Andy
140
Ich bin mit diesem Argument nicht einverstanden. Beim Unit-Test geht es nicht um die Klasse, sondern um den Code . Wenn ich eine Klasse mit einer öffentlichen Methode hatte und zehn private, mit denen das Ergebnis der öffentlichen Methode erstellt wurde, kann ich nicht sicherstellen, dass jede private Methode ordnungsgemäß funktioniert. Ich könnte versuchen, Testfälle mit der öffentlichen Methode zu erstellen, die die richtige private Methode auf die richtige Weise treffen. Aber ich habe dann keine Ahnung, ob die private Methode tatsächlich aufgerufen wurde, es sei denn, ich vertraue darauf, dass sich die öffentliche Methode niemals ändert. Private Methoden sollen für Produktionsbenutzer von Code privat sein, nicht für den Autor.
Quango
3
@Quango, in einem Standard - Testaufbau, der „Autor“ ist eine Produktion Benutzer des Codes. Wenn Sie jedoch kein Entwurfsproblem riechen, haben Sie die Möglichkeit, nicht öffentliche Methoden zu testen, ohne die Klasse zu ändern. System.ReflectionErmöglicht den Zugriff auf und das Aufrufen nicht öffentlicher Methoden mithilfe von Bindungsflags, sodass Sie NUnit hacken oder ein eigenes Framework einrichten können. Oder (einfacher, glaube ich) Sie können ein Flag zur Kompilierungszeit (#if TESTING) einrichten, um die Zugriffsmodifikatoren zu ändern und vorhandene Frameworks zu verwenden.
Harpo
23
Eine private Methode ist ein Implementierungsdetail. Bei Unit-Tests geht es vor allem darum, die Implementierung zu überarbeiten , ohne das Verhalten zu ändern . Wenn private Methoden so kompliziert werden, dass man sie einzeln mit Tests behandeln möchte, kann dieses Lemma verwendet werden: "Eine private Methode kann immer eine öffentliche (oder interne) Methode einer separaten Klasse sein". Das heißt, wenn ein Stück Logik so weit fortgeschritten ist, dass es getestet werden kann, ist es wahrscheinlich ein Kandidat dafür, eine eigene Klasse mit eigenen Tests zu sein.
Anders Forsgren
6
@AndersForsgren Im Gegensatz zu Integrations- oder Funktionstests geht es beim Unit- Test jedoch genau darum, solche Details zu testen. Die Codeabdeckung wird als wichtige Metrik für Unit-Tests angesehen, sodass theoretisch jede Logik "so weit fortgeschritten ist, dass sie getestet werden kann".
Kyle Strand
57

Ich bin damit einverstanden, dass der Schwerpunkt des Unit-Tests auf der öffentlichen Schnittstelle liegen sollte, aber Sie erhalten einen weitaus detaillierteren Eindruck Ihres Codes, wenn Sie auch private Methoden testen. Das MS-Testframework ermöglicht dies durch die Verwendung von PrivateObject und PrivateType, NUnit jedoch nicht. Was ich stattdessen mache ist:

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;
}

Auf diese Weise müssen Sie die Kapselung nicht zugunsten der Testbarkeit beeinträchtigen. Denken Sie daran, dass Sie Ihre BindingFlags ändern müssen, wenn Sie private statische Methoden testen möchten. Das obige Beispiel ist nur zum Beispiel Methoden.

user1039513
quelle
7
Das Testen dieser kleinen Hilfsmethoden erfasst den Einheitenteil beim Testen der Einheit. Auch sind nicht alle für Utility-Klassen geeignet.
Johan Larsson
12
Genau das brauchte ich. Vielen Dank! Alles, was ich brauchte, war zu überprüfen, ob ein privates Feld richtig eingerichtet wurde, und ich brauchte keine 3 Stunden, um herauszufinden, wie ich das über die öffentliche Schnittstelle feststellen konnte. Dies ist vorhandener Code, der nicht aus einer Laune heraus überarbeitet werden kann. Es ist lustig, wie eine einfache Frage mit einem idealistischen Dogma beantwortet werden kann ...
Droj
5
Dies sollte die ausgewählte Antwort sein, da dies die einzige ist, die die Frage tatsächlich beantwortet.
Bayaza
6
Einverstanden. Die gewählte Antwort hat ihre Vorzüge, ist aber die Antwort auf "sollte ich private Methoden testen", nicht die Frage des OP.
Chesterbr
2
es funktioniert. Danke! Dies ist, was viele Menschen suchen. Dies sollte die ausgewählte Antwort sein,
Able Johnson
47

Ein übliches Muster für das Schreiben von Komponententests besteht darin, nur öffentliche Methoden zu testen.

Wenn Sie feststellen, dass Sie viele private Methoden haben, die Sie testen möchten, ist dies normalerweise ein Zeichen dafür, dass Sie Ihren Code umgestalten sollten.

Es wäre falsch, diese Methoden in der Klasse zu veröffentlichen, in der sie derzeit leben. Das würde den Vertrag brechen, den diese Klasse haben soll.

Es kann richtig sein, sie in eine Hilfsklasse zu verschieben und dort öffentlich zu machen. Diese Klasse wird möglicherweise nicht von Ihrer API verfügbar gemacht.

Auf diese Weise wird Testcode niemals mit Ihrem öffentlichen Code gemischt.

Ein ähnliches Problem ist das Testen von Privatklassen, dh. Klassen, die Sie nicht aus Ihrer Assembly exportieren. In diesem Fall können Sie Ihre Testcode-Assembly mithilfe des Attributs InternalsVisibleTo explizit zu einem Freund der Produktionscode-Assembly machen.

morechilli
quelle
1
+ 1 yep! Ich bin gerade zu diesem Schluss gekommen, als ich meine Tests geschrieben habe, guter Rat!
Andy
1
Das Verschieben von privaten Methoden, die Sie testen möchten, aber Benutzern der API nicht zugänglich machen, in eine andere Klasse, die nicht verfügbar gemacht wird, und deren Veröffentlichung dort ist genau das, wonach ich gesucht habe.
Thomas N
danke @morechilli. Hatte InternalsVisibleTo noch nie gesehen. Das Testen wurde viel einfacher.
Andrew MacNaughton
Hallo. Vor kurzem einige Abstimmungen ohne Kommentare erhalten. Bitte geben Sie Feedback, um Ihre Gedanken oder Bedenken zu erläutern.
Morechilli
6
Wenn Sie also eine private Methode haben, die an mehreren Stellen innerhalb einer Klasse aufgerufen wird, um etwas sehr Spezifisches zu tun, das nur von dieser Klasse benötigt wird, und das nicht als Teil der öffentlichen API verfügbar gemacht werden soll ... sagen Sie, setzen Sie es in einen Helfer Klasse nur zum Testen? Sie können es auch einfach für die Klasse veröffentlichen.
Daniel Macias
21

Sie können private Methoden testen, indem Sie Ihre Testassembly als Friend-Assembly der zu testenden Zielassembly deklarieren. Siehe den Link unten für Details:

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

Dies kann nützlich sein, da es Ihren Testcode meistens von Ihrem Produktionscode trennt. Ich habe diese Methode selbst nie angewendet, da ich nie eine Notwendigkeit dafür gefunden habe. Ich nehme an, Sie könnten damit versuchen, extreme Testfälle zu testen, die Sie in Ihrer Testumgebung einfach nicht replizieren können, um zu sehen, wie Ihr Code damit umgeht.

Wie bereits gesagt, sollten Sie private Methoden wirklich nicht testen müssen. Sie möchten Ihren Code mehr als nur in kleinere Bausteine ​​umgestalten. Ein Tipp, der Ihnen bei der Umgestaltung helfen könnte, ist, über die Domäne nachzudenken, auf die sich Ihr System bezieht, und über die „echten“ Objekte nachzudenken, die diese Domäne bewohnen. Ihre Objekte / Klassen in Ihrem System sollten sich direkt auf ein reales Objekt beziehen, sodass Sie das genaue Verhalten des Objekts isolieren und die Verantwortlichkeiten der Objekte einschränken können. Dies bedeutet, dass Sie logisch umgestalten und nicht nur das Testen einer bestimmten Methode ermöglichen. Sie können das Verhalten der Objekte testen.

Wenn Sie immer noch das Bedürfnis haben, interne Tests durchzuführen, sollten Sie sich auch über Ihre Tests lustig machen, da Sie sich wahrscheinlich auf einen Teil des Codes konzentrieren möchten. Beim Verspotten fügen Sie Objektabhängigkeiten ein, aber die injizierten Objekte sind nicht die "echten" oder Produktionsobjekte. Sie sind Dummy-Objekte mit fest codiertem Verhalten, um das Isolieren von Verhaltensfehlern zu erleichtern. Rhino.Mocks ist ein beliebtes kostenloses Mocking-Framework, das im Wesentlichen die Objekte für Sie schreibt. TypeMock.NET (ein kommerzielles Produkt mit einer verfügbaren Community Edition) ist ein leistungsfähigeres Framework, das CLR-Objekte verspotten kann. Sehr nützlich, um die Klassen SqlConnection / SqlCommand und Datatable zu verspotten, beispielsweise beim Testen einer Datenbank-App.

Hoffentlich gibt Ihnen diese Antwort ein bisschen mehr Informationen, um Sie über Unit-Tests im Allgemeinen zu informieren und Ihnen zu helfen, bessere Ergebnisse mit Unit-Tests zu erzielen.

Dafydd Giddins
quelle
12
Es ist möglich, interne Methoden zu testen , nicht private (mit NUnit).
TrueWill
6

Ich bin dafür, private Methoden testen zu können. Beim Start von xUnit sollte die Funktionalität nach dem Schreiben des Codes getestet werden. Zu diesem Zweck ist das Testen der Schnittstelle ausreichend.

Unit-Tests haben sich zu einer testgetriebenen Entwicklung entwickelt. Die Fähigkeit, alle Methoden zu testen, ist für diese Anwendung nützlich.

Mark Glass
quelle
5

Diese Frage ist in den fortgeschrittenen Jahren, aber ich dachte, ich würde meine Art, dies zu tun, teilen.

Grundsätzlich habe ich alle meine Unit-Test-Klassen in der Assembly, die sie testen, in einem 'UnitTest'-Namespace unter dem' Standard 'für diese Assembly - jede Testdatei wird in Folgendes eingeschlossen:

#if DEBUG

...test code...

#endif

Block, und all das bedeutet, dass a) es nicht in einer Version verteilt wird und b) ich internal/ verwenden kannFriend Deklarationen Level verwenden kann, ohne Hoop Jumping.

Das andere, was dies bietet, was für diese Frage relevanter ist, ist die Verwendung von partialKlassen, mit denen ein Proxy zum Testen privater Methoden erstellt werden kann, um beispielsweise so etwas wie eine private Methode zu testen, die einen ganzzahligen Wert zurückgibt:

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

in den Hauptklassen der Baugruppe und der Testklasse:

#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

Natürlich müssen Sie sicherstellen, dass Sie diese Methode während der Entwicklung nicht verwenden, obwohl ein Release-Build in Kürze einen versehentlichen Aufruf anzeigt, wenn Sie dies tun.

Stuart Wood
quelle
4

Das Hauptziel von Unit-Tests ist es, die öffentlichen Methoden einer Klasse zu testen. Diese öffentlichen Methoden verwenden diese privaten Methoden. Unit-Tests testen das Verhalten von öffentlich verfügbaren Inhalten.

Maxime Rouiller
quelle
3

Entschuldigung, wenn dies die Frage nicht beantwortet, aber Lösungen wie das Verwenden von Reflection, #if #endif-Anweisungen oder das Sichtbarmachen privater Methoden das Problem nicht lösen. Es kann mehrere Gründe geben, private Methoden nicht sichtbar zu machen. Was ist, wenn es sich um Produktionscode handelt und das Team beispielsweise nachträglich Komponententests schreibt?

Für das Projekt, an dem ich arbeite, scheint nur MSTest (leider) eine Möglichkeit zu haben, mithilfe von Accessoren private Methoden zu testen.

Ritesh
quelle
2

Sie testen keine privaten Funktionen. Es gibt Möglichkeiten, mithilfe der Reflexion in private Methoden und Eigenschaften zu gelangen. Aber das ist nicht wirklich einfach und ich rate dringend von dieser Praxis ab.

Sie sollten einfach nichts testen, was nicht öffentlich ist.

Wenn Sie über interne Methoden und Eigenschaften verfügen, sollten Sie diese entweder in öffentlich ändern oder Ihre Tests mit der App versenden (etwas, das ich nicht wirklich als Problem sehe).

Wenn Ihr Kunde in der Lage ist, eine Test-Suite auszuführen und festzustellen, dass der von Ihnen gelieferte Code tatsächlich "funktioniert", sehe ich dies nicht als Problem an (solange Sie Ihre IP-Adresse nicht dadurch preisgeben). In jeder Version sind Testberichte und Berichte zur Codeabdeckung enthalten.

Migräne
quelle
Einige Kunden versuchen, das Budget klein zu halten und Diskussionen über den Umfang der Tests zu beginnen. Es ist schwer, diesen Leuten zu erklären, dass das Schreiben von Tests nicht darauf zurückzuführen ist, dass Sie ein schlechter Programmierer sind und Ihren eigenen Fähigkeiten nicht vertrauen.
MrFox
1

In der Theorie des Unit Testing sollte nur der Vertrag getestet werden. dh nur öffentliche Mitglieder der Klasse. In der Praxis möchte der Entwickler jedoch in der Regel interne Mitglieder testen. - und es ist nicht schlecht. Ja, es widerspricht der Theorie, aber in der Praxis kann es manchmal nützlich sein.

Wenn Sie also wirklich interne Mitglieder testen möchten, können Sie einen der folgenden Ansätze verwenden:

  1. Machen Sie Ihr Mitglied öffentlich. In vielen Büchern schlagen Autoren diesen Ansatz als einfach vor
  2. Sie können Ihre Mitglieder intern machen und InternalVisibleTo assebly hinzufügen
  3. Sie können Klassenmitglieder schützen und Ihre Testklasse von Ihrer Testklasse erben.

Codebeispiel (Pseudocode):

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

    protected void SomeMethod2() {}
    [Test]
    public void SomeMethodTest() { SomeMethod2(); }
}
burzhuy
quelle
Scheint, dass Kakerlake in Ihrem Beispielcode versteckt ist;) Die Methode in NUnits Fixture muss öffentlich sein, sonst erhalten Sie Message: Method is not public.
Gucu112
@ Gucu112 Danke. Ich habe es repariert. Im Allgemeinen spielt es keine Rolle, da das Ziel darin besteht, einen Ansatz aus dem Design-POV zu zeigen.
Burzhuy
1

Sie können Ihre Methoden intern schützen und dann verwenden assembly: InternalsVisibleTo("NAMESPACE") in Ihrem Test-Namespace verwenden.

Daher NEIN! Sie können nicht auf private Methoden zugreifen, dies ist jedoch eine Problemumgehung.

Furgalicious
quelle
0

Ich würde das private Methodenpaket sichtbar machen. Auf diese Weise halten Sie es einigermaßen privat, während Sie diese Methoden dennoch testen können. Ich stimme den Leuten nicht zu, die sagen, dass die öffentlichen Schnittstellen die einzigen sind, die getestet werden sollten. Die privaten Methoden enthalten häufig sehr kritischen Code, der nicht ordnungsgemäß getestet werden kann, indem nur die externen Schnittstellen verwendet werden.

Es läuft also wirklich darauf hinaus, ob Sie sich mehr für den richtigen Code oder das Verstecken von Informationen interessieren. Ich würde sagen, dass die Sichtbarkeit von Paketen ein guter Kompromiss ist, da für den Zugriff auf diese Methode jemand seine Klasse in Ihr Paket einfügen muss. Das sollte sie wirklich zweimal überlegen lassen, ob das eine wirklich kluge Sache ist.

Ich bin übrigens ein Java-Typ, daher kann die Sichtbarkeit von Paketen in C # als etwas ganz anderes bezeichnet werden. Es genügt zu sagen, dass sich zwei Klassen im selben Namespace befinden müssen, um auf diese Methoden zugreifen zu können.

Fylke
quelle