Wie verspotte ich eine Klasse ohne Schnittstelle?


81

Ich arbeite an .NET 4.0 mit C # in Windows 7.

Ich möchte die Kommunikation zwischen einigen Methoden mit Mock testen. Das einzige Problem ist, dass ich es ohne Implementierung einer Schnittstelle tun möchte. Ist das möglich?

Ich habe gerade viele Themen und einige Tutorials über Scheinobjekte gelesen, aber alle haben Schnittstellen verspottet und nicht die Klassen. Ich habe versucht, Rhino und Moq Frameworks zu verwenden.


1
Es ist wirklich schade, dass diese Tools unter dem Gesichtspunkt erstellt werden, ausschließlich "IInterfaces" zu verwenden.
AR

4
Es wird davon ausgegangen, dass Sie schnittstellenbasiertes DI verwenden. Dies ist heutzutage ein ziemlich normales Muster.
Maess

Leider widerspricht dieses Muster dem "Muster" des unveränderlichen Typs :(
Matthew Watson

3
Es gibt mehrere Methoden, die hier in dieser Antwort
BlueRaja - Danny Pflughoeft

Antworten:


65

Markieren Sie einfach jede Methode, die Sie fälschen müssen, als virtual(und nicht als privat). Dann können Sie eine Fälschung erstellen, die die Methode überschreiben kann.

Wenn Sie new Mock<Type>einen parameterlosen Konstruktor verwenden und keinen haben, können Sie die Parameter als Argumente des obigen Aufrufs übergeben, da er einen Typ von benötigtparam Objects


1
Ich frage mich daher, ob es überhaupt notwendig ist, eine Schnittstelle für jede Klasse zu erstellen, die ich verspotten möchte. Könnten wir nicht einfach konkrete Klassen zum Verspotten verwenden, wenn sie keine Schnittstelle haben?
Orad

13
@orad Tatsächlich erstelle ich in der Regel zuerst eine Klasse und erst dann eine Schnittstelle, wenn ich allgemeine Funktionen aufteilen muss.
Justin Pihony

Wie übergeben Sie den Mock dann an ein Objekt, das den angegebenen Typ erwartet? Wenn ich ein Modell 'new Mock <MyType>' erstelle und versuche, es an ein Objekt zu übergeben, das MyType erwartet, wird die Fehlermeldung "Mock <MyType> kann nicht in MyType konvertiert werden" angezeigt.
Neutrino

2
Ich habe es herausgefunden. Wenn Sie Ihr Modell als 'var myMock = new Mock <MyType> ()' definieren, müssen Sie es an die Klasse übergeben, die das Modell als myMock.Object verwendet.
Neutrino

4
Das Hauptproblem tritt auf, wenn es keinen parameterlosen Konstruktor gibt und der parametrisierte Konstruktor in der geschlossenen Concrete-Klasse nicht öffentlich ist. (Hier meine ich mit "geschlossen" in einem externen Paket). Dann kann man keine Schnittstelle implementieren, diese Methode nicht virtuell machen und auch den parametrisierten Konstruktor nicht verwenden.
Abhilash Pokhriyal

22

Die meisten Verspottungs-Frameworks (einschließlich Moq und RhinoMocks) generieren Proxy-Klassen als Ersatz für Ihre verspottete Klasse und überschreiben die virtuellen Methoden mit dem von Ihnen definierten Verhalten. Aus diesem Grund können Sie nur Schnittstellen oder virtuelle Methoden für konkrete oder abstrakte Klassen verspotten. Wenn Sie eine konkrete Klasse verspotten, müssen Sie außerdem fast immer einen parameterlosen Konstruktor bereitstellen, damit das Verspottungsframework weiß, wie die Klasse instanziiert wird.

Warum die Abneigung gegen das Erstellen von Schnittstellen in Ihrem Code?


101
Weil es die Codebasis mit Tonnen von Schnittstellen verstopft, was ohne eine technische Einschränkung der Test-Frameworks völlig unnötig wäre?
BlueRaja - Danny Pflughoeft

9
Sie haben den Ausdruck "Reichweite übersteigt Reichweite" gehört. Das erklärt, warum sich Entwickler immer beschweren. Die C # -Sprache wurde nicht für Unit-Tests entwickelt. Aus diesem Grund ist es wirklich schwierig, Scheinabhängigkeiten ohne eine enorme Unordnung sinnloser Schnittstellen oder virtueller Methoden einzufügen. Vielleicht wird bald jemand eine Sprache erfinden, die leicht zu testen ist. Bis dahin haben wir noch etwas zu beanstanden.
John Henckel

13
Ich bin froh zu wissen, dass ich nicht der einzige bin, der Schnittstellen und virtuelle Methoden als unübersichtlich ansieht, wenn ihr einziger Zweck darin besteht, Tests durchzuführen.
aaaaaa

13
"Der einzige Zweck besteht darin, Tests durchzuführen", ist es Ihnen jedoch nicht wichtig, testbaren Code zu erstellen?
MakkyNZ

12
"Warum die Abneigung gegen das Erstellen von Schnittstellen in Ihrem Code?" Weil ich dieses Objekt nicht geschrieben habe und keinen Zugriff auf den Code habe. Ich muss ein Modell eines geschlossenen Objekts erstellen, das ich nicht besitze und das keine Schnittstelle implementiert.
Sean Worle

16

Mit MoQ können Sie konkrete Klassen verspotten:

var mocked = new Mock<MyConcreteClass>();

Auf diese Weise können Sie jedoch virtualCode (Methoden und Eigenschaften) überschreiben .


1
Ich habe das versucht, aber wenn ich mein Testprojekt ausführe, löst mein Programm eine Ausnahme aus: "Proxy der Klasse kann nicht instanziiert werden" "Es wurde kein parameterloser Konstruktor gefunden."
Vinicius Seganfredo

2
Übergeben Sie einfach die Konstruktorparameter an den Mock <> -Konstruktor. ZBnew Mock<MyConcreteClass>(param1, anotherParam, thirdParam, evenMoreParams);
Bozhidar Stoyneff

1
Warum nicht einfach eine Schnittstelle für die Klasse erstellen?
Robert Perry

11

Ich denke, es ist besser, eine Schnittstelle für diese Klasse zu erstellen. Und erstellen Sie einen Komponententest über die Schnittstelle.

Wenn Sie keinen Zugriff auf diese Klasse haben, können Sie einen Adapter für diese Klasse erstellen.

Zum Beispiel:

public class RealClass
{
    int DoSomething(string input)
    {
        // real implementation here
    }
}

public interface IRealClassAdapter
{
    int DoSomething(string input);
}

public class RealClassAdapter : IRealClassAdapter
{
    readonly RealClass _realClass;

    public RealClassAdapter() => _realClass = new RealClass();

    int DoSomething(string input) => _realClass.DoSomething(input);
}

Auf diese Weise können Sie mit IRealClassAdapter ganz einfach ein Modell für Ihre Klasse erstellen.

Hoffe, es funktioniert.


4
Dies ist für die Wartung schrecklich. Wenn Sie der RealClass eine neue Methode hinzufügen möchten, müssen Sie sie auch dem IReadClassAdapter und auch RealClassAdapter hinzufügen. Dreifache Anstrengung! Eine bessere Lösung besteht darin, jeder öffentlichen Methode in der RealClass einfach ein "virtuelles" Schlüsselwort hinzuzufügen.
John Henckel

12
@ JohnHenckel Ich gehe davon aus, dass wir keinen Zugriff auf RealClass haben. Das Hinzufügen einer "virtuellen" Methode ist meiner Meinung nach keine Option. Wenn Sie Zugriff auf eine echte Klasse haben, ist es besser, die Schnittstelle direkt in die neue Klasse zu implementieren. Ich denke, das ist die beste Vorgehensweise.
Anang Satria

Vielen Dank. Dies scheint die einzig legitime Lösung zu sein, um Klassen ohne Zugriff zu verspotten. Auch wenn es viel "dummer" Code ist, brauche ich diesen, um den Code in einer "unangemessenen Umgebung" auszuführen
Michael Spannbauer

7

Die Standard-Mocking-Frameworks erstellen Proxy-Klassen. Dies ist der Grund, warum sie technisch auf Schnittstellen und virtuelle Methoden beschränkt sind.

Wenn Sie auch "normale" Methoden verspotten möchten, benötigen Sie ein Tool, das mit Instrumentierung anstelle der Proxy-Generierung arbeitet. Zum Beispiel können MS Moles und Typemock das. Aber der erstere hat eine schreckliche "API", und der letztere ist kommerziell.


Können wir die normale Klasse mit dieser öffentlichen Methode verspotten?
SivaRajini


2

Wenn es immer schlimmer wird, können Sie ein Schnittstellen- und Adapterpaar erstellen. Sie würden alle Verwendungen von ConcreteClass ändern, um stattdessen die Schnittstelle zu verwenden, und im Produktionscode immer den Adapter anstelle der konkreten Klasse übergeben.

Der Adapter implementiert die Schnittstelle, sodass der Mock auch die Schnittstelle implementieren kann.

Es ist mehr Gerüst als nur eine Methode virtuell zu machen oder nur eine Schnittstelle hinzuzufügen, aber wenn Sie keinen Zugriff auf die Quelle für die konkrete Klasse haben, können Sie aus einer Bindung herauskommen.


1

Ich habe so etwas in einem der alten und älteren Projekte gesehen, in denen ich gearbeitet habe, die keine Schnittstellen oder Best Practices enthalten, und es ist auch zu schwierig, sie zu erzwingen, Dinge neu zu erstellen oder den Code aufgrund der Reife des Projektgeschäfts umzugestalten In meinem UnitTest-Projekt habe ich einen Wrapper über die Klassen erstellt, die ich verspotten möchte, und die Wrapper-Implementierungsschnittstelle, die alle meine benötigten Methoden enthält, die ich einrichten und mit denen ich arbeiten möchte. Jetzt kann ich den Wrapper anstelle der realen Klasse verspotten.

Zum Beispiel:

Zu testender Dienst, der keine virtuellen Methoden oder Implementierungsschnittstellen enthält

public class ServiceA{

public void A(){}

public String B(){}

}

Wrapper zu moq

public class ServiceAWrapper : IServiceAWrapper{

public void A(){}

public String B(){}

}

Die Wrapper-Schnittstelle

public interface IServiceAWrapper{

void A();

String B();

}

Im Unit-Test können Sie nun den Wrapper verspotten:

    public void A_Run_ChangeStateOfX()
    {
    var moq = new Mock<IServiceAWrapper>();
    moq.Setup(...);
    }

Dies ist möglicherweise nicht die beste Vorgehensweise, aber wenn Ihre Projektregeln Sie auf diese Weise zwingen, tun Sie es. Fügen Sie außerdem alle Ihre Wrapper in Ihr Unit-Test-Projekt oder Helper-Projekt ein, das nur für die Unit-Tests angegeben wurde, um das Projekt nicht mit nicht benötigten Wrappern oder Adaptern zu überladen.

Update: Diese Antwort stammt aus mehr als einem Jahr, aber in diesem Jahr habe ich viele ähnliche Szenarien mit unterschiedlichen Lösungen gesehen. Zum Beispiel ist es so einfach, mit Microsoft Fake Framework Mocks, Fakes und Stubs zu erstellen und sogar private und geschützte Methoden ohne Schnittstellen zu testen. Sie können lesen: https://docs.microsoft.com/en-us/visualstudio/test/isolating-code-under-test-with-microsoft-fakes?view=vs-2017

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.