Wie testest du private Methoden?


479

Ich baue eine Klassenbibliothek auf, die einige öffentliche und private Methoden enthält. Ich möchte in der Lage sein, die privaten Methoden zu testen (hauptsächlich während der Entwicklung, aber auch für zukünftige Umgestaltungen).

Was ist der richtige Weg, um dies zu tun?


3
Vielleicht fehlt mir etwas, oder vielleicht ist es nur so, dass diese Frage pre-historicin Bezug auf Internetjahre ... ist , aber das Testen von privaten Methoden in Einheiten ist jetzt sowohl einfach als auch unkompliziert, da Visual Studio bei Bedarf und bei Bedarf die erforderlichen Accessor-Klassen erstellt Vorfüllen der Testlogik mit Ausschnitten, die fast dem entsprechen, was man sich für einfache Funktionstests wünscht. Siehe z. msdn.microsoft.com/en-us/library/ms184807%28VS.90%29.aspx
mjv

3
Dies scheint ein nahezu Duplikat von stackoverflow.com/questions/34571/… zu sein .
Raedwald

Der Fragesteller verwendet möglicherweise kein Visual Studio
Dave

3
Interne Tests nicht durchführen: blog.ploeh.dk/2015/09/22/unit-testing-internals
Mark Seemann

Antworten:


122

Wenn Sie .net verwenden, sollten Sie das InternalsVisibleToAttribute verwenden .


86
Yuck. Dies wird in Ihre freigegebenen Assemblys kompiliert.
Jay

14
@ Jay - konnte man #if DEBUGdas InternalsVisibleToAttribut nicht verwenden , um es nicht auf den Release-Code anzuwenden?
mpontillo

20
@Mike, Sie könnten, aber dann können Sie nur Unit-Tests mit Debug-Code ausführen, nicht mit Release-Code. Da der Release-Code optimiert ist, sehen Sie möglicherweise ein anderes Verhalten und unterschiedliche Timings. Im Multithread-Code bedeutet dies, dass Ihre Komponententests die Rennbedingungen nicht angemessen erkennen. Viel besser ist es, die Reflexion über den folgenden Vorschlag von @ AmazedSaint zu verwenden oder das integrierte PrivateObject / PrivateType zu verwenden. Auf diese Weise können Sie Privates in Release-Builds anzeigen, vorausgesetzt, Ihr Test-Harness wird mit vollem Vertrauen ausgeführt (was MSTest lokal ausführt)
Jay

121
Was vermisse ich? Warum sollte dies die akzeptierte Antwort sein, wenn sie die spezifische Frage des Testens privater Methoden nicht beantwortet? InternalsVisibleTo macht nur Methoden verfügbar, die als intern und nicht als privat gekennzeichnet sind, wie vom OP angefordert (und der Grund, warum ich hier gelandet bin). Ich denke, ich muss PrivateObject weiterhin verwenden, wie von Seven beantwortet.
Darren Lewis

6
@Jay Ich weiß , das ist ein bisschen spät kommt, aber eine Möglichkeit ist , etwas ähnliches zu verwenden #if RELEASE_TESTum InternalsVisibleTowie Mike vermuten läßt, und eine Kopie Ihrer Release - Build - Konfiguration vornehmen , dass definiert RELEASE_TEST. Sie können Ihren Release-Code mit Optimierungen testen, aber wenn Sie tatsächlich für das Release erstellen, werden Ihre Tests weggelassen.
Shaz

349

Wenn Sie eine private Methode testen möchten, stimmt möglicherweise etwas nicht. Unit-Tests sollen (im Allgemeinen) die Schnittstelle einer Klasse testen, dh ihre öffentlichen (und geschützten) Methoden. Sie können natürlich eine Lösung dafür "hacken" (auch wenn Sie nur die Methoden öffentlich machen), aber Sie möchten möglicherweise auch Folgendes berücksichtigen:

  1. Wenn die Methode, die Sie testen möchten, wirklich einen Test wert ist, kann es sich lohnen, sie in eine eigene Klasse zu verschieben.
  2. Fügen Sie den öffentlichen Methoden, die die private Methode aufrufen, weitere Tests hinzu und testen Sie die Funktionalität der privaten Methode. (Wie die Kommentatoren angegeben haben, sollten Sie dies nur tun, wenn die Funktionalität dieser privaten Methoden tatsächlich Teil der öffentlichen Schnittstelle ist. Wenn sie tatsächlich Funktionen ausführen, die dem Benutzer verborgen bleiben (dh der Komponententest), ist dies wahrscheinlich schlecht.)

38
Bei Option 2 müssen die Komponententests Kenntnisse über die zugrunde liegende Implementierung der Funktion haben. Das mache ich nicht gern. Ich denke im Allgemeinen, dass Unit-Tests die Funktion testen sollten, ohne etwas über die Implementierung anzunehmen.
Herms

15
Der Nachteil der Testimplementierung besteht darin, dass die Tests nur dann unterbrochen werden können, wenn Sie Änderungen an der Implementierung vornehmen. Dies ist unerwünscht, da Refactoring genauso wichtig ist wie das Schreiben der Tests in TDD.
JtR

30
Nun, die Tests sollten abbrechen, wenn Sie die Implementierung ändern. TDD würde bedeuten, zuerst die Tests zu ändern.
Sleske

39
@sleske - da stimme ich nicht ganz zu. Wenn sich die Funktionalität nicht geändert hat, gibt es keinen Grund, warum der Test unterbrochen werden sollte, da Tests wirklich das Verhalten / den Status testen sollten, nicht die Implementierung. Dies war es, was jtr damit meinte, dass Ihre Tests zerbrechlich wurden. In einer idealen Welt sollten Sie in der Lage sein, Ihren Code umzugestalten und Ihre Tests noch zu bestehen, um sicherzustellen, dass Ihr Refactoring die Funktionalität Ihres Systems nicht verändert hat.
Alconja

26
Entschuldigung für die Naivität (noch nicht viel Erfahrung mit dem Testen), aber ist die Idee des Unit-Tests nicht, jedes Codemodul für sich zu testen? Ich verstehe nicht wirklich, warum private Methoden von dieser Idee ausgeschlossen werden sollten.
OMill

118

Es ist möglicherweise nicht sinnvoll, private Methoden zu testen. Manchmal rufe ich aber auch gerne private Methoden aus Testmethoden auf. Meistens, um eine Codeduplizierung für die Testdatengenerierung zu verhindern ...

Microsoft bietet hierfür zwei Mechanismen an:

Accessoren

  • Wechseln Sie zum Quellcode der Klassendefinition
  • Klicken Sie mit der rechten Maustaste auf den Namen der Klasse
  • Wählen Sie "Private Accessor erstellen"
  • Wählen Sie das Projekt aus, in dem der Accessor erstellt werden soll => Sie erhalten eine neue Klasse mit dem Namen foo_accessor. Diese Klasse wird während der Kompilierung dynamisch generiert und teilt alle öffentlich verfügbaren Mitglieder auf.

Der Mechanismus ist jedoch manchmal etwas unlösbar, wenn es um Änderungen der Schnittstelle der ursprünglichen Klasse geht. Daher vermeide ich es meistens, dies zu verwenden.

PrivateObject-Klasse Die andere Möglichkeit besteht darin, Microsoft.VisualStudio.TestTools.UnitTesting.PrivateObject zu verwenden

// Wrap an already existing instance
PrivateObject accessor = new PrivateObject( objectInstanceToBeWrapped );

// Retrieve a private field
MyReturnType accessiblePrivateField = (MyReturnType) accessor.GetField( "privateFieldName" );

// Call a private method
accessor.Invoke( "PrivateMethodName", new Object[] {/* ... */} );

2
Wie rufen Sie private statische Methoden auf?
StuperUser

18
Private Accessoren sind in Visual Studio 2012 veraltet .
Ryan Gates

3
Die Accessor-Methode zum Testen privater Methoden wird ab VS 2011 nicht mehr unterstützt. blogs.msdn.com/b/visualstudioalm/archive/2012/03/08/…
Sanjit Misra

1
Wenn ich die Dokumente auf der Microsoft-Website hier lese, sehe ich keine Erwähnung, dass die PrivateObject-Klasse veraltet ist. Ich verwende MSVS 2013 und es funktioniert wie erwartet.
Stackunderflow

3
@RyanGates Die erste Lösung auf der Site, auf die Sie verwiesen haben, besagt, dass die Lösung für den Zugriff auf private Mitglieder darin besteht, "die PrivateObject-Klasse zu verwenden, um den Zugriff auf interne und private APIs in Ihrem Code zu unterstützen. Diese finden Sie in Microsoft.VisualStudio.QualityTools.UnitTestFramework. DLL-Assembly. "
Stackunderflow

78

Ich stimme der Philosophie "Sie sollten nur daran interessiert sein, die externe Schnittstelle zu testen" nicht zu. Es ist ein bisschen so, als würde man sagen, dass eine Autowerkstatt nur Tests durchführen sollte, um festzustellen, ob sich die Räder drehen. Ja, letztendlich interessiert mich das externe Verhalten, aber ich mag es, wenn meine eigenen, privaten, internen Tests etwas spezifischer und auf den Punkt gebracht werden. Ja, wenn ich refaktoriere, muss ich möglicherweise einige der Tests ändern, aber wenn es sich nicht um einen massiven Refaktor handelt, muss ich nur einige ändern, und die Tatsache, dass die anderen (unveränderten) internen Tests noch funktionieren, ist ein guter Indikator dafür Das Refactoring war erfolgreich.

Sie können versuchen, alle internen Fälle nur über die öffentliche Schnittstelle abzudecken. Theoretisch ist es möglich, jede interne Methode (oder zumindest jede wichtige) vollständig über die öffentliche Schnittstelle zu testen. Möglicherweise müssen Sie jedoch auf dem Kopf stehen, um dies zu erreichen Dies und die Verbindung zwischen den Testfällen, die über die öffentliche Schnittstelle ausgeführt werden, und dem internen Teil der Lösung, die getestet werden soll, ist möglicherweise schwer oder gar nicht zu erkennen. Einzelne Tests, die sicherstellen, dass die internen Maschinen ordnungsgemäß funktionieren, sind die geringfügigen Teständerungen wert, die beim Refactoring auftreten - zumindest war dies meine Erfahrung. Wenn Sie bei jedem Refactoring große Änderungen an Ihren Tests vornehmen müssen, ist dies möglicherweise nicht sinnvoll, aber in diesem Fall sollten Sie Ihr Design möglicherweise komplett überdenken.


20
Ich fürchte, ich stimme dir immer noch nicht zu. Durch die Behandlung jeder Komponente als Black Box können Module problemlos ausgetauscht werden. Wenn Sie eine haben FooService, die zu tun hat X, sollten Sie sich nur darum kümmern, dass dies tatsächlich der Fall ist, Xwenn Sie dazu aufgefordert werden. Wie es geht, sollte keine Rolle spielen. Wenn es Probleme in der Klasse gibt, die über die Schnittstelle nicht erkennbar sind (unwahrscheinlich), ist sie immer noch gültig FooService. Wenn es sich um ein Problem handelt, das über die Benutzeroberfläche sichtbar ist , sollte es bei einem Test an öffentlichen Mitgliedern erkannt werden. Der springende Punkt sollte sein, dass das Rad, solange es sich richtig dreht, als Rad verwendet werden kann.
Basic

5
Ein allgemeiner Ansatz besteht darin, dass Ihre interne Logik, wenn sie so kompliziert ist, dass Sie der Meinung sind, dass sie Unit-Tests erfordert, möglicherweise in eine Art Hilfsklasse mit einer öffentlichen Schnittstelle extrahiert werden muss, die Unit-getestet werden kann. Dann kann Ihre 'Eltern'-Klasse einfach diesen Helfer verwenden, und jeder kann entsprechend einem Unit-Test unterzogen werden.
Mir

9
@Basic: Völlig falsche Logik in dieser Antwort. Ein klassischer Fall, wenn Sie eine private Methode benötigen, ist, wenn Sie Code benötigen, der von öffentlichen Methoden wiederverwendet werden kann. Sie geben diesen Code in eine PrivMethod ein. Diese Methode sollte nicht öffentlich zugänglich gemacht werden, sondern muss getestet werden, um sicherzustellen, dass öffentliche Methoden, die auf PrivMethod basieren, sich wirklich darauf verlassen können.
Dima

6
@Dima Sicherlich, wenn es ein Problem mit gibt PrivMethod, ein Test, bei PubMethoddem Anrufe PrivMethodes aufdecken sollten? Was passiert, wenn Sie Ihre SimpleSmtpServicezu a ändern GmailService? Plötzlich zeigen Ihre privaten Tests auf Code, der nicht mehr existiert oder möglicherweise anders funktioniert und fehlschlagen würde, obwohl die Anwendung möglicherweise perfekt wie geplant funktioniert. Wenn es eine komplexe Verarbeitung gibt, die für beide E-Mail-Absender gelten würde, sollte sie sich möglicherweise in einer EmailProcessorbefinden, die von beiden verwendet und separat getestet werden kann.
Basic

2
@miltonb Wir betrachten dies möglicherweise aus verschiedenen Entwicklungsstilen. WRT die Interna, ich neige nicht dazu, sie Unit-Test. Wenn es ein Problem gibt (wie durch Schnittstellentests festgestellt), kann es entweder leicht durch Anhängen eines Debuggers aufgespürt werden oder die Klasse ist zu komplex und sollte aufgeteilt werden (mit der öffentlichen Schnittstelle der neuen getesteten Klasseneinheit). IMHO
Basic

51

In den seltenen Fällen, in denen ich private Funktionen testen wollte, habe ich sie normalerweise so geändert, dass sie stattdessen geschützt sind, und ich habe eine Unterklasse mit einer öffentlichen Wrapper-Funktion geschrieben.

Die Klasse:

...

protected void APrivateFunction()
{
    ...
}

...

Unterklasse zum Testen:

...

[Test]
public void TestAPrivateFunction()
{
    APrivateFunction();
    //or whatever testing code you want here
}

...

1
Sie können diese untergeordnete Klasse sogar in Ihre Komponententestdatei aufnehmen, anstatt die reale Klasse zu überladen. +1 für List.
Tim Abell

Wenn möglich, füge ich immer den gesamten testbezogenen Code in das Unit-Test-Projekt ein. Dies war nur Pseudocode.
Jason Jackson

2
Diese Funktion ist nicht privat, sie ist geschützt, das Nettoergebnis ... Sie haben Ihren Code weniger sicher gemacht / der privaten Funktionalität von
Krieg

22

Ich denke, eine grundlegendere Frage sollte gestellt werden: Warum versuchen Sie überhaupt, die private Methode zu testen? Dies ist ein Codegeruch, bei dem Sie versuchen, die private Methode über die öffentliche Schnittstelle dieser Klasse zu testen, während diese Methode aus einem bestimmten Grund privat ist, da es sich um ein Implementierungsdetail handelt. Man sollte sich nur mit dem Verhalten der öffentlichen Schnittstelle befassen, nicht mit der Art und Weise, wie sie unter dem Deckmantel implementiert wird.

Wenn ich das Verhalten der privaten Methode mithilfe allgemeiner Refactorings testen möchte, kann ich ihren Code in eine andere Klasse extrahieren (möglicherweise mit Sichtbarkeit auf Paketebene, um sicherzustellen, dass er nicht Teil einer öffentlichen API ist). Ich kann dann sein Verhalten isoliert testen.

Das Produkt des Refactorings bedeutet, dass die private Methode jetzt eine separate Klasse ist, die zu einem Kollaborateur der ursprünglichen Klasse geworden ist. Sein Verhalten wird durch eigene Unit-Tests gut verstanden worden sein.

Ich kann mich dann über sein Verhalten lustig machen, wenn ich versuche, die ursprüngliche Klasse zu testen, damit ich mich darauf konzentrieren kann, das Verhalten der öffentlichen Schnittstelle dieser Klasse zu testen, anstatt eine kombinatorische Explosion der öffentlichen Schnittstelle und das Verhalten aller ihrer privaten Methoden testen zu müssen .

Ich sehe das analog zum Autofahren. Wenn ich ein Auto fahre, fahre ich nicht mit geöffneter Motorhaube, damit ich sehen kann, dass der Motor funktioniert. Ich verlasse mich auf die Schnittstelle, die das Auto bietet, nämlich den Drehzahlmesser und den Tacho, um zu wissen, dass der Motor funktioniert. Ich verlasse mich darauf, dass sich das Auto tatsächlich bewegt, wenn ich das Gaspedal drücke. Wenn ich den Motor testen möchte, kann ich das isoliert überprüfen. : D.

Natürlich kann das direkte Testen privater Methoden ein letzter Ausweg sein, wenn Sie eine Legacy-Anwendung haben, aber ich würde es vorziehen, wenn Legacy-Code überarbeitet wird, um bessere Tests zu ermöglichen. Michael Feathers hat zu diesem Thema ein großartiges Buch geschrieben. http://www.amazon.co.uk/Working-Effectively-Legacy-Robert-Martin/dp/0131177052


16
Völlig falsche Logik in dieser Antwort. Ein klassischer Fall, wenn Sie eine private Methode benötigen, ist, wenn Sie Code benötigen, der von öffentlichen Methoden wiederverwendet werden kann. Sie geben diesen Code in eine PrivMethod ein. Diese Methode sollte nicht öffentlich zugänglich gemacht werden, sondern muss getestet werden, um sicherzustellen, dass öffentliche Methoden, die auf PrivMethod basieren, sich wirklich darauf verlassen können.
Dima

Sinnvoll während der ersten Entwicklung, aber möchten Sie Tests für private Methoden in Ihrem Standard-Regressionsanzug? Wenn sich die Implementierung ändert, kann die Testsuite beschädigt werden. OTOH: Wenn sich Ihre Regressionstests nur auf extern sichtbare öffentliche Methoden konzentrieren, sollte die Regressionssuite den Fehler weiterhin erkennen, wenn die private Methode später unterbrochen wird. Bei Bedarf können Sie dann den alten privaten Test bei Bedarf abstauben.
Alex Blakemore

9
Nicht einverstanden, sollten Sie nur die öffentliche Schnittstelle testen, sonst warum private Methoden erforderlich sind. Machen Sie sie in diesem Fall alle öffentlich und testen Sie sie alle. Wenn Sie private Methoden testen, brechen Sie die Kapselung. Wenn Sie eine private Methode testen möchten und sie in mehreren öffentlichen Methoden verwendet wird, sollte sie in eine eigene Klasse verschoben und isoliert getestet werden. Alle öffentlichen Methoden sollten dann an diese neue Klasse delegieren. Auf diese Weise haben Sie noch Tests für die Die Schnittstelle der ursprünglichen Klasse und Sie können überprüfen, ob sich das Verhalten nicht geändert hat, und Sie haben separate Tests für die delegierte private Methode.
Big Kahuna

@ Big Kahuna - Wenn Sie der Meinung sind, dass es keinen Fall gibt, in dem Sie private Methoden testen müssen, haben Sie nie mit einem ausreichend großen / komplexen Projekt gearbeitet. Oftmals endet eine öffentliche Funktion wie clientspezifische Validierungen mit 20 Zeilen, die nur sehr einfache private Methoden aufrufen, um den Code besser lesbar zu machen, aber Sie müssen immer noch jede einzelne private Methode testen. Das 20-fache Testen der öffentlichen Funktion erschwert das Debüt, wenn die Komponententests fehlschlagen.
Pedro.The.Kid

1
Ich arbeite für eine FTSE 100 Firma. Ich glaube, ich habe in meiner Zeit mehrere komplexe Projekte gesehen, danke. Wenn Sie auf dieser Ebene testen müssen, sollte jede private Methode als separate Mitarbeiter isoliert getestet werden, da dies impliziert, dass sie ein individuelles Verhalten haben, das getestet werden muss. Der Test für das Hauptmediatorobjekt wird dann nur zu einem Interaktionstest. Es wird nur getestet, ob die richtige Strategie aufgerufen wird. Ihr Szenario klingt so, als ob die betreffende Klasse SRP nicht folgt. Es gibt keinen einzigen Grund für eine Änderung, sondern eine 20 => SRP-Verletzung. Lesen Sie das GOOS-Buch oder Onkel Bob. YMWV
Big Kahuna

17

Private Typen, Interna und private Mitglieder sind aus irgendeinem Grund so, und oft möchten Sie sich nicht direkt mit ihnen anlegen. Und wenn Sie dies tun, besteht die Möglichkeit, dass Sie später brechen, da es keine Garantie gibt, dass die Leute, die diese Assemblys erstellt haben, die privaten / internen Implementierungen als solche behalten.

Aber manchmal, wenn ich einige Hacks / Erkundungen von kompilierten Assemblys oder Assemblys von Drittanbietern durchführe, wollte ich selbst eine private Klasse oder eine Klasse mit einem privaten oder internen Konstruktor initialisieren. Oder manchmal, wenn ich mich mit vorkompilierten Legacy-Bibliotheken befasse, die ich nicht ändern kann, schreibe ich am Ende einige Tests gegen eine private Methode.

So entstand der AccessPrivateWrapper - http://amazedsaint.blogspot.com/2010/05/accessprivatewrapper-c-40-dynamic.html - eine schnelle Wrapper-Klasse, die die Arbeit mit dynamischen C # 4.0-Funktionen und vereinfacht.

Sie können interne / private Typen wie erstellen

    //Note that the wrapper is dynamic
    dynamic wrapper = AccessPrivateWrapper.FromType
        (typeof(SomeKnownClass).Assembly,"ClassWithPrivateConstructor");

    //Access the private members
    wrapper.PrivateMethodInPrivateClass();

12

Nun, Sie können die private Methode auf zwei Arten testen

  1. Sie können eine PrivateObjectKlasseninstanz erstellen. Die Syntax lautet wie folgt

    PrivateObject obj= new PrivateObject(PrivateClass);
    //now with this obj you can call the private method of PrivateCalss.
    obj.PrivateMethod("Parameters");
  2. Sie können Reflexion verwenden.

    PrivateClass obj = new PrivateClass(); // Class containing private obj
    Type t = typeof(PrivateClass); 
    var x = t.InvokeMember("PrivateFunc", 
        BindingFlags.InvokeMethod | BindingFlags.NonPublic | BindingFlags.Public |  
            BindingFlags.Instance, null, obj, new object[] { 5 });

Gute Antwort, aber für # 1 ist Ihre Syntax falsch. Sie müssen zuerst eine Instanz deklarieren PrivateClassund diese verwenden. stackoverflow.com/questions/9122708/…
SharpC

10

Ich habe auch die InternalsVisibleToAttribute-Methode verwendet. Erwähnenswert ist auch, dass wenn Sie sich unwohl fühlen, Ihre zuvor privaten Methoden intern zu machen, um dies zu erreichen, diese möglicherweise ohnehin nicht Gegenstand direkter Komponententests sein sollten.

Schließlich testen Sie das Verhalten Ihrer Klasse und nicht die spezifische Implementierung. Sie können die letztere ändern, ohne die erstere zu ändern, und Ihre Tests sollten weiterhin bestehen.


Ich liebe den Punkt, Verhalten zu testen und nicht zu implementieren. Wenn Sie Ihre Komponententests an die Implementierung binden (private Methoden), werden die Tests spröde und müssen geändert werden, wenn sich die Implementierung ändert.
Bob Horn

9

Es gibt zwei Arten von privaten Methoden. Statische private Methoden und nicht statische private Methoden (Instanzmethoden). In den folgenden 2 Artikeln wird anhand von Beispielen erläutert, wie private Methoden einem Unit-Test unterzogen werden.

  1. Unit Testing Static Private Methods
  2. Unit Testing Nicht statische private Methoden

Geben Sie einige Beispiele, geben Sie nicht nur einen Link an
vinculis

Sieht hässlich aus. Kein Intellisense. Schlechte Lösung von MS. Ich bin geschockt!
Alerya

Der einfachste Weg, private statische Methoden zu testen
Grant

8

In MS Test ist eine nette Funktion integriert, die private Mitglieder und Methoden im Projekt verfügbar macht, indem eine Datei namens VSCodeGenAccessors erstellt wird

[System.Diagnostics.DebuggerStepThrough()]
    [System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.VisualStudio.TestTools.UnitTestGeneration", "1.0.0.0")]
    internal class BaseAccessor
    {

        protected Microsoft.VisualStudio.TestTools.UnitTesting.PrivateObject m_privateObject;

        protected BaseAccessor(object target, Microsoft.VisualStudio.TestTools.UnitTesting.PrivateType type)
        {
            m_privateObject = new Microsoft.VisualStudio.TestTools.UnitTesting.PrivateObject(target, type);
        }

        protected BaseAccessor(Microsoft.VisualStudio.TestTools.UnitTesting.PrivateType type)
            :
                this(null, type)
        {
        }

        internal virtual object Target
        {
            get
            {
                return m_privateObject.Target;
            }
        }

        public override string ToString()
        {
            return this.Target.ToString();
        }

        public override bool Equals(object obj)
        {
            if (typeof(BaseAccessor).IsInstanceOfType(obj))
            {
                obj = ((BaseAccessor)(obj)).Target;
            }
            return this.Target.Equals(obj);
        }

        public override int GetHashCode()
        {
            return this.Target.GetHashCode();
        }
    }

Mit Klassen, die von BaseAccessor abgeleitet sind

sowie

[System.Diagnostics.DebuggerStepThrough()]
[System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.VisualStudio.TestTools.UnitTestGeneration", "1.0.0.0")]
internal class SomeClassAccessor : BaseAccessor
{

    protected static Microsoft.VisualStudio.TestTools.UnitTesting.PrivateType m_privateType = new Microsoft.VisualStudio.TestTools.UnitTesting.PrivateType(typeof(global::Namespace.SomeClass));

    internal SomeClassAccessor(global::Namespace.Someclass target)
        : base(target, m_privateType)
    {
    }

    internal static string STATIC_STRING
    {
        get
        {
            string ret = ((string)(m_privateType.GetStaticField("STATIC_STRING")));
            return ret;
        }
        set
        {
            m_privateType.SetStaticField("STATIC_STRING", value);
        }
    }

    internal int memberVar    {
        get
        {
            int ret = ((int)(m_privateObject.GetField("memberVar")));
            return ret;
        }
        set
        {
            m_privateObject.SetField("memberVar", value);
        }
    }

    internal int PrivateMethodName(int paramName)
    {
        object[] args = new object[] {
            paramName};
        int ret = (int)(m_privateObject.Invoke("PrivateMethodName", new System.Type[] {
                typeof(int)}, args)));
        return ret;
    }

8
Die gen'd-Dateien existieren nur in VS2005. 2008 werden sie hinter den Kulissen generiert. Und sie sind ein Greuel. Und die zugehörige Shadow-Aufgabe ist auf einem Build-Server unzuverlässig.
Ruben Bartelink

Auch Accessoren wurden in VS2012-2013 nicht mehr unterstützt.
Zephan Schroeder

5

In CodeProject gibt es einen Artikel, in dem kurz die Vor- und Nachteile des Testens privater Methoden erläutert werden. Anschließend wird ein Reflektionscode für den Zugriff auf private Methoden bereitgestellt (ähnlich dem oben von Marcus bereitgestellten Code). Das einzige Problem, das ich im Beispiel festgestellt habe, ist, dass der Code überladene Methoden nicht berücksichtigt.

Den Artikel finden Sie hier:

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



4

Ich neige dazu, keine Compiler-Direktiven zu verwenden, weil sie die Dinge schnell durcheinander bringen. Eine Möglichkeit, dies zu verringern, wenn Sie sie wirklich benötigen, besteht darin, sie in eine Teilklasse einzuteilen und Ihren Build diese CS-Datei bei der Erstellung der Produktionsversion ignorieren zu lassen.


Sie würden die Test-Accessoren in eine Produktionsversion aufnehmen (um Compiler-Optimierungen usw. zu testen), sie jedoch in einer Release-Version ausschließen. Aber ich spalte die Haare und habe das trotzdem positiv bewertet, weil ich denke, dass es eine gute Idee ist, das Zeug an einem einzigen Ort zu platzieren. Danke für die Idee.
CAD Kerl

4

Sie sollten die privaten Methoden Ihres Codes überhaupt nicht testen. Sie sollten die 'öffentliche Schnittstelle' oder API testen, die öffentlichen Dinge Ihrer Klassen. Die API sind alle öffentlichen Methoden, die Sie externen Anrufern zur Verfügung stellen.

Der Grund dafür ist, dass Sie, sobald Sie mit dem Testen der privaten Methoden und Interna Ihrer Klasse beginnen, die Implementierung Ihrer Klasse (die privaten Dinge) mit Ihren Tests koppeln. Dies bedeutet, dass Sie bei der Änderung Ihrer Implementierungsdetails auch Ihre Tests ändern müssen.

Aus diesem Grund sollten Sie die Verwendung von InternalsVisibleToAtrribute vermeiden.

Hier ist ein großartiger Vortrag von Ian Cooper, der dieses Thema behandelt: Ian Cooper: TDD, wo ist alles schief gelaufen?


3

Manchmal kann es sinnvoll sein, private Erklärungen zu testen. Grundsätzlich verfügt ein Compiler nur über eine öffentliche Methode: Compile (Zeichenfolge outputFileName, params string [] sourceSFileNames). Ich bin sicher, Sie verstehen, dass es schwierig wäre, eine solche Methode zu testen, ohne jede "versteckte" Deklaration zu testen!

Aus diesem Grund haben wir Visual T #: erstellt, um Tests zu vereinfachen. Es ist eine kostenlose .NET-Programmiersprache (C # v2.0-kompatibel).

Wir haben den Operator '.-' hinzugefügt. Es verhält sich einfach wie '.' Operator, außer Sie können auch auf versteckte Deklarationen aus Ihren Tests zugreifen, ohne etwas in Ihrem getesteten Projekt zu ändern.

Schauen Sie sich unsere Website an: Laden Sie sie kostenlos herunter .


3

Ich bin überrascht, dass dies noch niemand gesagt hat, aber eine Lösung, die ich angewendet habe, besteht darin, innerhalb der Klasse eine statische Methode zu erstellen, um sich selbst zu testen. Auf diese Weise haben Sie Zugriff auf alles Öffentliche und Private zum Testen.

Darüber hinaus können Sie in einer Skriptsprache (mit OO-Fähigkeiten wie Python, Ruby und PHP) den Dateitest selbst ausführen, wenn er ausgeführt wird. Eine gute schnelle Möglichkeit, um sicherzustellen, dass Ihre Änderungen nichts kaputt machen. Dies ist offensichtlich eine skalierbare Lösung zum Testen aller Klassen: Führen Sie sie einfach alle aus. (Sie können dies auch in anderen Sprachen mit einer ungültigen Hauptleitung tun, die immer auch ihre Tests ausführt).


1
Obwohl dies praktisch ist, ist es nicht sehr elegant. Dies kann zu einer gewissen Verwirrung in der Codebasis führen und ermöglicht es Ihnen auch nicht, Ihre Tests von Ihrem echten Code zu trennen. Die Möglichkeit, extern zu testen, eröffnet die Möglichkeit, automatisierte Tests per Skript durchzuführen, anstatt statische Methoden von Hand zu schreiben.
Darren Reid

Dies schließt nicht aus, dass Sie extern testen. Rufen Sie einfach die statische Methode auf, wie Sie möchten. Die Codebasis ist auch nicht chaotisch ... Sie benennen die Methode entsprechend. Ich benutze "runTests", aber alles ähnliche funktioniert.
Rube

Ihr Recht, es schließt nicht aus, extern zu testen, aber es generiert viel mehr Code, dh macht die Codebasis chaotisch. Jede Klasse verfügt möglicherweise über viele private Testmethoden, mit denen ihre Variablen in einem oder mehreren ihrer Konstruktoren initialisiert werden. Zum Testen müssen Sie mindestens so viele statische Methoden schreiben, wie es zu testende Methoden gibt, und die Testmethoden müssen möglicherweise groß sein, um die richtigen Werte zu initialisieren. Dies würde die Wartung des Codes erschweren. Wie andere gesagt haben, ist das Testen des Verhaltens einer Klasse ein besserer Ansatz, der Rest sollte klein genug sein, um Fehler zu beheben.
Darren Reid

Ich benutze die gleiche Anzahl von Zeilen zum Testen wie jeder andere (eigentlich weniger, wie Sie später lesen werden). Sie müssen nicht ALLE Ihre privaten Methoden testen. Nur diejenigen, die getestet werden müssen :) Sie müssen auch nicht jeden in einer separaten Methode testen. Ich mache es mit einem Anruf. Dies erschwert die Wartung des Codes WENIGER, da alle meine Klassen dieselbe Umbrella-Unit-Testmethode haben, mit der alle privaten und geschützten Unit-Tests Zeile für Zeile ausgeführt werden. Das gesamte Testgeschirr ruft dann für alle meine Klassen dieselbe Methode auf, und die Wartung befindet sich alle in meinen Klassentests und allen.
Rube

3

Ich möchte hier ein klares Codebeispiel erstellen, das Sie für jede Klasse verwenden können, in der Sie die private Methode testen möchten.

Nehmen Sie diese Methoden in Ihre Testfallklasse auf und wenden Sie sie dann wie angegeben an.

  /**
   *
   * @var Class_name_of_class_you_want_to_test_private_methods_in
   * note: the actual class and the private variable to store the 
   * class instance in, should at least be different case so that
   * they do not get confused in the code.  Here the class name is
   * is upper case while the private instance variable is all lower
   * case
   */
  private $class_name_of_class_you_want_to_test_private_methods_in;

  /**
   * This uses reflection to be able to get private methods to test
   * @param $methodName
   * @return ReflectionMethod
   */
  protected static function getMethod($methodName) {
    $class = new ReflectionClass('Class_name_of_class_you_want_to_test_private_methods_in');
    $method = $class->getMethod($methodName);
    $method->setAccessible(true);
    return $method;
  }

  /**
   * Uses reflection class to call private methods and get return values.
   * @param $methodName
   * @param array $params
   * @return mixed
   *
   * usage:     $this->_callMethod('_someFunctionName', array(param1,param2,param3));
   *  {params are in
   *   order in which they appear in the function declaration}
   */
  protected function _callMethod($methodName, $params=array()) {
    $method = self::getMethod($methodName);
    return $method->invokeArgs($this->class_name_of_class_you_want_to_test_private_methods_in, $params);
  }

$ this -> _ callMethod ('_ someFunctionName', Array (param1, param2, param3));

Geben Sie die Parameter einfach in der Reihenfolge aus, in der sie in der ursprünglichen privaten Funktion angezeigt werden


3

Für alle, die private Methoden ohne viel Aufwand ausführen möchten. Dies funktioniert mit jedem Unit-Test-Framework, das nur gute alte Reflection verwendet.

public class ReflectionTools
{
    // If the class is non-static
    public static Object InvokePrivate(Object objectUnderTest, string method, params object[] args)
    {
        Type t = objectUnderTest.GetType();
        return t.InvokeMember(method,
            BindingFlags.InvokeMethod |
            BindingFlags.NonPublic |
            BindingFlags.Instance |
            BindingFlags.Static,
            null,
            objectUnderTest,
            args);
    }
    // if the class is static
    public static Object InvokePrivate(Type typeOfObjectUnderTest, string method, params object[] args)
    {
        MemberInfo[] members = typeOfObjectUnderTest.GetMembers(BindingFlags.NonPublic | BindingFlags.Static);
        foreach(var member in members)
        {
            if (member.Name == method)
            {
                return typeOfObjectUnderTest.InvokeMember(method, BindingFlags.NonPublic | BindingFlags.Static | BindingFlags.InvokeMethod, null, typeOfObjectUnderTest, args);
            }
        }
        return null;
    }
}

Dann können Sie in Ihren eigentlichen Tests Folgendes tun:

Assert.AreEqual( 
  ReflectionTools.InvokePrivate(
    typeof(StaticClassOfMethod), 
    "PrivateMethod"), 
  "Expected Result");

Assert.AreEqual( 
  ReflectionTools.InvokePrivate(
    new ClassOfMethod(), 
    "PrivateMethod"), 
  "Expected Result");

2

MbUnit hat dafür einen schönen Wrapper namens Reflector bekommen.

Reflector dogReflector = new Reflector(new Dog());
dogReflector.Invoke("DreamAbout", DogDream.Food);

Sie können auch Werte aus Eigenschaften festlegen und abrufen

dogReflector.GetProperty("Age");

In Bezug auf den "Test privat" stimme ich zu, dass .. in der perfekten Welt. Es macht keinen Sinn, private Unit-Tests durchzuführen. In der realen Welt möchten Sie möglicherweise private Tests schreiben, anstatt Code umzugestalten.


4
Nur zur Information, Reflectorwurde durch die leistungsstärkere Mirrorin Gallio / MbUnit v3.2 ersetzt. ( gallio.org/wiki/doku.php?id=mbunit:mirror )
Yann Trevin

2

Hier ist ein guter Artikel über Unit-Tests von privaten Methoden. Ich bin mir jedoch nicht sicher, was besser ist, um Ihre Anwendung speziell für Tests zu entwickeln (es ist wie das Erstellen von Tests nur zum Testen) oder Reflexion zum Testen zu verwenden. Ich bin mir ziemlich sicher, dass die meisten von uns den zweiten Weg wählen werden.


2

Meiner Meinung nach sollten Sie nur die öffentliche API Ihrer Klasse testen.

Wenn Sie eine Methode veröffentlichen, um sie einem Unit-Test zu unterziehen, wird die Kapselung unterbrochen, um Implementierungsdetails freizulegen.

Eine gute öffentliche API löst ein unmittelbares Ziel des Client-Codes und löst dieses Ziel vollständig.


Dies sollte die richtige Antwort sein, IMO. Wenn Sie viele private Methoden haben, liegt dies wahrscheinlich daran, dass Sie eine versteckte Klasse haben, die Sie in eine eigene öffentliche Schnittstelle aufteilen sollten.
Sunefred

2

Ich benutze die PrivateObject- Klasse. Aber wie bereits erwähnt besser, um das Testen privater Methoden zu vermeiden.

Class target = new Class();
PrivateObject obj = new PrivateObject(target);
var retVal = obj.Invoke("PrivateMethod");
Assert.AreEqual(retVal);

2
CC -Dprivate=public

"CC" ist der Befehlszeilen-Compiler auf dem von mir verwendeten System. -Dfoo=barmacht das Äquivalent von #define foo bar. Diese Kompilierungsoption ändert also effektiv alle privaten Inhalte in öffentliche.


2
Was ist das? Gilt das für Visual Studio?
YeahStu

"CC" ist der Befehlszeilen-Compiler auf dem von mir verwendeten System. "-Dfoo = bar" entspricht "#define foo bar". Diese Kompilierungsoption ändert also effektiv alle privaten Inhalte in öffentliche. Haha!
Mark Harrison

Legen Sie in Visual Studio eine Definition in Ihrer Build-Umgebung fest.
Mark Harrison

1

Hier ist ein Beispiel, zuerst die Methodensignatur:

private string[] SplitInternal()
{
    return Regex.Matches(Format, @"([^/\[\]]|\[[^]]*\])+")
                        .Cast<Match>()
                        .Select(m => m.Value)
                        .Where(s => !string.IsNullOrEmpty(s))
                        .ToArray();
}

Hier ist der Test:

/// <summary>
///A test for SplitInternal
///</summary>
[TestMethod()]
[DeploymentItem("Git XmlLib vs2008.dll")]
public void SplitInternalTest()
{
    string path = "pair[path/to/@Key={0}]/Items/Item[Name={1}]/Date";
    object[] values = new object[] { 2, "Martin" };
    XPathString xp = new XPathString(path, values);

    PrivateObject param0 = new PrivateObject(xp);
    XPathString_Accessor target = new XPathString_Accessor(param0);
    string[] expected = new string[] {
        "pair[path/to/@Key={0}]",
        "Items",
        "Item[Name={1}]",
        "Date"
    };
    string[] actual;
    actual = target.SplitInternal();
    CollectionAssert.AreEqual(expected, actual);
}

1

Eine Möglichkeit, dies zu tun, besteht darin, Ihre Methode zu haben protectedund ein Testgerät zu schreiben, das Ihre zu testende Klasse erbt. Auf diese Weise drehen Sie Ihre Methode nicht public, aber Sie aktivieren das Testen.


Ich bin damit nicht einverstanden, da Sie Ihren Verbrauchern auch erlauben, von der Basisklasse zu erben und die geschützten Funktionen zu verwenden. Dies war etwas, das Sie in erster Linie verhindern wollten, indem Sie diese Funktionen privat oder intern machten.
Nick N.

1

1) Wenn Sie einen Legacy-Code haben, können Sie private Methoden nur durch Reflektion testen.

2) Wenn es sich um neuen Code handelt, haben Sie folgende Möglichkeiten:

  • Verwenden Sie Reflexion (zu kompliziert)
  • Schreiben Sie einen Komponententest in dieselbe Klasse (macht den Produktionscode hässlich, indem auch Testcode enthalten ist).
  • Refactor und machen Sie die Methode in einer Art Util-Klasse öffentlich
  • Verwenden Sie die Annotation @VisibleForTesting und entfernen Sie private

Ich bevorzuge die einfachste und am wenigsten komplizierte Anmerkungsmethode. Das einzige Problem ist, dass wir die Sichtbarkeit erhöht haben, was meiner Meinung nach kein großes Problem darstellt. Wir sollten immer für die Schnittstelle codieren. Wenn wir also eine Schnittstelle MyService und eine Implementierung MyServiceImpl haben, können wir die entsprechenden Testklassen MyServiceTest (Testschnittstellenmethoden) und MyServiceImplTest (private Testmethoden) haben. Alle Clients sollten sowieso die Schnittstelle verwenden, so dass es eigentlich keine Rolle spielen sollte, obwohl die Sichtbarkeit der privaten Methode erhöht wurde.


1

Sie können es auch als öffentlich oder intern deklarieren (mit InternalsVisibleToAttribute), während Sie im Debug-Modus erstellen:

    /// <summary>
    /// This Method is private.
    /// </summary>
#if DEBUG
    public
#else
    private
#endif
    static string MyPrivateMethod()
    {
        return "false";
    }

Es bläht den Code auf, wird aber privatein einem Release-Build sein.


0

Sie können die Testmethode für die private Methode in Visual Studio 2008 generieren. Wenn Sie einen Komponententest für eine private Methode erstellen, wird Ihrem Testprojekt ein Ordner "Test References" und diesem Ordner ein Accessor hinzugefügt. Auf den Accessor wird auch in der Logik der Einheitentestmethode Bezug genommen. Mit diesem Accessor kann Ihr Komponententest private Methoden in dem Code aufrufen, den Sie testen. Für Details schauen Sie sich an

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


0

Beachten Sie auch, dass für InternalsVisibleToAtrribute eine starke Benennung Ihrer Assembly erforderlich ist. Dies führt zu eigenen Problemen, wenn Sie an einer Lösung arbeiten, für die diese Anforderung zuvor noch nicht bestand. Ich benutze den Accessor, um private Methoden zu testen. In dieser Frage finden Sie ein Beispiel dafür.


2
Nein, das InternalsVisibleToAttributeerfordert nicht , dass Ihre Assemblys einen starken Namen haben. Ich verwende es derzeit für ein Projekt, bei dem dies nicht der Fall ist.
Cody Gray

1
Um dies zu verdeutlichen: "Sowohl die aktuelle Assembly als auch die Friend-Assembly müssen nicht signiert sein oder beide müssen mit einem starken Namen signiert sein." - Von MSDN
Hari
Durch die Nutzung unserer Website bestätigen Sie, dass Sie unsere Cookie-Richtlinie und Datenschutzrichtlinie gelesen und verstanden haben.
Licensed under cc by-sa 3.0 with attribution required.