TDD: Verspottete Objekte verspotten

10

Manchmal müssen Objekte nur eng miteinander verbunden werden. Beispielsweise muss eine CsvFileKlasse wahrscheinlich eng mit der CsvRecordKlasse (oder ICsvRecordSchnittstelle) zusammenarbeiten.

Nach dem, was ich in der Vergangenheit gelernt habe, lautet einer der wichtigsten Grundsätze der testgetriebenen Entwicklung: "Testen Sie niemals mehr als eine Klasse gleichzeitig." Das heißt, Sie sollten ICsvRecordMocks oder Stubs anstelle von tatsächlichen Instanzen von verwenden CsvRecord.

Nachdem ich diesen Ansatz ausprobiert hatte, bemerkte ich jedoch, dass das Verspotten der CsvRecordKlasse etwas haarig werden kann. Was mich zu einer von zwei Schlussfolgerungen führt:

  1. Es ist schwer, Unit-Tests zu schreiben! Das ist ein Code-Geruch! Refactor!
  2. Es ist einfach unvernünftig, jede einzelne Abhängigkeit auszuspotten.

Als ich meine Verspottungen durch tatsächliche CsvRecordInstanzen ersetzte , lief es viel reibungsloser. Bei der Suche um für andere Völker Gedanken stolperte ich über diese Blog - Post , die oben # 2 zu unterstützen scheint. Bei Objekten, die von Natur aus eng miteinander verbunden sind, sollten wir uns nicht so sehr um das Verspotten kümmern.

Bin ich weit weg von der Strecke? Gibt es irgendwelche Nachteile bei der obigen Annahme Nr. 2? Sollte ich tatsächlich darüber nachdenken, mein Design umzugestalten?

Phil
quelle
1
Ich denke, es ist ein weit verbreitetes Missverständnis, dass die "Einheit" in "Einheitentests" notwendigerweise eine Klasse sein muss. Ich denke, Ihr Beispiel zeigt einen Fall, in dem es besser sein kann, dass diese beiden Klassen eine Einheit bilden. Aber versteh mich nicht falsch, ich stimme der Antwort von Robert Harvey voll und ganz zu.
Doc Brown

Antworten:

11

Wenn Sie wirklich eine Koordination zwischen diesen beiden Klassen benötigen, schreiben Sie eine CsvCoordinatorKlasse, die Ihre beiden Klassen kapselt, und testen Sie diese.

Ich bestreite jedoch den Begriff, der CsvRecordnicht unabhängig überprüfbar ist. CsvRecordist im Grunde eine DTO- Klasse, nicht wahr ? Es ist nur eine Sammlung von Feldern mit vielleicht ein paar Hilfsmethoden. Und CsvRecordkann auch in anderen Kontexten verwendet werden CsvFile; Sie können beispielsweise eine Sammlung oder ein Array von CsvRecords haben.

CsvRecordZuerst testen . Stellen Sie sicher, dass alle Tests bestanden wurden. Dann mach weiter und benutze es CsvRecordmit deiner CsvFileKlasse während des Tests. Verwenden Sie es als vorab getesteten Stub / Mock. Füllen Sie es mit relevanten Testdaten, geben Sie es weiter CsvFileund schreiben Sie Ihre Testfälle dagegen.

Robert Harvey
quelle
1
Ja, CsvRecord ist definitiv unabhängig testbar. Das Problem ist, dass CsvData-Tests fehlschlagen, wenn in CsvRecord etwas kaputt geht. Aber ich denke nicht, dass das ein großes Problem ist.
Phil
1
Ich denke, du willst, dass das passiert. :)
Robert Harvey
1
@RobertHarvey: Theoretisch könnte es zu einem Problem werden, wenn CsvRecord und CsvFile zu recht komplexen Klassen werden und wenn ein Test für CsvFile unterbrochen wird, wissen Sie jetzt nicht sofort, ob es sich um ein Problem in CsvFile oder CsvRecord handelt. Aber ich denke, das ist eher ein hypothetischer Fall - wenn ich die Aufgabe hätte, solche Klassen für ein reales Programm zu programmieren, würde ich es genau so machen, wie Sie es beschreiben.
Doc Brown
2
@Phil: Wenn CsvRecordPausen, dann CsvDatascheitert offensichtlich ; Dies ist jedoch in Ordnung, da Sie CsvRecordzuerst testen und wenn dies fehlschlägt, sind Ihre CsvFileTests bedeutungslos. Sie können immer noch zwischen Fehlern in CsvRecordund in unterscheiden CsvFile.
tdammers
5

Der Grund für das Testen einer Klasse zu einem Zeitpunkt ist, dass Sie nicht möchten, dass Tests für eine Klasse Abhängigkeiten vom Verhalten einer zweiten Klasse haben. Das heißt, wenn Ihr Test für Klasse A eine der Funktionen von Klasse B ausübt, sollten Sie Klasse B verspotten, um die Abhängigkeit von bestimmten Funktionen in Klasse B zu beseitigen.

Eine Klasse wie CsvRecordscheint mir hauptsächlich zum Speichern von Daten zu dienen - es ist keine Klasse mit zu viel eigener Funktionalität. Das heißt, es kann Konstruktoren, Getter, Setter haben, aber keine Methoden mit wirklich substanzieller Logik. Natürlich vermute ich hier - vielleicht haben Sie eine Klasse namens geschrieben CsvRecord, die zahlreiche komplexe Berechnungen durchführt.

Aber wenn CsvRecordes keine eigene Logik gibt, kann man nichts gewinnen, wenn man sich darüber lustig macht. Dies ist wirklich nur die alte Maxime - "verspotten Sie keine Wertobjekte" .

Wenn Sie also überlegen, ob Sie eine bestimmte Klasse verspotten möchten (für einen Test einer anderen Klasse), sollten Sie berücksichtigen, wie viel von ihrer eigenen Logik diese Klasse hat und wie viel von dieser Logik im Verlauf Ihres Tests ausgeführt wird.

Dawood ibn Kareem
quelle
+1. Jeder Test, dessen Ergebnis von der Richtigkeit des Verhaltens mehrerer Objekte abhängt, ist ein Integrationstest und kein Komponententest. Sie müssen eines dieser Objekte verspotten, um einen echten Komponententest zu erhalten. Dies gilt jedoch nicht für Objekte ohne wirkliches Verhalten - zum Beispiel nur für Getter und Setter.
Guillaume31
1

Nr. 2 ist in Ordnung. Dinge können und sollten eng miteinander verbunden sein, wenn ihre Konzepte eng miteinander verbunden sind. Dies sollte selten sein und im Allgemeinen vermieden werden. In dem von Ihnen angegebenen Beispiel ist dies jedoch sinnvoll.

Telastyn
quelle
0

"Gekoppelte" Klassen sind voneinander abhängig. Dies sollte in dem, was Sie beschreiben, nicht der Fall sein - ein CsvRecord sollte sich nicht wirklich um die CsvFile kümmern, die es enthält, daher geht die Abhängigkeit nur in eine Richtung. Das ist in Ordnung und keine enge Kopplung.

Wenn eine Klasse den variablen String-Namen enthält, würden Sie doch nicht behaupten, dass sie eng mit String verbunden ist, oder?

Testen Sie den CsvRecord also auf das gewünschte Verhalten.

Verwenden Sie dann ein Verspottungs-Framework (Mockito ist großartig), um zu testen, ob Ihre Einheit mit den Objekten interagiert, von denen es richtig abhängt. Das Verhalten, das Sie wirklich testen möchten, ist, dass CsvFile CsvRcords wie erwartet behandelt. Das Innenleben von CvsRecord sollte keine Rolle spielen - so kommuniziert CvsFile damit.

Schließlich geht es bei TDD nicht nur um Unit-Tests. Sie können (und sollten) sicherlich mit Funktionstests beginnen, die das Funktionsverhalten Ihrer größeren Komponenten untersuchen, dh Ihre User Story oder Ihr Szenario. Ihre Unit-Tests setzen die Erwartungen und verifizieren die Teile, die Funktionstests machen das Gleiche für das Ganze.

Matthew Flynn
quelle
1
-1, enge Kopplung bedeutet nicht unbedingt zyklische Abhängigkeiten, das ist ein Missverständnis. Im Beispiel CsvFile ist eng gekoppelt CsvRecord(aber nicht umgekehrt). Das OP fragt, ob es eine gute Idee ist, sie zu testen, CsvFileindem Sie sie CsvRecordüber eine entkoppeln ICsvRecord, nicht umgekehrt.
Doc Brown
2
@DocBrown: Ob die Kopplung eng ist oder nicht, hängt davon ab, wie stark CsvFiledas Innenleben CsvRecord, dh die Anzahl der Annahmen, die die Datei über den Datensatz hat, abhängt . Schnittstellen helfen dabei, solche Annahmen zu dokumentieren und durchzusetzen (oder vielmehr das Fehlen anderer Annahmen), aber der Umfang der Kopplung bleibt gleich, außer dass Sie mit einer Schnittstelle eine andere Datensatzklasse einbinden können CsvFile. Die Einführung der Schnittstelle, nur damit Sie sagen können, dass Sie die Kopplung reduziert haben, ist dumm.
tdammers
0

Hier gibt es wirklich zwei Fragen. Das erste ist, wenn Situationen existieren, in denen das Verspotten eines Objekts nicht ratsam ist. Das ist zweifellos wahr, wie die anderen hervorragenden Antworten zeigen. Die zweite Frage ist, ob Ihr spezieller Fall eine dieser Situationen ist. In dieser Frage bin ich nicht überzeugt.

Der wahrscheinlich häufigste Grund, eine Klasse nicht zu verspotten, ist, wenn es sich um eine Wertklasse handelt. Sie müssen sich jedoch den Grund für die Regel ansehen. Das liegt nicht daran, dass die verspottete Klasse irgendwie schlecht sein wird, sondern daran, dass sie im Wesentlichen mit dem Original identisch ist. Wenn dies der Fall wäre, wäre Ihr Komponententest mit der ursprünglichen Klasse nicht einfacher.

Es kann sehr gut sein, dass Ihr Code eine der seltenen Ausnahmen ist, bei denen Refactoring nicht helfen würde, aber Sie sollten ihn nur dann deklarieren, wenn sorgfältige Refactoring-Bemühungen nicht funktioniert haben. Selbst erfahrene Entwickler können Probleme haben, Alternativen zu ihrem eigenen Design zu finden. Wenn Ihnen keine Möglichkeit zur Verbesserung einfällt, bitten Sie einen erfahrenen Mitarbeiter, einen zweiten Blick darauf zu werfen.

Die meisten Leute scheinen anzunehmen, dass Sie CsvRecordeine Werteklasse sind. Versuchen Sie es zu einem. Mach es unveränderlich, wenn du kannst. Wenn Sie zwei Objekte mit Zeigern aufeinander haben, entfernen Sie eines davon und finden Sie heraus, wie es funktioniert. Suchen Sie nach Orten, an denen Klassen und Funktionen aufgeteilt werden können. Der beste Ort zum Teilen einer Klasse stimmt möglicherweise nicht immer mit dem physischen Layout der Datei überein. Versuchen Sie, die Eltern-Kind-Beziehung der Klassen umzukehren. Möglicherweise benötigen Sie eine separate Klasse zum Lesen und Schreiben von CSV-Dateien. Möglicherweise benötigen Sie separate Klassen für die Verarbeitung der Datei-E / A und der Schnittstelle zu den oberen Ebenen. Es gibt viele Dinge zu versuchen, bevor es für unrefaktable erklärt wird.

Karl Bielefeldt
quelle