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").

15voto

Roger Hill Punkte 2963

Eine andere Möglichkeit, die noch nicht erwähnt wurde, ist die Erstellung der Unit-Test-Klasse als untergeordnetes Objekt des zu testenden Objekts. 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 würde Ihnen erlauben, alle Ihre Tests vom eigentlichen Code getrennt zu halten, so dass Sie keine Test-Assemblies in der Produktion einsetzen müssen. Die Umstellung von privaten Methoden auf geschützte Methoden wäre bei vielen geerbten Objekten akzeptabel, und es ist eine recht einfach durchzuführende Änderung.

ABER...

Dies ist zwar ein interessanter Ansatz, um das Problem der Prüfung versteckter Methoden zu lösen, aber ich bin mir nicht sicher, ob dies in allen Fällen die richtige Lösung für dieses Problem ist. Es scheint etwas seltsam zu sein, ein Objekt intern zu testen, und ich vermute, dass es einige Szenarien gibt, in denen dieser Ansatz scheitern wird. (Unveränderliche Objekte zum Beispiel, könnte einige Tests wirklich schwer machen).

Ich erwähne diesen Ansatz, möchte aber darauf hinweisen, dass es sich dabei eher um einen Brainstorming-Vorschlag als um eine legitime Lösung handelt. Nehmen Sie ihn mit Vorsicht zur Kenntnis.

EDIT: Ich finde es wirklich witzig, dass die Leute diese Antwort ablehnen, da ich sie ausdrücklich als schlechte Idee bezeichne. Bedeutet das, dass die Leute mit mir übereinstimmen? Ich bin so verwirrt.....

9voto

Kai Hartmann Punkte 2952

Aus dem Buch Effizientes Arbeiten mit altem Code :

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

Laut dem Autor kann man dies beheben, indem man eine neue Klasse erstellt und die Methode als public .

Der Autor erklärt weiter:

"Gutes Design ist prüfbar, und Design, das nicht prüfbar ist, ist schlecht".

Innerhalb dieser Grenzen bleibt Ihnen also nur die Möglichkeit, die Methode public entweder in der aktuellen oder einer neuen Klasse.

8voto

Soma Mbadiwe Punkte 1459

Jetzt ist es 2022!

...und wir haben .NET6

Obwohl dies die Frage nicht wirklich beantwortet, ist mein bevorzugter Ansatz heutzutage, Code und Test im selben C#-Projekt unterzubringen, mit Namenskonventionen wie <ClassName>.Tests.cs . Dann verwende ich internal Zugriffsmodifikator anstelle von private .

In der Projektdatei habe ich etwas wie dieses:

<ItemGroup Condition="'$(Configuration)' == 'Release'">
  <Compile Remove="**\*.Tests.cs" />
</ItemGroup>

zum Ausschluss der Testdateien in release baut. Ändern Sie nach Bedarf.

FAQ 1 : Aber manchmal möchte man den Code auch im (optimierten) Release-Build testen.

Antwort : Ich finde es unnötig. Ich vertraue darauf, dass der Compiler seine Arbeit macht, ohne meine Absicht zu vereiteln. Bislang hatte ich keinen Grund, seine Fähigkeit dazu in Frage zu stellen.

FAQ 2 : Aber ich möchte die Methode (oder Klasse) wirklich behalten private .

Antwort : Auf dieser Seite finden Sie viele ausgezeichnete Lösungen zum Ausprobieren. Meiner Erfahrung nach, mit Zugang Modifikator auf internal ist in der Regel mehr als ausreichend, da die Methode (oder Klasse) außerhalb des Projekts, in dem sie definiert ist, nicht sichtbar sein wird. Darüber hinaus gibt es nichts mehr zu verbergen.

5voto

lstanczyk Punkte 1273

Ich verwende dieses Hilfsmittel (Objekttyperweiterung)

 public static  TReturn CallPrivateMethod<TReturn>(
        this object instance,
        string methodName,
        params object[] parameters)
    {
        Type type = instance.GetType();
        BindingFlags bindingAttr = BindingFlags.NonPublic | BindingFlags.Instance;
        MethodInfo method = type.GetMethod(methodName, bindingAttr);

        return (TReturn)method.Invoke(instance, parameters);
    }

Sie können sie folgendermaßen aufrufen

Calculator systemUnderTest = new Calculator();
int result = systemUnderTest.CallPrivateMethod<int>("PrivateAdd",1,8);

Einer der Vorteile ist die Verwendung von Generika, um den Rückgabetyp im Voraus zu bestimmen.

3voto

o0omycomputero0o Punkte 2936

Extrahieren Sie eine private Methode in eine andere Klasse und testen Sie sie in dieser Klasse; lesen Sie mehr über das SRP-Prinzip (Single Responsibility Principle)

Es scheint, dass Sie einen Extrakt für die private Methode zu einer anderen Klasse; in dieser sollte public . Anstatt zu versuchen, den Test auf dem private Methode, sollten Sie testen public Methode dieser anderen Klasse.

Wir haben das folgende Szenario:

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

Wir müssen die Logik von _someLogic aber es scheint, dass Class A mehr Rollen einnehmen als nötig (Verstoß gegen das SRP-Prinzip); einfach in zwei Klassen umstrukturieren

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

Auf diese Weise someLogic könnte ein Test auf A2 sein; in A1 wird einfach ein gefälschtes A2 erstellt und dann in den Konstruktor injiziert, um zu testen, dass A2 die Funktion namens someLogic .

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