419 Stimmen

Unit-Tests für private Methoden in C#

Visual Studio ermöglicht Unit-Tests von privaten Methoden über eine automatisch generierte Accessor-Klasse. Ich habe einen Test einer privaten Methode geschrieben, der erfolgreich kompiliert, aber zur Laufzeit fehlschlägt. Eine ziemlich minimale Version des Codes und der Test 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 lautet:

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 damit wohl auch dem Compiler - ist target vom Typ TypeA_Accessor. Aber zur Laufzeit ist es vom Typ TypeA, und daher schlägt das Hinzufügen der Liste fehl.

Gibt es eine Möglichkeit, diesen Fehler zu beheben? Oder, vielleicht wahrscheinlicher, welche anderen Ratschläge haben andere Leute (ich sagen vielleicht "nicht private Methoden testen" und "nicht haben Unit-Tests manipulieren den Zustand der Objekte").

824voto

Scuttle Punkte 8147

Sie können die PrivateObject Klasse:

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

Nota: PrivateObject y PrivateType sind für Projekte, die auf netcoreapp2.0 ausgerichtet sind, nicht verfügbar - GitHub Thema 366

118voto

Shivprasad Koirala Punkte 25296

"Es gibt nichts, was man als Standard oder beste Praxis bezeichnen könnte, wahrscheinlich sind das nur populäre Meinungen".

Das Gleiche gilt auch für diese Diskussion.

enter image description here

Es hängt alles davon ab, was Sie denken, ist eine Einheit, wenn Sie denken, UNIT ist eine Klasse, dann werden Sie nur auf die öffentliche Methode. Wenn Sie UNIT für eine Codezeile halten, werden Sie sich nicht schuldig fühlen, wenn Sie auf private Methoden treffen.

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 ansehen ( http://www.youtube.com/watch?v=Vq6Gcs9LrPQ ), in dem gezeigt wird, wie "PrivateObject" zu verwenden ist, und in dem auch erörtert wird, ob das Testen von privaten Methoden logisch ist oder nicht.

110voto

Jeff Punkte 1397

Ein weiterer Gedanke ist, die Tests auf "interne" Klassen/Methoden auszudehnen, so dass diese Tests eher einem White-Box-Sinn entsprechen. Sie können verwenden InternalsVisibleTo Attribut für die Baugruppe, um sie separaten Unit-Test-Modulen zugänglich zu machen.

In Kombination mit einer versiegelten Klasse können Sie eine solche Kapselung erreichen, dass Ihre Testmethoden nur von der Unittest-Assembly sichtbar sind. Bedenken Sie, dass eine geschützte Methode in einer 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 Einheitstest:

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

51voto

Jack Davidson Punkte 3873

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

28voto

CP70 Punkte 287

Ähmh... Kam hier entlang mit genau dem gleichen Problem: Test a einfach , aber entscheidend privat Methode. Nach der Lektüre dieses Threads scheint es so zu sein: "Ich möchte dieses einfache Loch in dieses einfache Stück Metall bohren, und ich möchte sicherstellen, dass die Qualität den Spezifikationen entspricht", und dann kommt "Okay, das ist nicht so einfach. Zunächst einmal gibt es kein geeignetes Werkzeug dafür, aber man könnte ein Gravitationswellenobservatorium in seinem Garten bauen. Lesen Sie meinen Artikel unter http://foobar.brigther-than-einstein.org/ Zuerst muss man natürlich einige Kurse für fortgeschrittene Quantenphysik besuchen, dann braucht man tonnenweise ultrakaltes Nitrogenium, und dann natürlich mein Buch, das bei Amazon erhältlich ist"...

Mit anderen Worten...

Nein, das Wichtigste zuerst.

Jede einzelne Methode, sei sie privat, intern, geschützt, öffentlich hat testbar sein. Es muss eine Möglichkeit geben, solche Tests ohne solche Umstände durchzuführen, wie sie hier vorgestellt wurden.

Warum? Genau denn der architektonischen Erwähnungen, die bisher von einigen Mitwirkenden gemacht wurden. Vielleicht kann eine einfache Wiederholung der Softwareprinzipien einige Missverständnisse ausräumen.

In diesem Fall handelt es sich um die üblichen Verdächtigen: OCP, SRP und, wie immer, KIS.

Aber warten Sie einen Moment. Die Idee, alles öffentlich zugänglich zu machen, ist mehr oder weniger politisch und eine Art von Einstellung. Aber. Wenn es um Code geht, auch in der Open-Source-Gemeinschaft, ist das kein Dogma. Stattdessen ist es eine gute Praxis, etwas zu "verstecken", um es einfacher zu machen, sich mit einer bestimmten API vertraut zu machen. Sie würden zum Beispiel die Kernberechnungen Ihres neu auf dem Markt befindlichen digitalen Thermometer-Bausteins verstecken - nicht um die Mathematik hinter der tatsächlichen Messkurve vor neugierigen Code-Lesern zu verbergen, sondern um zu verhindern, dass Ihr Code von einigen, vielleicht plötzlich wichtigen Nutzern abhängig wird, die nicht widerstehen können, Ihren ehemals privaten, internen, geschützten Code zu verwenden, um ihre eigenen Ideen umzusetzen.

Wovon rede ich eigentlich?

private double TranslateMeasurementIntoLinear(double actualMeasurement);

Es ist leicht, das Zeitalter des Wassermanns auszurufen, aber wenn mein Sensor von 1.0 auf 2.0 wechselt, könnte sich die Implementierung von Translate... von einer einfachen linearen Gleichung, die für jeden leicht verständlich und "wiederverwendbar" ist, zu einer ziemlich ausgeklügelten Berechnung ändern, die Analyse oder was auch immer verwendet, und so würde ich den Code anderer kaputt machen. Und warum? Weil sie die Grundprinzipien der Software-Programmierung nicht verstanden haben, nicht einmal KIS.

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

Erstens: Frohes neues Jahr an alle!

Zweitens: Üben Sie Ihren Architektenunterricht.

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

CodeJaeger.com

CodeJaeger ist eine Gemeinschaft für Programmierer, die täglich Hilfe erhalten..
Wir haben viele Inhalte, und Sie können auch Ihre eigenen Fragen stellen oder die Fragen anderer Leute lösen.

Powered by:

X