Ich habe gerade einen Auszug aus dem Buch "Growing Object-Oriented Software" gelesen, in dem einige Gründe erläutert werden, warum das Verspotten einer konkreten Klasse nicht empfohlen wird.
Hier ein Beispielcode eines Unit-Tests für die MusicCentre-Klasse:
public class MusicCentreTest {
@Test public void startsCdPlayerAtTimeRequested() {
final MutableTime scheduledTime = new MutableTime();
CdPlayer player = new CdPlayer() {
@Override
public void scheduleToStartAt(Time startTime) {
scheduledTime.set(startTime);
}
}
MusicCentre centre = new MusicCentre(player);
centre.startMediaAt(LATER);
assertEquals(LATER, scheduledTime.get());
}
}
Und seine erste Erklärung:
Das Problem bei diesem Ansatz besteht darin, dass die Beziehung zwischen den Objekten implizit bleibt. Ich hoffe, wir haben inzwischen klargestellt, dass die Absicht der testgetriebenen Entwicklung mit Scheinobjekten darin besteht, Beziehungen zwischen Objekten zu entdecken. Wenn ich eine Unterklasse habe, enthält der Domänencode nichts, was eine solche Beziehung sichtbar macht, sondern nur Methoden für ein Objekt. Dies macht es schwieriger zu erkennen, ob der Dienst, der diese Beziehung unterstützt, an anderer Stelle relevant sein könnte, und ich muss die Analyse beim nächsten Arbeiten mit der Klasse erneut durchführen.
Ich kann nicht genau herausfinden, was er meint, wenn er sagt:
Dies macht es schwieriger zu erkennen, ob der Dienst, der diese Beziehung unterstützt, an anderer Stelle relevant sein könnte, und ich muss die Analyse beim nächsten Arbeiten mit der Klasse erneut durchführen.
Ich verstehe, dass der Dienst der MusicCentre
aufgerufenen Methode entspricht startMediaAt
.
Was meint er mit "anderswo"?
Der vollständige Auszug ist hier: http://www.mockobjects.com/2007/04/test-smell-mocking-concrete-classes.html
Antworten:
Der Autor dieses Beitrags fördert die Verwendung von Schnittstellen gegenüber der Verwendung von Mitgliedsklassen.
It turns out that my MusicCentre object only uses the starting and stopping methods on the CdPlayer, the rest are used by some other part of the system. I'm over-specifying my MediaCentre by requiring it to talk to a CdPlayer, what it actually needs is a ScheduledDevice.
Die Beziehung, die er später wieder entdecken möchte, ist die Tatsache, dass die MediaCentre-Klasse nicht das gesamte CdPlayer-Objekt benötigt. Seine Behauptung ist, dass es durch die Verwendung einer Schnittstelle (vermutlich auf Start | Stopp beschränkt) einfacher ist zu verstehen, was die Interaktion wirklich ist.
"anderswo" bedeutet einfach, dass andere Objekte möglicherweise ähnlich eingeschränkte Beziehungen haben und das Vollmitgliedsobjekt nicht erforderlich ist - eine Teilmenge der über eine Schnittstelle eingeschlossenen Funktionalität sollte ausreichen.
Die Behauptung macht mehr Sinn, wenn Sie alle potenziellen Funktionen ausschöpfen:
Jetzt macht seine Behauptung "Ich brauche nur Start & Stopp" mehr Sinn. Die Verwendung des konkreten Elementobjekts anstelle einer Schnittstelle macht zukünftigen Entwicklern weniger klar, was wirklich erforderlich ist. Das Ausführen von Komponententests von MediaCentre für alle anderen Funktionen in CdPlayer ist eine Verschwendung von Testaufwand, da sie zum Status "egal" gehören. Wenn die
Record
Funktion in diesem Fall nicht funktioniert hat, ist uns das wirklich egal, da sie nicht erforderlich ist. Aber ein zukünftiger Betreuer würde das nicht unbedingt anhand des geschriebenen Codes wissen.Letztendlich besteht die Prämisse des Autors darin, nur das zu verwenden, was benötigt wird, und zukünftigen Betreuern klar zu machen, was zuvor benötigt wurde. Ziel ist es, die Nacharbeit / erneute Analyse des Codemoduls während der nachfolgenden Wartung zu minimieren.
quelle
Nachdem ich viel darüber nachgedacht habe, erhalte ich eine mögliche Interpretation dieses Zitats:
Der angegebene "Service" entspricht der "Tatsache der Planung". Dies könnte durch eine gut benannte und auf eine Rolle fokussierte Schnittstelle mit dem Namen "ScheduledDevice" oder implizit durch eine konkrete Methodenimplementierung ausgedrückt werden, die nicht von Schnittstellen abhängt.
Im obigen Beispiel wird die Zeitplanung durch das gesamte Objekt mit vollem Funktionsumfang ausgedrückt
CDPlayer
. Somit führt es immer noch zu einer impliziten Beziehung zwischenMusicCentre
und "Tatsache der Planung".Wenn wir also anfangen, konkrete Klassen zu injizieren und sie in Objekte auf hoher Ebene zu verspotten; Wenn wir diese testen möchten, müssen wir jedes injizierte "konkrete" Objekt analysieren, um festzustellen, ob es eine bestimmte Beziehung darstellt, die wir verspotten müssen, weil sie versteckt sind (implizit). Im Gegenteil, durch die IMMER-Codierung über die Schnittstelle kann der Entwickler direkt herausfinden, welche Art von Beziehung vom übergeordneten Objekt bedient werden soll, und daher Merkmale erkennen, die verspottet werden müssen, um den Komponententest zu isolieren.
quelle
Der Dienst, den ich hier meinte, war CDPlayer.scheduleToStartAt (). Das nennt das MediaCentre - den Mitarbeiter, den es braucht, um zu funktionieren. Das MediaCentre ist das zu testende Objekt.
Die Idee ist, dass ich dieser Abhängigkeitsrolle einen Namen geben und darüber sprechen kann, wenn ich explizit mache, wovon das MediaCentre abhängt, und nicht von einer Implementierungsklasse. Das MediaCentre muss lediglich wissen, dass es mit ScheduledDevices kommuniziert. Wenn sich der Rest des Systems ändert, muss ich das MediaCentre nur ändern, wenn sich seine Funktionen ändern.
Hilft das?
quelle