Verspottende Betonklasse - Nicht empfohlen

11

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 MusicCentreaufgerufenen 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

Mik378
quelle
Fügte einen Kommentar zu seinem Blog hinzu, da ich aus diesen Zitaten nicht herausfinden konnte, was er meinte.
Oligofren
@oligofren Es ist wirklich ein großes Rätsel :) ...
Mik378

Antworten:

6

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:

  • Start
  • halt
  • Pause
  • Aufzeichnung
  • zufällige Spielreihenfolge
  • Sample-Tracks, Beginn des Songs
  • Sample-Tracks, zufälliges Sample des Songs
  • Medieninformationen bereitstellen
  • ...

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 RecordFunktion 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
Danke für diese tolle Antwort. Sie sagten jedoch: "Das Ausführen von Komponententests für alle anderen Funktionen ist eine Verschwendung von Testaufwand, da sie zum Status" egal "gehören." Ist es nicht eher so: "Das Erstellen von Mocks für jede der anderen Funktionen ist eine Verschwendung von Testaufwand, da sie zum Status" egal "gehören."?
Mik378
@ Mik378 - ja, genau darum ging es mir, ich habe es nur anders formuliert. Und ich habe meine Antwort aktualisiert, um das klarer zu machen.
Aber ich finde den Begriff "Unit-Tests ausführen" verwirrend. Das würde bedeuten, dass MusicCentre seinen Mitarbeiter einem Unit-Test unterziehen wird ... während es seinen Mitarbeiter tatsächlich MOCKS, um seine EIGENEN Dienste einem Unit-Test zu unterziehen. Übrigens verstehe ich jetzt die Bedeutung :)
Mik378
@ Mik378 - wir sagen dasselbe und ich verwende wahrscheinlich weniger genaue Präzision, um dies zu tun. Entschuldigung für die Verwirrung.
4

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.

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 zwischen MusicCentreund "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.

Mik378
quelle
Ich denke du hast es jetzt. Leider habe ich keine Benachrichtigung über Ihren Kommentar erhalten.
Steve Freeman
3

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?

Steve Freeman
quelle
(Autor dieses großartigen Artikels :)) Was ich interpretieren wollte, war dieser Satz: "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 bei meiner nächsten Arbeit erneut durchführen mit der Klasse ". Welche Art von Analyse? Die Tatsache, zu erkennen, welche Objektmethode die Beziehung implementieren soll, da diese eindeutig verborgen ist?
Mik378