it-swarm.com.de

Ist es empfehlenswert, konkrete Klasse zu verspotten?

Die meisten Beispiele auf der Website zum Verspotten von Frameworks beziehen sich auf das Verspotten von Interface. Angenommen, NSubstitute, das ich derzeit verwende, alle ihre spöttischen Beispiele sind, die Benutzeroberfläche zu verspotten.

Aber in Wirklichkeit sah ich stattdessen einige Entwickler, die sich über eine konkrete Klasse lustig machten. Ist es empfehlenswert, konkrete Klasse zu verspotten?

34
DonDon

Theoretisch ist es absolut kein Problem, eine konkrete Klasse zu verspotten; Wir testen gegen eine logische Schnittstelle (und nicht gegen ein Schlüsselwort interface), und es spielt keine Rolle, ob diese logische Schnittstelle durch eine class oder interface bereitgestellt wird. 

In der Praxis macht .NET/C # dies etwas problematisch. Wie Sie ein .NET-Mocking-Framework erwähnt haben, gehe ich davon aus, dass Sie darauf beschränkt sind. 

In .NET/C # -Member sind die Mitglieder standardmäßig nicht virtuell. Daher funktionieren alle Proxy-basierten Methoden des Mocking-Verhaltens (dh sie werden von der Klasse abgeleitet und überschreiben alle Mitglieder, um testspezifische Aufgaben auszuführen) nur dann, wenn Sie die Mitglieder explizit markieren als virtual. Dies führt zu einem Problem: Sie verwenden eine Instanz einer gespielten Klasse, die in Ihrem Gerätetest absolut sicher sein soll (dh, es wird kein echter Code ausgeführt). Wenn Sie jedoch nicht sichergestellt haben, dass alles virtual ist, können Sie am Ende enden mit einer Mischung aus echtem und nachgeahmtem Code (dies ist besonders problematisch, wenn Konstruktorlogik vorhanden ist, die immer ausgeführt wird, und wenn andere konkrete Abhängigkeiten vorhanden sind, die neu sind).

Es gibt einige Möglichkeiten, dies zu umgehen. 

  • Verwenden Sie interfaces. Dies funktioniert und ist das, was wir in der NSubstitute-Dokumentation empfehlen , hat aber den Nachteil, dass Ihre Codebase möglicherweise mit aufgeblähten Schnittstellen aufgebläht wird, die nicht unbedingt benötigt werden. Wenn wir gute Abstraktionen in unserem Code finden, werden wir natürlich mit sauberen, wiederverwendbaren Schnittstellen enden, die wir testen können. Ich habe es nicht ganz so gesehen, aber YMMV. :)
  • Mache fleißig alles virtuell. Ein Nachteil ist, dass wir vorschlagen, dass alle diese Mitglieder als Erweiterungspunkte in unserem Design gedacht sind, wenn wir wirklich nur das Verhalten der gesamten Klasse zum Testen ändern möchten. Es hält auch nicht die Ausführung der Konstruktorlogik an und hilft auch nicht, wenn die konkrete Klasse andere Abhängigkeiten erfordert.
  • Verwenden Sie das Umschreiben von Assemblys über etwas wie das Add-In Virtuosity for Fody , mit dem Sie alle Klassenmitglieder in Ihrer Assembly als virtuell bearbeiten können.
  • Verwenden Sie eine nicht-Proxy-basierte Mocking-Bibliothek wie TypeMock (bezahlt) , JustMock (bezahlt) , Microsoft Fakes (erforderlich VS Ultimate/Enterprise, obwohl sein Vorgänger Microsoft Moles - kostenlos ist) oder Prig (kostenlos + Open Source) . Ich glaube, dass diese in der Lage sind, alle Aspekte von Klassen sowie statische Mitglieder zu verspotten.

Eine allgemeine Beschwerde gegen die letzte Idee ist, dass Sie über eine "falsche" Naht testen. Wir gehen aus den Mechanismen heraus, die normalerweise für die Erweiterung von Code verwendet werden, um das Verhalten unseres Codes zu ändern. Wenn Sie sich außerhalb dieser Mechanismen befinden müssen, könnte dies auf Steifheit in unserem Design hindeuten. Ich verstehe dieses Argument, aber ich habe Fälle gesehen, in denen das Rauschen bei der Erstellung einer anderen Schnittstelle die Vorteile überwiegt. Ich denke, es geht darum, sich des möglichen Designproblems bewusst zu sein. Wenn Sie diese Rückmeldungen nicht benötigen, um die Steifigkeit des Designs hervorzuheben, bieten sie hervorragende Lösungen.

Eine letzte Idee, die ich da draußen verwende, ist, mit den Größen der Einheiten in unseren Tests herumzuspielen. Normalerweise haben wir eine einzelne Klasse als Einheit. Wenn wir eine Reihe zusammenhängender Klassen als Einheit haben und Schnittstellen als eine gut definierte Grenze um diese Komponente fungieren, können wir vermeiden, so viele Klassen wie möglich zu spekulieren, sondern stattdessen nur über eine stabilere Grenze. Dies kann unsere Tests komplizierter machen, mit dem Vorteil, dass wir eine zusammenhängende Einheit der Funktionalität testen und die Entwicklung solider Schnittstellen um diese Einheit angeregt werden.

Hoffe das hilft.

67
David Tchepak

Update :

3 Jahre später möchte ich zugeben, dass ich meine Meinung geändert habe.

Theoretisch mag ich es immer noch nicht, Schnittstellen zu erstellen, um die Erstellung von Scheinobjekten zu erleichtern. In der Praxis (ich verwende NSubstitute) ist es viel einfacher, Substitute.For<MyInterface>() zu verwenden, als eine echte Klasse mit mehreren Parametern zu verspotten, z. Substitute.For<MyCLass>(mockedParam1, mockedParam2, mockedParam3), wobei jeder Parameter separat verspottet werden soll. Weitere mögliche Probleme sind in NSubstitute-Dokumentation beschrieben

In unserem Unternehmen wird jetzt empfohlen, Schnittstellen zu verwenden.

Ursprüngliche Antwort :

Wenn Sie nicht mehrere Implementierungen derselben Abstraktion erstellen müssen, erstellen Sie keine Schnittstelle. Wie es von David Tchepak gezeigt , möchten Sie nicht Ihre Codebasis mit Schnittstellen aufblähen, die möglicherweise nicht wirklich benötigt werden.

From http://blog.ploeh.dk/2010/12/02/InterfacesAreNotAbstractions.aspx

Extrahieren Sie Schnittstellen aus Ihren Klassen, um eine lose Kopplung zu ermöglichen? Wenn ja, haben Sie wahrscheinlich eine 1: 1-Beziehung zwischen Ihren Schnittstellen und den konkreten Klassen , die diese implementieren. Das ist wahrscheinlich kein gutes Zeichen und verstößt gegen RAP = Reused Abstractions Principle .

Nur eine Implementierung einer bestimmten Schnittstelle ist ein Codegeruch.

Wenn Ihr Ziel die Testbarkeit ist, bevorzuge ich die zweite Option von David Tchepaks Antwort oben .

Ich bin jedoch nicht davon überzeugt, dass man alles virtuell machen muss. Es reicht aus, nur die Methoden, die Sie ersetzen wollen, virtuell zu machen. Ich werde auch einen Kommentar neben der Methodendeklaration hinzufügen, dass die Methode nur virtuell ist, um sie für das Unit-Test-Mocking ersetzbar zu machen.

Beachten Sie jedoch, dass das Ersetzen von konkreten Klassen anstelle von Schnittstellen einige Einschränkungen aufweist. Z.B. für NSubstitute

Hinweis: Für Klassen werden keine rekursiven Ersetzungen erstellt, da das Erstellen und Verwenden von Klassen möglicherweise unerwünschte Nebenwirkungen haben kann

.

10

Die Frage ist eher: Warum nicht?

Ich kann mir ein paar Szenarien vorstellen, in denen dies nützlich ist, wie zum Beispiel:

Die Implementierung einer konkreten Klasse ist noch nicht abgeschlossen oder der Typ, der sie durchgeführt hat, ist unzuverlässig. Also spott ich die Klasse so, wie sie spezifiziert ist, und teste meinen Code dagegen.

Es kann auch nützlich sein, Klassen zu simulieren, die Datenbankzugriffe durchführen. Wenn Sie keine Testdatenbank haben, möchten Sie möglicherweise Werte für Ihre Tests zurückgeben, die immer konstant sind (was durch das Spotteln der Klasse leicht ist).

1
konqi

Es ist nicht so, dass es empfohlen wird. Sie können dies tun, wenn Sie keine andere Wahl haben. 

Normalerweise ist ein gut entworfenes Projekt auf der Definition von Schnittstellen für Ihre separaten Komponenten erforderlich, sodass Sie die einzelnen Komponenten isoliert testen können, indem Sie die anderen Komponenten nachahmen. Wenn Sie jedoch mit Legacy-Code/Code arbeiten, den Sie nicht ändern dürfen und trotzdem Ihre Klassen testen möchten, haben Sie keine Wahl und Sie können nicht dafür kritisiert werden (vorausgesetzt, Sie haben sich die Mühe gemacht, zu wechseln diese Komponenten zu Schnittstellen und wurden das Recht zu) verweigert.

0
jolivier