Statische Methoden verspotten

73

Vor kurzem habe ich begonnen, Moq für Unit-Tests zu verwenden. Ich benutze Moq, um Klassen zu verspotten, die ich nicht testen muss.

Wie gehen Sie normalerweise mit statischen Methoden um?

public void foo(string filePath)
{
    File f = StaticClass.GetFile(filePath);
}

Wie könnte diese statische Methode StaticClass.GetFile()verspottet werden?

PS Ich würde mich über Lesematerial freuen, das Sie für Moq und Unit Testing empfehlen.

Kevin Meredith
quelle

Antworten:

38

Das Verspotten von Frameworks wie Moq oder Rhinomocks kann nur Scheininstanzen von Objekten erstellen. Dies bedeutet, dass das Verspotten statischer Methoden nicht möglich ist.

Sie können auch Google nach weiteren Informationen durchsuchen .

Außerdem wurden hier , hier und hier einige Fragen zu StackOverflow gestellt .

Pure.Krome
quelle
48

@ Pure.Krome: gute Antwort, aber ich werde ein paar Details hinzufügen

@ Kevin: Sie müssen eine Lösung auswählen, abhängig von den Änderungen, die Sie am Code vornehmen können.
Wenn Sie es ändern können, macht eine Abhängigkeitsinjektion den Code testbarer. Wenn Sie nicht können, brauchen Sie eine gute Isolation.
Mit dem kostenlosen Mocking-Framework (Moq, RhinoMocks, NMock ...) können Sie nur Delegierte, Schnittstellen und virtuelle Methoden verspotten. Für statische, versiegelte und nicht virtuelle Methoden haben Sie also drei Lösungen:

Ich empfehle Moles , weil es kostenlos und effizient ist und Lambda-Ausdrücke wie Moq verwendet. Nur ein wichtiges Detail: Maulwürfe liefern Stummel, keine Verspottungen. Sie können Moq also weiterhin für Schnittstellen und Delegaten verwenden;)

Mock: Eine Klasse, die eine Schnittstelle implementiert und die Möglichkeit bietet, die Werte für die Rückgabe / Ausnahmen für bestimmte Methoden dynamisch festzulegen und zu überprüfen, ob bestimmte Methoden aufgerufen / nicht aufgerufen wurden.
Stub: Wie eine Scheinklasse, nur dass sie nicht die Möglichkeit bietet, zu überprüfen, ob Methoden aufgerufen / nicht aufgerufen wurden.

Jeco
quelle
8
Als Update dazu. Moles heißt jetzt Fakes und ist in Visual Studio 2012 integriert: msdn.microsoft.com/en-us/library/hh549175.aspx
gscragg
2
In Moles können Sie überprüfen, ob eine Methode aufgerufen wurde, indem Sie lediglich eine boolesche Variable hinzufügen und diese innerhalb der Stub-Funktion auf true setzen. Außerdem können Sie mit dieser Problemumgehung alle Parameter im Aufruf überprüfen.
Mart
3
Ihre Definition von Mocks und Stubs ist komplizierter als nötig. Ein Mock ist ein gefälschtes Objekt, von dem Sie behaupten, dass ein Verhalten aufgetreten ist. Ein Stub ist ein gefälschtes Objekt, das nur verwendet wird, um dem Test "vordefinierte" Daten bereitzustellen. Stubs werden niemals dagegen behauptet. Zusamenfassend. Bei Mocks geht es um Verhalten und bei Stubs um Staat.
user1739635
Es gibt eine Open-Source-Alternative zu Microsoft Fakes (die nur in Premium- / Ultimate-Editionen von Visual Studio verfügbar ist) namens Prig (PRototyping JIG): github.com/urasandesu/Prig . Es ist sowohl als Nuget- als auch als herunterladbare Binärdatei verfügbar. Solange Sie die Quelle nicht kompilieren, kann es in weniger Premium-Editionen von Visual Studio verwendet werden.
Anders Asplund
1
Microsoft.Fakes ist nicht "kostenlos", da es in der VS Community Edition nicht verfügbar ist.
Crono
17

In .NET besteht die Möglichkeit, MOQ und andere Verspottungsbibliotheken auszuschließen. Sie müssen mit der rechten Maustaste auf den Lösungs-Explorer für eine Assembly klicken, die eine statische Methode enthält, die Sie verspotten möchten, und " Gefälschte Assembly hinzufügen" auswählen . Als nächstes können Sie die statischen Assembly-Methoden frei verspotten.

Angenommen, Sie möchten die System.DateTime.Nowstatische Methode verspotten . Tun Sie dies zum Beispiel so:

using (ShimsContext.Create())
{
    System.Fakes.ShimDateTime.NowGet = () => new DateTime(1837, 1, 1);
    Assert.AreEqual(DateTime.Now.Year, 1837);
}

Sie haben ähnliche Eigenschaften für jede statische Eigenschaft und Methode.

pt12lol
quelle
9
Eine Warnung für Personen, die dies finden: MS Fakes ist nur in Visual Studio 2012+ Premium / Ultimate integriert. Sie werden es wahrscheinlich nicht in eine kontinuierliche Integrationskette integrieren können.
Thomasb
3
Dies sollte die akzeptierte Antwort sein. MS Fakes with Shims machen es jetzt sehr gut möglich, statische Methoden zu verspotten.
Jez
1
Seien Sie sehr vorsichtig mit MS Fakes! Fakes ist ein Umleitungsframework, dessen Verwendung schnell zu einer schlechten Architektur führt. Verwenden Sie Fälschungen nur, wenn dies unbedingt erforderlich ist, um Anrufe an Drittanbieter- oder (sehr) ältere Bibliotheken abzufangen. Die beste Lösung besteht darin, einen Klassen-Wrapper mit einer Schnittstelle zu erstellen und die Schnittstelle dann durch Konstruktorinjektion oder über ein Injektionsframework weiterzugeben. Verspotten Sie die Schnittstelle und übergeben Sie diese an den zu testenden Code. Halte dich von Fälschungen fern, wenn es überhaupt möglich ist.
Mike Christian
10

Sie können dies mit der Pose- Bibliothek erreichen, die bei nuget erhältlich ist. Sie können sich unter anderem über statische Methoden lustig machen. Schreiben Sie in Ihre Testmethode Folgendes:

Shim shim = Shim.Replace(() => StaticClass.GetFile(Is.A<string>()))
    .With((string name) => /*Here return your mocked value for test*/);
var sut = new Service();
PoseContext.Isolate(() =>
    result = sut.foo("filename") /*Here the foo will take your mocked implementation of GetFile*/, shim);

Weitere Informationen finden Sie hier https://medium.com/@tonerdo/unit-testing-datetime-now-in-c-without-using-interfaces-978d372478e8

mr100
quelle
Sehr vielversprechend, obwohl ich beim Versuch, es zu verwenden, festgestellt habe, dass es noch keine Erweiterungsmethoden unterstützt. Hoffentlich wird es das bald beinhalten.
Samer Adra
Das beeindruckt mich auch. Aber es funktioniert bereits für Erweiterungsmethoden, habe es gerade getestet! Sie verspotten nur die Erweiterungsmethode, als wäre sie eine gewöhnliche statische Methode: Shim shim = Shim.Replace (() => StaticClass.GetSomeNumber (Is.A <int> ()). With ((int value) => 2); In diesem Fall ist GetSomeNumber eine Erweiterungsmethode und wird daher im Code verwendet: var number = 3.GetSomeNumber (); Und das funktioniert!
Mr100
Dies hat natürlich Einschränkungen, da ich es nicht mit generischen statischen Methoden zum Laufen bringen konnte. Aber Erweiterungsmethoden sind sicher kein Problem.
Mr100
Pose wurde seit einiger Zeit nicht mehr aktualisiert und es gibt eine Reihe von Problemen, die dazu führen, dass es derzeit kein guter Kandidat ist.
David Clarke
3

Ich mochte Pose, konnte aber nicht aufhören, InvalidProgramException auszulösen, was ein bekanntes Problem zu sein scheint . Jetzt benutze ich Kittel wie folgt :

Smock.Run(context =>
{
    context.Setup(() => DateTime.Now).Returns(new DateTime(2000, 1, 1));

    // Outputs "2000"
    Console.WriteLine(DateTime.Now.Year);
});
Sirdank
quelle
1

Ich habe mit dem Konzept herumgespielt, die statischen Methoden zu überarbeiten, um einen Delegaten aufzurufen, den Sie zu Testzwecken extern festlegen können.

Dies würde kein Testframework verwenden und wäre eine vollständig maßgeschneiderte Lösung. Der Refactor hat jedoch keinen Einfluss auf die Signatur Ihres Anrufers und wäre daher relativ sicher.

Damit dies funktioniert, müssen Sie Zugriff auf die statische Methode haben, damit sie für externe Bibliotheken wie z System.DateTime.

Hier ist ein Beispiel, mit dem ich gespielt habe, wo ich einige statische Methoden erstellt habe, eine mit einem Rückgabetyp, der zwei Parameter akzeptiert, und eine generische, die keinen Rückgabetyp hat.

Die statische Hauptklasse:

public static class LegacyStaticClass
{
    // A static constructor sets up all the delegates so production keeps working as usual
    static LegacyStaticClass()
    {
        ResetDelegates();
    }

    public static void ResetDelegates()
    {
        // All the logic that used to be in the body of the static method goes into the delegates instead.
        ThrowMeDelegate = input => throw input;
        SumDelegate = (a, b) => a + b;
    }

    public static Action<Exception> ThrowMeDelegate;
    public static Func<int, int, int> SumDelegate;

    public static void ThrowMe<TException>() where TException : Exception, new()
        => ThrowMeDelegate(new TException());

    public static int Sum(int a, int b)
        => SumDelegate(a, b);
}

Die Unit-Tests (xUnit und Shouldly)

public class Class1Tests : IDisposable
{
    [Fact]
    public void ThrowMe_NoMocking_Throws()
    {
        Should.Throw<Exception>(() => LegacyStaticClass.ThrowMe<Exception>());
    }

    [Fact]
    public void ThrowMe_EmptyMocking_DoesNotThrow()
    {
        LegacyStaticClass.ThrowMeDelegate = input => { };

        LegacyStaticClass.ThrowMe<Exception>();

        true.ShouldBeTrue();
    }

    [Fact]
    public void Sum_NoMocking_AddsValues()
    {
        LegacyStaticClass.Sum(5, 6).ShouldBe(11);
    }

    [Fact]
    public void Sum_MockingReturnValue_ReturnsMockedValue()
    {
        LegacyStaticClass.SumDelegate = (a, b) => 6;
        LegacyStaticClass.Sum(5, 6).ShouldBe(6);
    }

    public void Dispose()
    {
        LegacyStaticClass.ResetDelegates();
    }
}
Joe_DM
quelle