521 Stimmen

Wie kann man abstrakte Klassen testen: mit Stubs erweitern?

Ich habe mich gefragt, wie man abstrakte Klassen und Klassen, die abstrakte Klassen erweitern, in einer Einheit testen kann.

Sollte ich die abstrakte Klasse testen, indem ich sie erweitere und die abstrakten Methoden ausblende, und dann alle konkreten Methoden testen? Dann nur die Methoden testen, die ich überschreibe, und die abstrakten Methoden in den Einheitstests für Objekte testen, die meine abstrakte Klasse erweitern?

Sollte ich einen abstrakten Testfall haben, der zum Testen der Methoden der abstrakten Klasse verwendet werden kann, und diese Klasse in meinem Testfall für Objekte erweitern, die die abstrakte Klasse erweitern?

Beachten Sie, dass meine abstrakte Klasse einige konkrete Methoden hat.

521voto

Nigel Thorne Punkte 20178

Es gibt zwei Möglichkeiten, wie abstrakte Basisklassen verwendet werden.

  1. Sie spezialisieren Ihr abstraktes Objekt, aber alle Clients werden die abgeleitete Klasse über ihre Basisschnittstelle verwenden.

  2. Sie verwenden eine abstrakte Basisklasse, um die Duplizierung innerhalb von Objekten in Ihrem Entwurf zu vermeiden, und die Clients verwenden die konkreten Implementierungen über ihre eigenen Schnittstellen!


Lösung für 1 - Strategie-Muster

Option1

Im ersten Fall haben Sie eine Schnittstelle, die durch die virtuellen Methoden in der abstrakten Klasse definiert ist, die von den abgeleiteten Klassen implementiert werden.

Sie sollten in Erwägung ziehen, diese Schnittstelle zu einer echten Schnittstelle zu machen, Ihre abstrakte Klasse in eine konkrete Klasse umzuwandeln und eine Instanz dieser Schnittstelle in ihren Konstruktor aufzunehmen. Ihre abgeleiteten Klassen werden dann zu Implementierungen dieser neuen Schnittstelle.

IMotor

Das bedeutet, dass Sie jetzt Ihre zuvor abstrakte Klasse mit einer Mock-Instanz der neuen Schnittstelle und jede neue Implementierung über die jetzt öffentliche Schnittstelle testen können. Alles ist einfach und testbar.


Lösung für 2

In der zweiten Situation arbeitet Ihre abstrakte Klasse als Hilfsklasse.

AbstractHelper

Werfen Sie einen Blick auf die darin enthaltenen Funktionen. Schauen Sie, ob etwas davon auf die zu bearbeitenden Objekte übertragen werden kann, um die Duplizierung zu minimieren. Wenn Sie noch etwas übrig haben, machen Sie es zu einer Hilfsklasse, die Ihre konkrete Implementierung in ihrem Konstruktor übernimmt und ihre Basisklasse entfernt.

Motor Helper

Dies wiederum führt zu konkreten Klassen, die einfach und leicht testbar sind.


In der Regel

Bevorzugen Sie ein komplexes Netz von einfachen Objekten gegenüber einem einfachen Netz von komplexen Objekten.

Der Schlüssel zu erweiterbarem, testbarem Code sind kleine Bausteine und eine unabhängige Verdrahtung.


Aktualisiert: Wie kann man mit Mischungen aus beiden umgehen?

Es ist möglich, eine Basisklasse zu haben, die beide Aufgaben erfüllt, d. h. sie hat eine öffentliche Schnittstelle und geschützte Hilfsmethoden. Wenn dies der Fall ist, können Sie die Hilfsmethoden in eine Klasse auslagern (Szenario 2) und den Vererbungsbaum in ein Strategiemuster umwandeln.

Wenn Sie feststellen, dass einige Methoden von Ihrer Basisklasse direkt implementiert werden und andere virtuell sind, können Sie den Vererbungsbaum immer noch in ein Strategiemuster umwandeln, aber ich würde dies auch als einen guten Indikator dafür ansehen, dass die Verantwortlichkeiten nicht korrekt ausgerichtet sind und möglicherweise überarbeitet werden müssen.


Update 2: Abstrakte Klassen als Sprungbrett (2014/06/12)

Neulich hatte ich eine Situation, in der ich abstrakte Begriffe verwendet habe, und ich würde gerne herausfinden, warum.

Wir haben ein Standardformat für unsere Konfigurationsdateien. Dieses spezielle Tool hat 3 Konfigurationsdateien, die alle in diesem Format vorliegen. Ich wollte eine stark typisierte Klasse für jede Einstellungsdatei, so dass eine Klasse durch Dependency Injection nach den Einstellungen fragen kann, die für sie von Bedeutung sind.

Ich implementierte dies, indem ich eine abstrakte Basisklasse hatte, die weiß, wie man die Formate der Einstellungsdateien analysiert, und abgeleitete Klassen, die dieselben Methoden zur Verfügung stellten, aber den Speicherort der Einstellungsdatei kapselten.

Ich hätte einen "SettingsFileParser" schreiben können, den die 3 Klassen umhüllen, und dann an die Basisklasse delegieren können, um die Datenzugriffsmethoden freizulegen. Ich habe mich entschieden, dies nicht zu tun dennoch da dies zu 3 abgeleiteten Klassen mit mehr Delegation Code als alles andere.

Wie auch immer... wenn sich dieser Code weiterentwickelt und die Verbraucher jeder dieser Einstellungsklassen klarer werden. Jeder Einstellungsbenutzer wird nach einigen Einstellungen fragen und sie auf irgendeine Weise umwandeln (da Einstellungen Text sind, können sie in Objekte verpackt oder in Zahlen umgewandelt werden usw.). Wenn dies geschieht, werde ich damit beginnen, diese Logik in Datenmanipulationsmethoden zu extrahieren und sie in die stark typisierten Einstellungsklassen zurückzuschieben. Dies wird zu einer Schnittstelle auf höherer Ebene für jeden Satz von Einstellungen führen, die schließlich nicht mehr weiß, dass sie es mit "Einstellungen" zu tun hat.

An diesem Punkt benötigen die stark typisierten Einstellungsklassen keine "Getter"-Methoden mehr, die die zugrunde liegende "Settings"-Implementierung offenlegen.

An diesem Punkt würde ich nicht mehr wollen, dass ihre öffentliche Schnittstelle die Einstellungszugriffsmethoden enthält; also werde ich diese Klasse ändern, um eine Einstellungsparserklasse zu kapseln, anstatt von ihr abzuleiten.

Die abstrakte Klasse ist daher: eine Möglichkeit für mich, Delegationscode im Moment zu vermeiden, und eine Markierung im Code, die mich daran erinnert, das Design später zu ändern. Es kann sein, dass ich nie dazu komme, also kann sie eine ganze Weile leben... das kann nur der Code zeigen.

Ich finde, das trifft auf jede Regel zu... wie "keine statischen Methoden" oder "keine privaten Methoden". Sie weisen auf einen Geruch im Code hin... und das ist gut so. Es hält Sie auf der Suche nach der Abstraktion, die Sie übersehen haben... und lässt Sie in der Zwischenzeit Ihren Kunden weiterhin einen Mehrwert bieten.

Ich stelle mir vor, dass Regeln wie diese eine Landschaft definieren, in der wartbarer Code in den Tälern lebt. Wenn man neues Verhalten hinzufügt, ist das wie Regen, der auf den Code fällt. Am Anfang platziert man es dort, wo es landet, dann refaktorisiert man, damit die Kräfte des guten Designs das Verhalten umherschieben können, bis alles in den Tälern landet.

287voto

Patrick Desjardins Punkte 130529

Schreiben Sie ein Mock-Objekt und verwenden Sie es nur zum Testen. Sie sind in der Regel sehr sehr sehr minimal (erben von der abstrakten Klasse) und nicht mehr.Dann, in Ihrem Unit Test können Sie die abstrakte Methode, die Sie testen möchten, aufrufen.

Sie sollten abstrakte Klassen testen, die eine gewisse Logik enthalten, wie alle anderen Klassen, die Sie haben.

12voto

Mnementh Punkte 48509

Bei abstrakten Klassen und Schnittstellen gehe ich wie folgt vor: Ich schreibe einen Test, der das Objekt so verwendet, wie es konkret ist. Aber die Variable vom Typ X (X ist die abstrakte Klasse) wird in dem Test nicht gesetzt. Diese Testklasse wird nicht zur Testsuite hinzugefügt, sondern Unterklassen davon, die eine setup-Methode haben, die die Variable auf eine konkrete Implementierung von X setzt. Die Unterklassen des nicht verwendeten Tests können bei Bedarf weitere Test-Methoden hinzufügen.

11voto

Um einen Unit-Test speziell für die abstrakte Klasse zu erstellen, sollten Sie sie zu Testzwecken ableiten, die Ergebnisse von base.method() und das beabsichtigte Verhalten beim Erben testen.

Man testet eine Methode, indem man sie aufruft, also testet man eine abstrakte Klasse, indem man sie implementiert...

9voto

Seth Petry-Johnson Punkte 11555

Wenn Ihre abstrakte Klasse konkrete Funktionalität enthält, die einen geschäftlichen Wert hat, dann werde ich sie normalerweise direkt testen, indem ich ein Testdouble erstelle, das die abstrakten Daten ausblendet, oder indem ich ein Mocking-Framework verwende, das dies für mich erledigt. Welche Methode ich wähle, hängt stark davon ab, ob ich testspezifische Implementierungen der abstrakten Methoden schreiben muss oder nicht.

Das häufigste Szenario, in dem ich dies tun muss, ist, wenn ich die Vorlage Methodenmuster wenn ich z. B. eine Art erweiterbaren Rahmen baue, der von einem Dritten verwendet wird. In diesem Fall ist es die abstrakte Klasse, die den Algorithmus definiert, den ich testen möchte, daher ist es sinnvoller, die abstrakte Basis zu testen als eine spezifische Implementierung.

Ich halte es jedoch für wichtig, dass sich diese Tests auf die nur konkrete Implementierungen von echter Geschäftslogik ; Sie sollten keine Einheitstests durchführen Ausführungsdetails der abstrakten Klasse, da Sie sonst spröde Tests erhalten.

CodeJaeger.com

CodeJaeger ist eine Gemeinschaft für Programmierer, die täglich Hilfe erhalten..
Wir haben viele Inhalte, und Sie können auch Ihre eigenen Fragen stellen oder die Fragen anderer Leute lösen.

Powered by:

X