Sollte ich meine Unterklassen oder meine abstrakte Elternklasse einem Unit-Test unterziehen?

12

Ich habe eine Skelettimplementierung, wie in Punkt 18 von Effective Java (ausführliche Diskussion hier ). Es ist eine abstrakte Klasse, die zwei öffentliche Methoden methodA () und methodB () bereitstellt, die Unterklassenmethoden aufrufen, um "die Lücken zu füllen", die ich nicht abstrahiert definieren kann.

Ich habe es zuerst entwickelt, indem ich eine konkrete Klasse erstellt und Unit-Tests dafür geschrieben habe. Als die zweite Klasse kam, konnte ich allgemeines Verhalten extrahieren, die "fehlenden Lücken" in der zweiten Klasse implementieren und es war fertig (natürlich wurden die Komponententests für die zweite Unterklasse erstellt).

Die Zeit verging und jetzt habe ich 4 Unterklassen, von denen jede 3 kurze geschützte Methoden implementiert, die sehr spezifisch für ihre konkrete Implementierung sind, während die Skelettimplementierung die gesamte generische Arbeit erledigt.

Mein Problem ist, dass ich beim Erstellen einer neuen Implementierung die Tests erneut schreibe:

  • Ruft die Unterklasse eine bestimmte erforderliche Methode auf?
  • Hat eine bestimmte Methode eine bestimmte Anmerkung?
  • Erhalte ich die erwarteten Ergebnisse von Methode A?
  • Erhalte ich die erwarteten Ergebnisse von Methode B?

Während sehen Vorteile mit diesem Ansatz:

  • Es dokumentiert die Anforderungen der Unterklasse durch Tests
  • Bei einem problematischen Refactoring kann dies schnell fehlschlagen
  • Testen Sie die Unterklasse als Ganzes und über ihre öffentliche API ("funktioniert methodA ()?" Und nicht "funktioniert diese geschützte Methode?").

Das Problem, das ich habe, ist, dass die Tests für eine neue Unterklasse im Grunde genommen ein Kinderspiel sind: - Die Tests sind in allen Unterklassen alle gleich, sie ändern nur den Rückgabetyp der Methoden (die Skeleton-Implementierung ist generisch) und die Eigenschaften I. Überprüfen Sie den Assert-Teil des Tests. - Normalerweise haben die Unterklassen keine spezifischen Tests für sie

Ich mag die Art und Weise, wie sich die Tests auf das Ergebnis der Unterklasse selbst konzentrieren und es vor Refactoring schützen, aber die Tatsache, dass die Implementierung des Tests nur "manuelle Arbeit" wurde, lässt mich denken, dass ich etwas falsch mache.

Ist dieses Problem beim Testen der Klassenhierarchie häufig? Wie kann ich das vermeiden?

ps: Ich habe darüber nachgedacht, die Skelettklasse zu testen, aber es scheint auch seltsam, ein Modell einer abstrakten Klasse zu erstellen, um sie testen zu können. Und ich denke, dass ein Refactoring der abstrakten Klasse, das das Verhalten ändert, das von der Unterklasse nicht erwartet wird, nicht so schnell bemerkt wird. Mein Mut sagt mir, dass das Testen der Unterklasse "als Ganzes" der bevorzugte Ansatz ist, aber bitte zögern Sie nicht, mir zu sagen, dass ich falsch liege :)

ps2: Ich habe gegoogelt und viele Fragen zu diesem Thema gefunden. Einer von ihnen ist dieser, der eine großartige Antwort von Nigel Thorne hat. Mein Fall wäre seine "Nummer 1". Das wäre großartig, aber ich kann es derzeit nicht umgestalten, also müsste ich mit diesem Problem leben. Wenn ich umgestalten könnte, hätte ich jede Unterklasse als Strategie. Das wäre in Ordnung, aber ich würde immer noch die "Hauptklasse" und die Strategien testen, aber ich würde kein Refactoring bemerken, das die Integration zwischen Hauptklasse und Strategien unterbricht.

ps3: Ich habe einige Antworten gefunden, die besagen, dass es "akzeptabel" ist, die abstrakte Klasse zu testen. Ich bin damit einverstanden, dass dies akzeptabel ist, aber ich würde gerne wissen, welcher Ansatz in diesem Fall der bevorzugte ist (ich beginne immer noch mit Unit-Tests).

JSBach
quelle
2
Schreiben Sie eine abstrakte Testklasse und lassen Sie Ihre konkreten Tests davon erben, wobei Sie die Struktur des Geschäftscodes widerspiegeln. Tests sollen das Verhalten und nicht die Implementierung einer Einheit testen, aber das bedeutet nicht, dass Sie Ihr Insiderwissen über das System nicht nutzen können, um dieses Ziel effizienter zu erreichen.
Kilian Foth
Ich habe irgendwo gelesen, dass man Vererbung nicht für Tests verwenden sollte. Ich suche nach der Quelle, um sie sorgfältig zu lesen
JSBach
4
@KilianFoth Bist du dir über diesen Rat sicher? Dies klingt nach einem Rezept für spröde Komponententests, das sehr empfindlich auf Konstruktionsänderungen reagiert und daher nicht sehr wartbar ist.
Konrad Morawski

Antworten:

5

Ich kann nicht sehen, wie Sie Tests für eine abstrakte Basisklasse schreiben würden . Sie können es nicht instanziieren, sodass Sie keine Instanz davon zum Testen haben können. Sie könnten eine konkrete "Dummy" -Unterklasse nur zum Testen erstellen - nicht sicher, wie viel Nutzen das wäre. YMMV.

Jedes Mal, wenn Sie eine neue Implementierung (Klasse) erstellen, sollten Sie Tests schreiben, die diese neue Implementierung ausführen. Da Teile der Funktionalität (Ihre "Lücken") in jeder Unterklasse implementiert sind , ist dies unbedingt erforderlich. Die Ausgabe jeder der geerbten Methoden kann (und sollte ) für jede neue Implementierung unterschiedlich sein.

Phill W.
quelle
Ja, sie sind unterschiedlich (die Typen und Inhalte sind unterschiedlich). Technisch gesehen wäre ich in der Lage, diese Klasse zu verspotten oder eine einfache Implementierung nur zu Testzwecken mit leeren Implementierungen zu haben. Ich mag diesen Ansatz nicht. Aber die Tatsache, dass ich nicht "denken muss", dass ich Tests in neuen Implementierungen bestehen muss, macht mich komisch und lässt mich auch fragen, wie sicher ich in diesem Test bin (natürlich, wenn ich die Basisklasse absichtlich ändere, die Tests
schlagen
4

Normalerweise erstellen Sie eine Basisklasse, um eine Schnittstelle zu erzwingen. Sie möchten diese Schnittstelle testen, nicht die folgenden Details. Daher sollten Sie Tests erstellen, die jede Klasse einzeln instanziieren, aber nur mit den Methoden der Basisklasse testen.

Der Vorteil dieser Methode besteht darin, dass Sie die Schnittstelle jetzt testen können, da Sie sie getestet haben. Möglicherweise befindet sich der Code in einer untergeordneten Klasse im übergeordneten Code oder umgekehrt. Jetzt werden Ihre Tests beweisen, dass Sie das Verhalten beim Verschieben dieses Codes nicht unterbrochen haben.

Im Allgemeinen sollten Sie den Code testen, wie er verwendet werden soll. Das bedeutet, dass Sie das Testen privater Methoden vermeiden müssen und dass Ihre zu testende Einheit möglicherweise etwas größer ist als ursprünglich angenommen - möglicherweise eher eine Gruppe von Klassen als eine einzelne Klasse. Ihr Ziel sollte es sein, Änderungen zu isolieren, damit Sie selten einen Test ändern müssen, wenn Sie in einem bestimmten Bereich umgestalten.

Michael K.
quelle