Unit-Test privater Methoden in C #

291

Visual Studio ermöglicht das Unit-Testen privater Methoden über eine automatisch generierte Accessor-Klasse. Ich habe einen Test einer privaten Methode geschrieben, die erfolgreich kompiliert wurde, aber zur Laufzeit fehlschlägt. Eine ziemlich minimale Version des Codes und des Tests ist:

//in project MyProj
class TypeA
{
    private List<TypeB> myList = new List<TypeB>();

    private class TypeB
    {
        public TypeB()
        {
        }
    }

    public TypeA()
    {
    }

    private void MyFunc()
    {
        //processing of myList that changes state of instance
    }
}    

//in project TestMyProj           
public void MyFuncTest()
{
    TypeA_Accessor target = new TypeA_Accessor();
    //following line is the one that throws exception
    target.myList.Add(new TypeA_Accessor.TypeB());
    target.MyFunc();

    //check changed state of target
}

Der Laufzeitfehler ist:

Object of type System.Collections.Generic.List`1[MyProj.TypeA.TypeA_Accessor+TypeB]' cannot be converted to type 'System.Collections.Generic.List`1[MyProj.TypeA.TypeA+TypeB]'.

Laut Intellisense - und daher denke ich, dass der Compiler - ist das Ziel vom Typ TypeA_Accessor. Zur Laufzeit ist es jedoch vom Typ TypeA, und daher schlägt das Hinzufügen der Liste fehl.

Kann ich diesen Fehler auf irgendeine Weise stoppen? Oder wahrscheinlich eher, welche anderen Ratschläge andere Menschen haben (ich gehe davon aus, dass "private Methoden nicht testen" und "Unit-Tests den Zustand von Objekten nicht manipulieren lassen").

Junichiro
quelle
Sie benötigen einen Accessor für die Privatklasse TypeB. Accessor TypeA_Accessor bietet Zugriff auf private und geschützte Methoden von TypeA. Typ B ist jedoch keine Methode. Es ist eine Klasse.
Dima
Accessor bietet Zugriff auf private / geschützte Methoden, Mitglieder, Eigenschaften und Ereignisse. Es bietet keinen Zugriff auf private / geschützte Klassen innerhalb Ihrer Klasse. Und private / geschützte Klassen (Typ B) dürfen nur von Methoden zum Besitz von Klassen (Typ A) verwendet werden. Im Grunde versuchen Sie also, eine private Klasse (TypeB) von außerhalb von TypeA zu "myList" hinzuzufügen, die privat ist. Da Sie Accessor verwenden, ist der Zugriff auf myList problemlos möglich. Sie können TypeB jedoch nicht über Accessor verwenden. Eine mögliche Lösung wäre, Typ B außerhalb von Typ A zu verschieben. Aber es kann Ihr Design brechen.
Dima
Fühlen Sie, dass das Testen privater Methoden durch die folgenden stackoverflow.com/questions/250692/…
nate_weldon

Antworten:

274

Ja, testen Sie keine privaten Methoden. Die Idee eines Komponententests besteht darin, die Einheit anhand ihrer öffentlichen 'API' zu testen.

Wenn Sie feststellen, dass Sie viel privates Verhalten testen müssen, versteckt sich höchstwahrscheinlich eine neue 'Klasse' in der Klasse, die Sie testen, extrahieren und über die öffentliche Schnittstelle testen möchten.

Ein Ratschlag / Denkwerkzeug ..... Es gibt eine Idee, dass keine Methode jemals privat sein sollte. Das heißt, alle Methoden sollten auf einer öffentlichen Schnittstelle eines Objekts leben. Wenn Sie der Meinung sind, dass Sie es privat machen müssen, lebt es höchstwahrscheinlich auf einem anderen Objekt.

Dieser Ratschlag funktioniert in der Praxis nicht ganz, ist aber meistens ein guter Ratschlag, und oft wird er Menschen dazu bringen, ihre Objekte in kleinere Objekte zu zerlegen.

Keith Nicholas
quelle
385
Ich bin nicht einverstanden. In OOD sind private Methoden und Eigenschaften eine intrinsische Möglichkeit, sich nicht zu wiederholen ( en.wikipedia.org/wiki/Don%27t_repeat_yourself ). Die Idee hinter der Black-Box-Programmierung und -Kapselung besteht darin, technische Details vor dem Abonnenten zu verbergen. Es ist also in der Tat notwendig, nicht triviale private Methoden und Eigenschaften in Ihrem Code zu haben. Und wenn es nicht trivial ist, muss es getestet werden.
AxD
8
Es ist nicht intrinsisch, einige OO-Sprachen haben keine privaten Methoden, private Eigenschaften können Objekte mit öffentlichen Schnittstellen enthalten, die getestet werden können.
Keith Nicholas
7
Der Punkt dieses Ratschlags ist, wenn Sie Ihr Objekt eine Sache tun und trocken sind, dann gibt es oft wenig Grund, private Methoden zu haben. Oft tun private Methoden etwas, für das das Objekt nicht wirklich verantwortlich ist, aber es ist sehr nützlich, wenn es nicht trivial ist, dann ist es im Allgemeinen ein anderes Objekt, da es wahrscheinlich gegen SRP verstößt
Keith Nicholas
28
Falsch. Möglicherweise möchten Sie private Methoden verwenden, um Codeduplizierungen zu vermeiden. Oder zur Validierung. Oder für viele andere Zwecke, von denen die öffentliche Welt nichts wissen sollte.
Jorj
37
Als Sie auf eine so schrecklich gestaltete OO-Codebasis gestoßen wurden und gebeten wurden, diese schrittweise zu verbessern, war es eine Enttäuschung, dass ich keine ersten Tests für private Methoden durchführen konnte. Ja, vielleicht wären diese Methoden in den Lehrbüchern nicht hier, aber in der realen Welt haben wir Benutzer, die Produktanforderungen haben. Ich kann nicht einfach ein massives Refactoring von "Look at ma clean code" durchführen, ohne dass einige Tests im Projekt durchgeführt werden müssen. Es fühlt sich an wie ein weiterer Fall, in dem praktizierende Programmierer in Wege gezwungen werden, die naiv gut erscheinen, aber echte Unordnung nicht berücksichtigen.
dune.rocks
666

Sie können die PrivateObject-Klasse verwenden

Class target = new Class();
PrivateObject obj = new PrivateObject(target);
var retVal = obj.Invoke("PrivateMethod");
Assert.AreEqual(expectedVal, retVal);
Scuttle
quelle
25
Dies ist die richtige Antwort, nachdem Microsoft PrivateObject hinzugefügt hat.
Zoey
4
Gute Antwort, aber bitte beachten Sie, dass die PrivateMethod anstelle von "privat" "geschützt" werden muss.
HerbalMart
23
@HerbalMart: Vielleicht verstehe ich Sie falsch, aber wenn Sie vorschlagen, dass PrivateObject nur auf geschützte und nicht auf private Mitglieder zugreifen kann, irren Sie sich.
kmote
17
@JeffPearce Für statische Methoden können Sie "PrivateType pt = new PrivateType (typeof (MyClass));" verwenden und dann InvokeStatic für das pt-Objekt aufrufen, wie Sie Invoke für ein privates Objekt aufrufen würden.
Steve Hibbert
12
Falls sich jemand ab MSTest.TestFramework v1.2.1 wundert - die Klassen PrivateObject und PrivateType sind für Projekte mit .NET Core 2.0 nicht verfügbar - Hierfür gibt es ein Github-Problem: github.com/Microsoft/testfx/issues/366
shiitake
98

"Es gibt nichts, was als Standard oder Best Practice bezeichnet wird, wahrscheinlich handelt es sich nur um populäre Meinungen."

Gleiches gilt auch für diese Diskussion.

Geben Sie hier die Bildbeschreibung ein

Es hängt alles davon ab, was Sie für eine Einheit halten. Wenn Sie UNIT für eine Klasse halten, werden Sie nur die öffentliche Methode anwenden. Wenn Sie glauben, dass UNIT Codezeilen sind, die auf private Methoden treffen, werden Sie sich nicht schuldig fühlen.

Wenn Sie private Methoden aufrufen möchten, können Sie die Klasse "PrivateObject" verwenden und die Methode invoke aufrufen. Sie können sich dieses ausführliche Youtube-Video ( http://www.youtube.com/watch?v=Vq6Gcs9LrPQ ) ansehen, das die Verwendung von "PrivateObject" zeigt und auch erläutert, ob das Testen privater Methoden logisch ist oder nicht.

Shivprasad Koirala
quelle
55

Ein weiterer Gedanke hier ist, das Testen auf "interne" Klassen / Methoden auszudehnen, um einen besseren Eindruck von diesen Tests zu vermitteln. Sie können InternalsVisibleToAttribute für die Assembly verwenden, um diese separaten Unit-Test-Modulen auszusetzen.

In Kombination mit einer versiegelten Klasse können Sie sich einer solchen Kapselung nähern, dass die Testmethode nur von der unittesten Montage Ihrer Methoden sichtbar ist. Bedenken Sie, dass die geschützte Methode in der versiegelten Klasse de facto privat ist.

[assembly: InternalsVisibleTo("MyCode.UnitTests")]
namespace MyCode.MyWatch
{
    #pragma warning disable CS0628 //invalid because of InternalsVisibleTo
    public sealed class MyWatch
    {
        Func<DateTime> _getNow = delegate () { return DateTime.Now; };


       //construktor for testing purposes where you "can change DateTime.Now"
       internal protected MyWatch(Func<DateTime> getNow)
       {
           _getNow = getNow;
       }

       public MyWatch()
       {            
       }
   }
}

Und Unit-Test:

namespace MyCode.UnitTests
{

[TestMethod]
public void TestminuteChanged()
{
    //watch for traviling in time
    DateTime baseTime = DateTime.Now;
    DateTime nowforTesting = baseTime;
    Func<DateTime> _getNowForTesting = delegate () { return nowforTesting; };

    MyWatch myWatch= new MyWatch(_getNowForTesting );
    nowforTesting = baseTime.AddMinute(1); //skip minute
    //TODO check myWatch
}

[TestMethod]
public void TestStabilityOnFebruary29()
{
    Func<DateTime> _getNowForTesting = delegate () { return new DateTime(2024, 2, 29); };
    MyWatch myWatch= new MyWatch(_getNowForTesting );
    //component does not crash in overlap year
}
}
Jeff
quelle
Ich bin mir nicht sicher ob ich das verstehe. InternalsVisibleToAttribute macht Methoden und Attribute, die als "intern" markiert sind, zugänglich, aber meine Felder und Methoden sind "privat". Schlagen Sie vor, dass ich Dinge von privat zu intern ändere? Ich glaube ich verstehe falsch.
Junichiro
2
Ja, das schlage ich vor. Es ist ein bisschen "hacky", aber zumindest sind sie nicht "öffentlich".
Jeff
27
Dies ist eine wunderbare Antwort, nur weil nicht "private Methoden nicht testen" steht, aber ja, es ist ziemlich "hacky". Ich wünschte, es gäbe eine Lösung. IMO ist es schlecht zu sagen, "private Methoden sollten nicht getestet werden", weil ich es so sehe: Es ist gleichbedeutend mit "private Methoden sollten nicht korrekt sein".
MasterMastic
5
ya ken, ich bin auch verwirrt von denen, die behaupten, dass private Methoden nicht im Unit-Test getestet werden sollten. Öffentliche APIs sind die Ausgabe, aber manchmal liefert eine falsche Implementierung auch die richtige Ausgabe. Oder die Implementierung hat einige schlimme Nebenwirkungen verursacht, z. B. das Halten von Ressourcen, die nicht erforderlich sind, das Verweisen auf Objekte, die verhindern, dass sie von gc ... usw. Gesammelt werden. Sofern sie keinen anderen Test bereitstellen, der die privaten Methoden abdecken kann, als den Komponententest, würde ich sonst in Betracht ziehen, dass sie keinen zu 100% getesteten Code verwalten können.
Mr. Pony
Ich stimme MasterMastic zu. Dies sollte die akzeptierte Antwort sein.
XDS
27

Eine Möglichkeit, private Methoden zu testen, ist die Reflexion. Dies gilt auch für NUnit und XUnit:

MyObject objUnderTest = new MyObject();
MethodInfo methodInfo = typeof(MyObject).GetMethod("SomePrivateMethod", BindingFlags.NonPublic | BindingFlags.Instance);
object[] parameters = {"parameters here"};
methodInfo.Invoke(objUnderTest, parameters);
Jack Davidson
quelle
call methods statisch und nicht statisch ?
Kiquenet
2
Der Nachteil von reflexionsabhängigen Methoden besteht darin, dass sie beim Umbenennen von Methoden mit R # zum Brechen neigen. Bei kleinen Projekten ist das vielleicht kein großes Problem, aber bei großen Codebasen wird es ziemlich nervig, wenn Unit-Tests auf diese Weise abbrechen und sie dann schnell beheben müssen. In diesem Sinne geht mein Geld an Jeffs Antwort.
XDS
2
@XDS Schade, dass nameof () nicht funktioniert, um den Namen einer privaten Methode von außerhalb ihrer Klasse abzurufen.
Gabriel Morin
12

Ähm ... kam hier mit genau dem gleichen Problem mit: Testen Sie eine einfache , aber entscheidende Privatperson Methode. Nach dem Lesen dieses Threads scheint es so zu sein: "Ich möchte dieses einfache Loch in dieses einfache Stück Metall bohren und sicherstellen, dass die Qualität den Spezifikationen entspricht" und dann kommt "Okay, das ist nicht zu einfach. Erstens gibt es dafür kein geeignetes Werkzeug, aber Sie könnten in Ihrem Garten ein Gravitationswellenobservatorium bauen. Lesen Sie meinen Artikel unter http://foobar.brigther-than-einstein.org/ Zuerst natürlich Sie Müssen Sie einige Kurse für fortgeschrittene Quantenphysik besuchen, dann brauchen Sie Tonnen von ultra-coolem Stickstoffium und dann natürlich mein Buch bei Amazon "...

Mit anderen Worten...

Nein, das Wichtigste zuerst.

Jede einzelne Methode, sei es privat, intern, geschützt, öffentlich hat zu prüfbar sein. Es muss eine Möglichkeit geben, solche Tests ohne den hier vorgestellten Lärm durchzuführen.

Warum? Genau weil der architektonischen Erwähnungen, die bisher von einigen Mitwirkenden gemacht wurden. Vielleicht kann eine einfache Wiederholung der Software-Prinzipien einige Missverständnisse beseitigen.

In diesem Fall sind die üblichen Verdächtigen: OCP, SRP und wie immer KIS.

Aber warte eine Minute. Die Idee, alles öffentlich zugänglich zu machen, ist eher weniger politisch und eine Art Haltung. Aber. Wenn es um Code geht, ist dies auch in der Open Source Community kein Dogma. Stattdessen empfiehlt es sich, etwas zu "verstecken", um sich leichter mit einer bestimmten API vertraut zu machen. Sie würden zum Beispiel die Kernberechnungen Ihres neu auf den Markt gebrachten digitalen Thermometer-Bausteins verbergen - nicht, um neugierigen Codelesern die Mathematik hinter der tatsächlich gemessenen Kurve zu verbergen, sondern um zu verhindern, dass Ihr Code von einigen abhängig wird. Vielleicht plötzlich wichtige Benutzer, die nicht widerstehen konnten, Ihren ehemals privaten, internen, geschützten Code zu verwenden, um ihre eigenen Ideen umzusetzen.

Worüber rede ich?

private double TranslateMeasurementIntoLinear (double actualMeasurement);

Es ist leicht, das Zeitalter des Wassermanns oder das, was heutzutage genannt wird, zu proklamieren, aber wenn mein Sensor von 1,0 auf 2,0 steigt, kann sich die Implementierung von Translate ... von einer einfachen linearen Gleichung ändern, die leicht verständlich und "neu" ist verwendbar "für alle, für eine ziemlich ausgefeilte Berechnung, die Analyse oder was auch immer verwendet, und so würde ich den Code anderer brechen. Warum? Weil sie die Prinzipien der Software-Codierung nicht verstanden haben, nicht einmal KIS.

Um dieses Märchen kurz zu machen: Wir brauchen eine einfache Möglichkeit, private Methoden zu testen - ohne Umschweife.

Erstens: Frohes neues Jahr allerseits!

Zweitens: Wiederholen Sie Ihren Architektenunterricht.

Drittens: Der "öffentliche" Modifikator ist Religion, keine Lösung.

CP70
quelle
5

Eine andere Option, die nicht erwähnt wurde, besteht darin, die Unit-Test-Klasse als untergeordnetes Element des zu testenden Objekts zu erstellen. NUnit Beispiel:

[TestFixture]
public class UnitTests : ObjectWithPrivateMethods
{
    [Test]
    public void TestSomeProtectedMethod()
    {
        Assert.IsTrue(this.SomeProtectedMethod() == true, "Failed test, result false");
    }
}

Dies würde ein einfaches Testen privater und geschützter (aber nicht vererbter privater) Methoden ermöglichen und es Ihnen ermöglichen, alle Ihre Tests vom tatsächlichen Code getrennt zu halten, damit Sie keine Testassemblys für die Produktion bereitstellen. Das Umstellen Ihrer privaten Methoden auf geschützte Methoden ist für viele geerbte Objekte akzeptabel, und es ist eine ziemlich einfache Änderung.

JEDOCH...

Obwohl dies ein interessanter Ansatz zur Lösung des Problems des Testens versteckter Methoden ist, bin ich mir nicht sicher, ob ich befürworten würde, dass dies in allen Fällen die richtige Lösung für das Problem ist. Es scheint ein wenig seltsam, ein Objekt intern zu testen, und ich vermute, dass es einige Szenarien gibt, in denen dieser Ansatz Sie in die Luft jagt. (Unveränderliche Objekte zum Beispiel können einige Tests sehr schwierig machen).

Während ich diesen Ansatz erwähne, würde ich vorschlagen, dass dies eher ein Brainstorming-Vorschlag als eine legitime Lösung ist. Nimm es mit einem Körnchen Salz.

EDIT: Ich finde es wirklich komisch, dass die Leute diese Antwort ablehnen, da ich dies ausdrücklich als schlechte Idee beschreibe. Bedeutet das, dass die Leute mir zustimmen? Ich bin so verwirrt.....

Roger Hill
quelle
Es ist eine kreative Lösung, aber irgendwie hackig.
Shinzou
3

Aus dem Buch Effektiv mit Legacy-Code arbeiten :

"Wenn wir eine private Methode testen müssen, sollten wir sie öffentlich machen. Wenn uns die Veröffentlichung stört, bedeutet dies in den meisten Fällen, dass unsere Klasse zu viel tut und wir sie reparieren sollten."

Die Möglichkeit, dies zu beheben, besteht laut Autor darin, eine neue Klasse zu erstellen und die Methode als hinzuzufügen public .

Der Autor erklärt weiter:

"Gutes Design ist testbar, und Design, das nicht testbar ist, ist schlecht."

Innerhalb dieser Grenzen besteht Ihre einzige echte Option darin, die Methode publicentweder in der aktuellen oder in einer neuen Klasse zu erstellen.

Kai Hartmann
quelle
2

TL; DR: Private Methode in eine andere Klasse extrahieren, auf dieser Klasse testen; Lesen Sie mehr über das SRP-Prinzip (Single Responsibility Principle)

Es scheint, dass Sie die privateMethode in eine andere Klasse extrahieren müssen . in diesem sollte sein public. Anstatt zu versuchen, die privateMethode zu testen , sollten Sie die publicMethode dieser anderen Klasse testen .

Wir haben das folgende Szenario:

Class A
+ outputFile: Stream
- _someLogic(arg1, arg2) 

Wir müssen die Logik von testen _someLogic; aber es scheint, dass Class Asie mehr Rolle spielen als nötig (gegen das SRP-Prinzip verstoßen); Refactor einfach in zwei Klassen

Class A1
    + A1(logicHandler: A2) # take A2 for handle logic
    + outputFile: Stream
Class A2
    + someLogic(arg1, arg2) 

Auf diese Weise someLogickönnte auf A2 getestet werden; In A1 erstellen Sie einfach ein falsches A2 und injizieren es dann in den Konstruktor, um zu testen, ob A2 für die genannte Funktion aufgerufen wird someLogic.

o0omycomputero0o
quelle
0

In VS 2005/2008 können Sie Private Accessor verwenden , um private Mitglieder zu testen. Diese Methode wurde jedoch in späteren Versionen von VS nicht mehr verwendet

Allen
quelle
1
Gute Antwort von 2008 bis vielleicht Anfang 2010. Lesen Sie jetzt die Alternativen zu PrivateObject und Reflection (siehe mehrere Antworten oben). VS2010 hatte Accessor-Fehler, MS hat sie in VS2012 abgelehnt. Wenn Sie nicht gezwungen sind, in VS2010 oder älter zu bleiben (> 18 Jahre alte Build-Tools), sparen Sie sich Zeit, indem Sie private Accessoren vermeiden. :-).
Zephan Schroeder