Angenommen, ich habe eine Klasse (vergib das erfundene Beispiel und das schlechte Design davon):
class MyProfit
{
public decimal GetNewYorkRevenue();
public decimal GetNewYorkExpenses();
public decimal GetNewYorkProfit();
public decimal GetMiamiRevenue();
public decimal GetMiamiExpenses();
public decimal GetMiamiProfit();
public bool BothCitiesProfitable();
}
(Beachten Sie, dass die Methoden GetxxxRevenue () und GetxxxExpenses () Abhängigkeiten aufweisen, die ausgeblendet sind.)
Jetzt teste ich BothCitiesProfitable (), was von GetNewYorkProfit () und GetMiamiProfit () abhängt. Ist es in Ordnung, GetNewYorkProfit () und GetMiamiProfit () zu stubgen?
Anscheinend teste ich GetNewYorkProfit () und GetMiamiProfit () zusammen mit BothCitiesProfitable () gleichzeitig, wenn dies nicht der Fall ist. Ich muss sicherstellen, dass ich das Stubbing für GetxxxRevenue () und GetxxxExpenses () einrichte, damit die GetxxxProfit () -Methoden die richtigen Werte zurückgeben.
Bisher habe ich nur Beispiele für Stichprobenabhängigkeiten von externen Klassen gesehen, nicht von internen Methoden.
Und wenn es in Ordnung ist, gibt es ein bestimmtes Muster, das ich verwenden sollte, um dies zu tun?
AKTUALISIEREN
Ich mache mir Sorgen, dass wir das Kernthema übersehen könnten, und das ist wahrscheinlich die Schuld meines schlechten Beispiels. Die grundlegende Frage lautet: Wenn eine Methode in einer Klasse von einer anderen öffentlich zugänglichen Methode in derselben Klasse abhängt, ist es in Ordnung (oder sogar empfohlen), diese andere Methode zu entfernen?
Vielleicht fehlt mir etwas, aber ich bin mir nicht sicher, ob es immer Sinn macht, die Klasse aufzuteilen. Vielleicht wäre ein anderes minimal besseres Beispiel:
class Person
{
public string FirstName()
public string LastName()
public string FullName()
}
wobei vollständiger Name definiert ist als:
public string FullName()
{
return FirstName() + " " + LastName();
}
Ist es in Ordnung, beim Testen von FullName () FirstName () und LastName () zu stubben?
quelle
Antworten:
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:
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:
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?
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 sein
FirstName() + " " + 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:
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.
quelle
Obwohl ich der Antwort von Winston Ewert zustimme, ist es manchmal einfach nicht möglich, eine Klasse zu trennen, sei es aus Zeitgründen, weil die API bereits an anderer Stelle verwendet wird oder weil Sie eine haben.
In einem solchen Fall würde ich anstelle von Spottmethoden meine Tests schreiben, um die Klasse schrittweise abzudecken. Testen Sie die Methoden
getExpenses()
undgetRevenue()
und anschließend diegetProfit()
Methoden und anschließend die gemeinsam genutzten Methoden. Es ist richtig, dass Sie mehr als einen Test für eine bestimmte Methode haben. Da Sie jedoch Tests geschrieben haben, um die Methoden einzeln zu behandeln, können Sie sicher sein, dass Ihre Ausgaben bei einer getesteten Eingabe zuverlässig sind.quelle
Um Ihre Beispiele weiter zu vereinfachen, sagen Sie, Sie testen
C()
, was vonA()
und abhängtB()
, von denen jedes seine eigenen Abhängigkeiten hat. IMO kommt es darauf an, was Ihr Test zu erreichen versucht.Wenn Sie das Verhalten der zu test
C()
bei bekannten VerhaltenA()
undB()
dann ist es wahrscheinlich am einfachsten und am besten StubA()
undB()
. Dies würde wahrscheinlich von Puristen als Unit-Test bezeichnet.Wenn Sie das Verhalten des gesamten Systems zu testen sind (aus
C()
‚s - Perspektive), würde ich verlassenA()
undB()
wie umgesetzt und entweder Stummel aus ihren Abhängigkeiten (wenn es macht es zu Test möglich) oder eine Sandbox - Umgebung, wie zum Beispiel eines Test einrichten Datenbank. Ich würde das einen Integrationstest nennen.Beide Ansätze mögen gültig sein. Wenn es also so konzipiert ist, dass Sie es in beide Richtungen testen können, ist es auf lange Sicht wahrscheinlich besser.
quelle