Angenommen, ich habe die folgende (stark vereinfachte) Klassenstruktur:
class Base
{
public:
Base(int valueForFoo) : foo(valueForFoo) { };
virtual ~Base() = 0;
int doThings() { return foo; };
int doOtherThings() { return 42; };
protected:
int foo;
}
class BarDerived : public Base
{
public:
BarDerived() : Base(12) { };
~BarDerived() { };
int doBarThings() { return foo + 1; };
}
class BazDerived : public Base
{
public:
BazDerived() : Base(25) { };
~BazDerived() { };
int doBazThings() { return 2 * foo; };
}
Wie Sie sehen können, die doThings
Funktion in der Basisklasse zu unterschiedlichen Ergebnissen in jeder gibt abgeleiteten Klasse aufgrund der unterschiedlichen Werte foo
, während die doOtherThings
Funktion verhält sich gleich über alle Klassen .
Wenn ich Unit-Tests für diese Klassen implementieren möchte doThings
, ist mir die Handhabung von doBarThings
/ doBazThings
klar - sie müssen für jede abgeleitete Klasse abgedeckt werden. Aber wie soll doOtherThings
damit umgegangen werden? Ist es empfehlenswert, den Testfall in beiden abgeleiteten Klassen im Wesentlichen zu duplizieren? Das Problem wird schlimmer, wenn es ein halbes Dutzend Funktionen wie doOtherThings
und mehr abgeleitete Klassen gibt .
quelle
virtual
?BarDerived
undBase
kann einmal die gleiche Klasse gewesen sein. Wenn eine ähnliche Funktionalität hinzugefügt werden sollte, wurde der gemeinsame Teil in die Basisklasse verschoben, wobei in jeder abgeleiteten Klasse unterschiedliche Spezialisierungen implementiert wurden.Antworten:
In Ihren Tests
BarDerived
möchten Sie nachweisen, dass alle (öffentlichen) MethodenBarDerived
korrekt funktionieren (für die von Ihnen getesteten Situationen). Ähnliches gilt fürBazDerived
.Die Tatsache, dass einige der Methoden in einer Basisklasse implementiert sind, ändert nichts an diesem Testziel für
BarDerived
undBazDerived
. Das führt zu dem Schluss , dassBase::doOtherThings
sowohl im Rahmen getestet werden sollBarDerived
und ,BazDerived
und dass Sie bekommen sehr Ähnliche Tests für diese Funktion.Der Vorteil des Testens
doOtherThings
für jede abgeleitete Klasse besteht darin, dass der Testfehler im Testfall darauf hinweist, dass Sie möglicherweise gegen die Anforderungen einer anderen Klasse verstoßen , wenn die Anforderungen fürBarDerived
Änderungen so sind, dassBarDerived::doOtherThings
24 zurückgegeben werden müssenBazDerived
.quelle
Normalerweise würde ich erwarten, dass Base eine eigene Spezifikation hat, die Sie für jede konforme Implementierung überprüfen können, einschließlich der abgeleiteten Klassen.
quelle
Sie haben hier einen Konflikt.
Sie möchten den Rückgabewert von testen
doThings()
, der auf einem Literal (const-Wert) basiert.Jeder Test, den Sie dafür schreiben, führt dazu, dass ein konstanter Wert getestet wird , was unsinnig ist.
Um Ihnen ein sinnlicheres Beispiel zu zeigen (ich bin schneller mit C #, aber das Prinzip ist das gleiche)
Diese Klasse kann sinnvoll getestet werden:
Das Testen ist sinnvoller. Sein Ausgang ist auf den Eingang aus , dass Sie gewählt haben , es zu geben. In einem solchen Fall können Sie einer Klasse eine willkürlich ausgewählte Eingabe geben, ihre Ausgabe beobachten und testen, ob sie Ihren Erwartungen entspricht.
Einige Beispiele für dieses Testprinzip. Beachten Sie, dass meine Beispiele immer die direkte Kontrolle über die Eingabe der testbaren Methode haben.
Alle diese Tests können jeden gewünschten Wert eingeben , solange ihre Erwartungen mit den eingegebenen übereinstimmen .
Wenn Ihre Ausgabe jedoch durch einen konstanten Wert bestimmt wird, müssen Sie eine konstante Erwartung erstellen und dann testen, ob die erste mit der zweiten übereinstimmt. Was albern ist, das wird entweder immer oder nie passieren; Beides ist kein aussagekräftiges Ergebnis.
Einige Beispiele für dieses Testprinzip. Beachten Sie, dass diese Beispiele keine Kontrolle über mindestens einen der zu vergleichenden Werte haben.
Diese Tests für einen bestimmten Wert. Wenn Sie diesen Wert ändern, schlagen Ihre Tests fehl. Im Wesentlichen testen Sie nicht, ob Ihre Logik für eine gültige Eingabe funktioniert, sondern nur, ob jemand in der Lage ist , ein Ergebnis genau vorherzusagen , über das er keine Kontrolle hat.
Das testet im Wesentlichen das Wissen des Testautors über die Geschäftslogik. Es wird nicht der Code getestet, sondern der Autor selbst.
Zurück zu Ihrem Beispiel:
Warum hat
BarDerived
immer einfoo
gleich12
? Was ist die Bedeutung davon?Und da Sie dies bereits entschieden haben, was versuchen Sie zu erreichen, indem Sie einen Test schreiben, der bestätigt, dass er
BarDerived
immerfoo
gleich ist12
?Dies wird noch schlimmer, wenn Sie anfangen zu berücksichtigen, dass
doThings()
dies in einer abgeleiteten Klasse überschrieben werden kann. Stellen SieAnotherDerived
sich vor, Sie würden überschreiben,doThings()
damit es immer zurückkehrtfoo * 2
. Jetzt haben Sie eine Klasse, die fest codiert istBase(12)
und derendoThings()
Wert 24 ist. Obwohl sie technisch testbar ist, hat sie keine kontextbezogene Bedeutung. Der Test ist nicht nachvollziehbar.Ich kann mir wirklich keinen Grund vorstellen, diesen fest codierten Wertansatz zu verwenden. Selbst wenn es einen gültigen Anwendungsfall gibt, verstehe ich nicht, warum Sie versuchen, einen Test zu schreiben, um diesen fest codierten Wert zu bestätigen . Es gibt nichts zu gewinnen, wenn getestet wird, ob ein konstanter Wert dem gleichen konstanten Wert entspricht.
Jeder Testfehler beweist von Natur aus, dass der Test falsch ist . Es gibt kein Ergebnis, bei dem ein Testfehler beweist, dass die Geschäftslogik falsch ist. Sie können effektiv nicht bestätigen, welche Tests erstellt wurden, um sie überhaupt zu bestätigen.
Das Problem hat nichts mit Vererbung zu tun, falls Sie sich fragen. Sie haben zufällig einen const-Wert im Basisklassenkonstruktor verwendet, aber Sie hätten diesen const-Wert an einer anderen Stelle verwenden können, und dann wäre er nicht mit einer geerbten Klasse verbunden.
Bearbeiten
Es gibt Fälle, in denen fest codierte Werte kein Problem darstellen. (Nochmals, entschuldigen Sie die C # -Syntax, aber das Prinzip ist immer noch dasselbe)
Während der konstante Wert (
2
/3
) noch den Ausgabewert von beeinflusstGetMultipliedValue()
, hat der Verbraucher Ihrer Klasse auch noch die Kontrolle darüber!In diesem Beispiel können noch aussagekräftige Tests geschrieben werden:
base(value, 2)
mit der const in übereinstimmtinputValue * 2
.Der erste Aufzählungspunkt ist für den Test nicht relevant. Der zweite ist!
quelle
<
und die>
Klammern, die die HTML-Tags kapseln. Leider (aufgrund von Wahnsinn) verwendet ein "spezialisierter" Browser![{
und}]!
stattdessen und Intitech entscheidet, dass Sie diesen Browser in Ihrem HTML-Writer unterstützen müssen. Zum Beispiel haben Sie diegetHeaderStart()
undgetHeaderEnd()
Funktion, die - bis jetzt - zurückgegeben hat<HEADER>
und<\HEADER>
.<
, das andere mit![{
. Das wäre aber ziemlich schlimm. So haben Sie jede Funktion legen Sie die Stelle (n) Satz in einer Variablen in den Orten<
und>
gehen würden, und abgeleitete Klassen erstellen , die - je nachdem , ob sie standardkonforme oder verrückt sind, bietet die sachgemäßen<HEADER>
oder![{HEADER}]!
WedergetHeaderStart()
Funktion ist bei der Eingabe abhängig und setzt auf die Konstante gesetzt während der Konstruktion der abgeleiteten Klasse. Trotzdem würde ich michgetHeaderStart()
sinnvoll oder nicht?