Sie sollten die betreffende Klasse aufteilen.
Jede Klasse sollte eine einfache Aufgabe erledigen. Wenn Ihre Aufgabe zum Testen zu kompliziert ist, ist die Aufgabe, die die Klasse ausführt, zu groß.
Ignoriere die Dummheit dieses Designs:
class NewYork
{
decimal GetRevenue();
decimal GetExpenses();
decimal GetProfit();
}
class Miami
{
decimal GetRevenue();
decimal GetExpenses();
decimal GetProfit();
}
class MyProfit
{
MyProfit(NewYork new_york, Miami miami);
boolean bothProfitable();
}
AKTUALISIEREN
Das Problem bei Stub-Methoden in einer Klasse besteht darin, dass Sie die Kapselung verletzen. Bei Ihrem Test sollte geprüft werden, ob das äußere Verhalten des Objekts mit den Spezifikationen übereinstimmt. Was auch immer im Inneren des Objekts passiert, geht ihn nichts an.
Die Tatsache, dass FullName FirstName und LastName verwendet, ist ein Implementierungsdetail. Nichts außerhalb der Klasse sollte sich darum kümmern, dass das wahr ist. Indem Sie die öffentlichen Methoden verspotten, um das Objekt zu testen, nehmen Sie an, dass das Objekt implementiert ist.
Irgendwann in der Zukunft könnte diese Annahme nicht mehr zutreffen. Möglicherweise wird die gesamte Namenslogik auf ein Namensobjekt verlagert, das die Person einfach aufruft. Möglicherweise greift FullName direkt auf die Mitgliedsvariablen first_name und last_name zu, anstatt FirstName und LastName aufzurufen.
Die zweite Frage ist, warum Sie das Bedürfnis haben, dies zu tun. Schließlich könnte Ihre Personenklasse wie folgt getestet werden:
Person person = new Person("John", "Doe");
Test.AssertEquals(person.FullName(), "John Doe");
Sie sollten nicht das Gefühl haben, etwas für dieses Beispiel zu brauchen. Wenn Sie dann tun, sind Sie stumm und gut… hören Sie es auf! Es hat keinen Vorteil, die Methoden dort zu verspotten, da Sie sowieso die Kontrolle darüber haben, was darin enthalten ist.
Der einzige Fall, in dem es sinnvoll erscheint, die von FullName verwendeten Methoden zu verspotten, ist, wenn es sich bei FirstName () und LastName () um nicht triviale Operationen handelt. Vielleicht schreiben Sie einen dieser zufälligen Namensgeneratoren, oder Vorname und Nachname fragen die Datenbank nach Antworten ab oder so. Wenn dies jedoch der Fall ist, deutet dies darauf hin, dass das Objekt etwas tut, das nicht zur Klasse Person gehört.
Anders ausgedrückt bedeutet das Verspotten der Methoden, das Objekt in zwei Teile zu zerlegen. Ein Teil wird verspottet, während das andere Teil getestet wird. Was Sie tun, ist im Wesentlichen ein Ad-hoc-Auflösen des Objekts. Wenn dies der Fall ist, brechen Sie das Objekt bereits auf.
Wenn Ihre Klasse einfach ist, sollten Sie nicht das Bedürfnis verspüren, Teile davon während eines Tests zu verhöhnen. Wenn Ihre Klasse so komplex ist, dass Sie sich lustig fühlen, sollten Sie sie in einfachere Teile aufteilen.
UPDATE WIEDER
So wie ich es sehe, hat ein Objekt äußeres und inneres Verhalten. Externes Verhalten umfasst Rückgabewerte, Aufrufe an andere Objekte usw. Natürlich sollte alles in dieser Kategorie getestet werden. (Was würdest du sonst testen?) Aber das interne Verhalten sollte eigentlich nicht getestet werden.
Jetzt wird das interne Verhalten getestet, denn es führt zum externen Verhalten. Aber ich schreibe keine Tests direkt über das interne Verhalten, nur indirekt über das externe Verhalten.
Wenn ich etwas testen möchte, sollte es verschoben werden, damit es zu einem externen Verhalten wird. Deshalb denke ich, wenn Sie etwas verspotten möchten, sollten Sie das Objekt so aufteilen, dass das, was Sie verspotten möchten, jetzt im äußeren Verhalten der fraglichen Objekte liegt.
Aber welchen Unterschied macht es? Wenn FirstName () und LastName () Mitglieder eines anderen Objekts sind, ändert sich dann wirklich das Problem von FullName ()? Wenn wir entscheiden, dass es notwendig ist, FirstName und LastName zu verspotten, hilft es dann tatsächlich, dass sie sich auf einem anderen Objekt befinden?
Ich denke, wenn Sie Ihren spöttischen Ansatz verwenden, dann erstellen Sie eine Naht im Objekt. Sie haben Funktionen wie Vorname () und Nachname (), die direkt mit einer externen Datenquelle kommunizieren. Sie haben auch FullName (), was nicht der Fall ist. Aber da sie alle in derselben Klasse sind, ist das nicht ersichtlich. Einige Teile sollen nicht direkt auf die Datenquelle zugreifen, andere nicht. Ihr Code wird klarer, wenn Sie nur diese beiden Gruppen aufteilen.
BEARBEITEN
Machen wir einen Schritt zurück und fragen: Warum verspotten wir Objekte, wenn wir testen?
- Führen Sie die Tests konsistent aus (vermeiden Sie den Zugriff auf Dinge, die sich von Lauf zu Lauf ändern).
- Vermeiden Sie den Zugriff auf teure Ressourcen (nicht auf Dienste von Drittanbietern usw.)
- Vereinfachen Sie das zu testende System
- Erleichtern Sie das Testen aller möglichen Szenarien (z. B. Simulation von Fehlern usw.).
- Vermeiden Sie es, von den Details anderer Codeteile abzuhängen, damit Änderungen an diesen anderen Codeteilen diesen Test nicht unterbrechen.
Ich denke, die Gründe 1 bis 4 treffen auf dieses Szenario nicht zu. Das Verspotten der externen Quelle beim Testen von fullname berücksichtigt alle diese Gründe für das Verspotten. Das einzige Stück, das nicht behandelt wird, ist die Einfachheit, aber es scheint, dass das Objekt so einfach ist, dass es kein Problem darstellt.
Ich denke, Ihr Anliegen ist Grund Nummer 5. Das Anliegen ist, dass eine Änderung der Implementierung von FirstName und LastName zu einem späteren Zeitpunkt den Test unterbrechen wird. Zukünftig können Vorname und Nachname die Namen von einem anderen Ort oder einer anderen Quelle erhalten. Aber FullName wird es wahrscheinlich immer seinFirstName() + " " + LastName()
. Aus diesem Grund möchten Sie FullName testen, indem Sie FirstName und LastName verspotten.
Was Sie dann haben, ist eine Teilmenge des Personenobjekts, die sich mit größerer Wahrscheinlichkeit ändert als die anderen. Der Rest des Objekts verwendet diese Teilmenge. Diese Teilmenge ruft gegenwärtig ihre Daten mit einer Quelle ab, kann diese Daten jedoch zu einem späteren Zeitpunkt auf eine völlig andere Weise abrufen. Aber für mich klingt es so, als wäre diese Untermenge ein bestimmtes Objekt, das versucht, herauszukommen.
Es scheint mir, dass Sie das Objekt aufteilen, wenn Sie die Methode des Objekts verspotten. Aber Sie tun dies ad-hoc. Ihr Code macht nicht deutlich, dass sich in Ihrem Person-Objekt zwei unterschiedliche Teile befinden. Teilen Sie das Objekt einfach in Ihren eigentlichen Code auf, damit Sie beim Lesen Ihres Codes erkennen können, was gerade passiert. Wählen Sie die tatsächliche Aufteilung des Objekts, die Sinn macht, und versuchen Sie nicht, das Objekt für jeden Test unterschiedlich aufzuteilen.
Ich vermute, Sie haben etwas dagegen, Ihr Objekt aufzuteilen, aber warum?
BEARBEITEN
Ich lag falsch.
Sie sollten Objekte aufteilen, anstatt Ad-hoc-Teilungen durch Verspotten einzelner Methoden einzuführen. Ich habe mich jedoch zu sehr auf die eine Methode zum Teilen von Objekten konzentriert. OO bietet jedoch mehrere Methoden zum Teilen eines Objekts.
Was ich vorschlagen würde:
class PersonBase
{
abstract sring FirstName();
abstract string LastName();
string FullName()
{
return FirstName() + " " + LastName();
}
}
class Person extends PersonBase
{
string FirstName();
string LastName();
}
class FakePerson extends PersonBase
{
void setFirstName(string);
void setLastName(string);
string getFirstName();
string getLastName();
}
Vielleicht haben Sie das die ganze Zeit so gemacht. Aber ich denke nicht, dass diese Methode die Probleme haben wird, die ich bei den Spottmethoden gesehen habe, da wir klar definiert haben, auf welcher Seite sich jede Methode befindet. Durch die Verwendung der Vererbung vermeiden wir die Unannehmlichkeiten, die durch die Verwendung eines zusätzlichen Wrapper-Objekts entstehen würden.
Dies bringt eine gewisse Komplexität mit sich, und für nur ein paar Dienstprogrammfunktionen würde ich sie wahrscheinlich nur testen, indem ich die zugrunde liegende Quelle eines Drittanbieters verspotte. Sicher, sie sind einer erhöhten Bruchgefahr ausgesetzt, aber es lohnt sich nicht, sie neu anzuordnen. Wenn Sie ein Objekt haben, das so komplex ist, dass Sie es teilen müssen, halte ich so etwas für eine gute Idee.