Betrachten Sie eine Funktion wie diese:
function savePeople(dataStore, people) {
people.forEach(person => dataStore.savePerson(person));
}
Es könnte so verwendet werden:
myDataStore = new Store('some connection string', 'password');
myPeople = ['Joe', 'Maggie', 'John'];
savePeople(myDataStore, myPeople);
Nehmen wir an, dass dies Store
eigene Komponententests hat oder vom Hersteller bereitgestellt wird. Auf jeden Fall vertrauen wir Store
. Und nehmen wir weiter an, dass die Fehlerbehandlung - z. B. von Fehlern bei der Trennung der Datenbank - nicht in der Verantwortung von liegt savePeople
. Nehmen wir in der Tat an, dass der Speicher selbst eine magische Datenbank ist, die in keiner Weise fehlerfrei sein kann. Angesichts dieser Annahmen lautet die Frage:
Sollte savePeople()
ein Unit-Test durchgeführt werden, oder würden solche Tests dem Testen des eingebauten forEach
Sprachkonstrukts gleichkommen?
Wir könnten natürlich einen Schein abgeben dataStore
und behaupten, der dataStore.savePerson()
für jede Person einmal aufgerufen wird. Sie könnten durchaus das Argument vorbringen, dass ein solcher Test Sicherheit gegen Implementierungsänderungen bietet: Wenn wir uns beispielsweise dafür entscheiden, ihn durch forEach
eine herkömmliche for
Schleife oder eine andere Methode der Iteration zu ersetzen . Der Test ist also nicht ganz trivial. Und doch scheint es furchtbar nahe ...
Hier ist ein weiteres Beispiel, das möglicherweise fruchtbarer ist. Stellen Sie sich eine Funktion vor, die nur andere Objekte oder Funktionen koordiniert. Zum Beispiel:
function bakeCookies(dough, pan, oven) {
panWithRawCookies = pan.add(dough);
oven.addPan(panWithRawCookies);
oven.bakeCookies();
oven.removePan();
}
Wie sollte eine Funktion wie diese Unit getestet werden, vorausgesetzt, Sie glauben, dass dies der Fall ist? Es ist schwer für mich , jede Art von Gerät zu testen , sich vorzustellen , die einfach nicht spotten dough
, pan
und oven
, und dann behaupten , dass Methoden auf sie genannt werden. Ein solcher Test ist jedoch nichts anderes, als die exakte Implementierung der Funktion zu duplizieren.
Zeigt diese Unfähigkeit, die Funktion in einer aussagekräftigen Black-Box-Weise zu testen, einen Konstruktionsfehler bei der Funktion selbst an? Wenn ja, wie könnte es verbessert werden?
Um die Frage, die das bakeCookies
Beispiel motiviert , noch klarer zu gestalten, füge ich ein realistischeres Szenario hinzu, auf das ich beim Versuch gestoßen bin, dem Legacy-Code Tests hinzuzufügen und ihn umzugestalten.
Wenn ein Benutzer ein neues Konto erstellt, müssen einige Dinge geschehen: 1) Ein neuer Benutzerdatensatz muss in der Datenbank erstellt werden. 2) Eine Begrüßungs-E-Mail muss gesendet werden. 3) Die IP-Adresse des Benutzers muss für Betrugsfälle aufgezeichnet werden Zwecke.
Deshalb möchten wir eine Methode erstellen, die alle Schritte "Neuer Benutzer" miteinander verbindet:
function createNewUser(validatedUserData, emailService, dataStore) {
userId = dataStore.insertUserRecord(validateduserData);
emailService.sendWelcomeEmail(validatedUserData);
dataStore.recordIpAddress(userId, validatedUserData.ip);
}
Beachten Sie, dass, wenn eine dieser Methoden einen Fehler auslöst, der Fehler in den aufrufenden Code aufsteigen soll, damit er den Fehler nach Belieben behandeln kann. Wenn es vom API-Code aufgerufen wird, übersetzt es den Fehler möglicherweise in einen entsprechenden http-Antwortcode. Wenn es von einer Webschnittstelle aufgerufen wird, übersetzt es den Fehler möglicherweise in eine entsprechende Meldung, die dem Benutzer angezeigt wird, und so weiter. Der Punkt ist, dass diese Funktion nicht weiß, wie sie mit den Fehlern umgehen soll, die möglicherweise ausgelöst werden.
Das Wesen meiner Verwirrung ist, dass es zum Testen einer solchen Funktion in einer Einheit notwendig erscheint, die genaue Implementierung im Test selbst zu wiederholen (indem angegeben wird, dass Methoden in einer bestimmten Reihenfolge auf Mocks angewendet werden), und dass dies falsch erscheint.
quelle
Antworten:
Sollte
savePeople()
das Gerät getestet werden? Ja. Sie testen nicht, ob dasdataStore.savePerson
funktioniert oder ob die Datenbankverbindung funktioniert oder ob es überhauptforeach
funktioniert. Sie testen, dasssavePeople
das Versprechen, das es durch seinen Vertrag macht , erfüllt wird.Stellen Sie sich dieses Szenario vor: Jemand überarbeitet die Codebasis grundlegend und entfernt versehentlich den
forEach
Teil der Implementierung, sodass immer nur das erste Element gespeichert wird. Möchten Sie nicht, dass ein Komponententest dies erfasst?quelle
Normalerweise taucht diese Art von Frage auf, wenn Leute "Test-After" -Entwicklungen durchführen. Gehen Sie dieses Problem aus Sicht von TDD an, bei dem Tests vor der Implementierung durchgeführt werden, und stellen Sie sich diese Frage erneut als Übung.
Zumindest in meiner Anwendung von TDD, die normalerweise von außen nach innen ausgeführt wird, würde ich keine Funktion wie
savePeople
nach der Implementierung implementierensavePerson
. Die FunktionensavePeople
undsavePerson
würden als eine Funktion beginnen und aus denselben Einheitentests heraus getestet werden. Die Trennung zwischen den beiden würde sich nach einigen Tests im Refactoring-Schritt ergeben. Diese Arbeitsweise würde auch die Frage aufwerfen, wo sich die Funktion befindensavePeople
sollte - ob es sich um eine freie Funktion oder einen Teil der Funktion handeltdataStore
.Am Ende würden die Tests nicht nur überprüfen , ob Sie richtig eine speichern kann
Person
in demStore
, aber auch viele Personen. Dies würde mich auch zu der Frage führen, ob andere Überprüfungen erforderlich sind, zum Beispiel: "Muss ich sicherstellen, dass diesavePeople
Funktion atomar ist und entweder alle oder keine speichert?" Wie würden diese Fehler aussehen? "und so weiter. All dies ist viel mehr als nur die Überprüfung auf die Verwendung einerforEach
oder anderer Formen der Iteration.Wenn die Anforderung, mehr als eine Person auf einmal zu speichern, erst erfüllt wurde, nachdem sie erfüllt
savePerson
wurde, aktualisiere ich die vorhandenen Tests vonsavePerson
, um die neue Funktion auszuführensavePeople
, und stelle sicher, dass weiterhin eine Person gespeichert werden kann, indem zunächst einfach delegiert wird. Testen Sie dann das Verhalten für mehr als eine Person durch neue Tests und überlegen Sie, ob es notwendig wäre, das Verhalten atomar zu machen oder nicht.quelle
savePeople
? Wie ich im letzten Absatz von OP oder auf andere Weise beschrieben habe?savePerson
Ihnen vorgeschlagene Funktion verwenden, sondern sie durch allgemeinere testensavePeople
. Die Unit-Tests fürStore
werden so geändert, dass siesavePeople
nicht direkt aufgerufensavePerson
, sondern durchlaufen werden. Daher werden keine Mocks verwendet. Die Datenbank sollte natürlich nicht vorhanden sein, da wir Codierungsprobleme von den verschiedenen Integrationsproblemen, die bei tatsächlichen Datenbanken auftreten, isolieren möchten.Ja, das sollte es. Versuchen Sie jedoch, Ihre Testbedingungen unabhängig von der Implementierung zu schreiben. Verwandeln Sie beispielsweise Ihr Verwendungsbeispiel in einen Komponententest:
Dieser Test macht mehrere Dinge:
savePeople()
savePeople()
savePeople()
Beachten Sie, dass Sie den Datenspeicher weiterhin verspotten / stubben / fälschen können. In diesem Fall würde ich nicht nach expliziten Funktionsaufrufen suchen, sondern nach dem Ergebnis der Operation. Auf diese Weise ist mein Test auf zukünftige Änderungen / Refaktoren vorbereitet.
Beispielsweise könnte Ihre Datenspeicherimplementierung
saveBulkPerson()
in Zukunft eine Methode bereitstellen - jetzt würde eine Änderung der zu verwendenden ImplementierungsavePeople()
densaveBulkPerson()
Komponententest nicht abbrechen, solange diessaveBulkPerson()
wie erwartet funktioniert. Und wennsaveBulkPerson()
irgendwie nicht funktioniert wie erwartet, Ihr Gerät zu testen wird , dass fangen.Versuchen Sie, wie gesagt, die erwarteten Ergebnisse und die Funktionsschnittstelle zu testen, nicht die Implementierung (es sei denn, Sie führen Integrationstests durch - dann kann das Abfangen bestimmter Funktionsaufrufe hilfreich sein). Wenn es mehrere Möglichkeiten gibt, eine Funktion zu implementieren, sollten alle mit Ihrem Komponententest funktionieren.
In Bezug auf Ihre Aktualisierung der Frage:
Test auf Zustandsänderungen! ZB wird ein Teil des Teigs verwendet. Stellen Sie gemäß Ihrer Implementierung sicher, dass die verwendete Menge
dough
in die Menge passt,pan
oder dass die Mengedough
aufgebraucht ist. Stellen Siepan
nach dem Funktionsaufruf sicher, dass das Cookies enthält. Stellen Sie sicher, dass dasoven
leer ist / sich im selben Zustand wie zuvor befindet.Überprüfen Sie für zusätzliche Tests die Flankenfälle: Was passiert, wenn die
oven
vor dem Anruf nicht leer ist? Was passiert, wenn nicht genug da istdough
? Ist derpan
schon voll?Sie sollten in der Lage sein, alle erforderlichen Daten für diese Tests aus den Objekten Teig, Pfanne und Ofen selbst abzuleiten. Die Funktionsaufrufe müssen nicht erfasst werden. Behandeln Sie die Funktion so, als stünde Ihnen ihre Implementierung nicht zur Verfügung!
Tatsächlich schreiben die meisten TDD-Benutzer ihre Tests, bevor sie die Funktion schreiben, sodass sie nicht von der tatsächlichen Implementierung abhängig sind.
Für Ihre neueste Ergänzung:
Für eine Funktion wie diese würde ich die Parameter
dataStore
und verspotten / stub / fake (was auch immer allgemeiner erscheint)emailService
. Diese Funktion führt keine Zustandsübergänge für einen Parameter aus, sondern delegiert sie an Methoden einiger von ihnen. Ich würde versuchen, zu überprüfen, dass der Aufruf der Funktion 4 Dinge tat:Die ersten drei Prüfungen können mit Schein, Stich oder Fälschung von
dataStore
und durchgeführt werdenemailService
(Sie möchten beim Testen wirklich keine E-Mails senden). Da ich dies für einige der Kommentare nachschlagen musste, sind dies die Unterschiede:dataStore
, die nur eine geeignete Version voninsertUserRecord()
und implementiertrecordIpAddress()
.Erwartete Ausnahmen / Fehler sind gültige Testfälle: Sie bestätigen, dass sich die Funktion in einem solchen Fall wie erwartet verhält. Dies kann erreicht werden, indem das entsprechende Schein- / Fälschungs- / Stichobjekt geworfen wird, wenn dies gewünscht wird.
Manchmal muss das gemacht werden (obwohl es Ihnen bei Integrationstests am wichtigsten ist). Häufiger gibt es andere Möglichkeiten, um die erwarteten Nebenwirkungen / Zustandsänderungen zu überprüfen.
Das Überprüfen der genauen Funktionsaufrufe führt zu eher spröden Komponententests: Nur kleine Änderungen an der ursprünglichen Funktion führen zum Fehlschlagen. Dies kann erwünscht sein oder nicht, erfordert jedoch eine Änderung der entsprechenden Komponententests, wenn Sie eine Funktion ändern (Refactoring, Optimierung, Fehlerbehebung, ...).
Leider verliert der Komponententest in diesem Fall etwas an Glaubwürdigkeit: Da er geändert wurde, bestätigt er die Funktion nicht, nachdem sich die Änderung wie zuvor verhalten hat.
Angenommen, jemand fügt
oven.preheat()
in Ihrem Beispiel für das Backen von Cookies einen Aufruf für (Optimierung!) Hinzu:Bei meinen Unit-Tests versuche ich, so allgemein wie möglich zu sein: Wenn sich die Implementierung ändert, aber das sichtbare Verhalten (aus Sicht des Aufrufers) immer noch dasselbe ist, sollten meine Tests bestanden werden. Im Idealfall sollte der einzige Fall, in dem ich einen vorhandenen Komponententest ändern muss, eine Fehlerbehebung sein (des Tests, nicht der getesteten Funktion).
quelle
myDataStore.containsPerson('Joe')
, davon ausgehen, dass eine funktionale Testdatenbank vorhanden ist. Sobald Sie das tun, schreiben Sie einen Integrationstest und keinen Komponententest.savePeople()
diese Personen tatsächlich zu dem von Ihnen bereitgestellten Datenspeicher hinzugefügt werden, solange dieser Datenspeicher die erwartete Schnittstelle implementiert. Ein Integrationstest besteht beispielsweise darin, zu überprüfen, ob mein Datenbank-Wrapper die richtigen Datenbankaufrufe für einen Methodenaufruf ausführt.myDataStore.containsPerson('Joe')
, müssen Sie eine funktionale Datenbank verwenden. Sobald Sie diesen Schritt gemacht haben, ist es kein Unit-Test mehr.myPeople
) im Array sind. IMHO sollte ein Mock immer noch dasselbe beobachtbare Verhalten aufweisen, das von einem realen Objekt erwartet wird, andernfalls prüfen Sie, ob die Mock-Schnittstelle und nicht die reale Schnittstelle kompatibel ist.Der primäre Wert, den ein solcher Test bietet, besteht darin, dass Ihre Implementierung überarbeitbar ist.
Ich habe in meiner Karriere viele Leistungsoptimierungen durchgeführt und häufig Probleme mit dem von Ihnen gezeigten Muster festgestellt: Um N Entitäten in der Datenbank zu speichern, führen Sie N Einfügungen durch. Normalerweise ist es effizienter, eine Masseneinfügung mit einer einzelnen Anweisung durchzuführen.
Andererseits wollen wir auch nicht vorzeitig optimieren. Wenn Sie in der Regel nur 1 bis 3 Personen gleichzeitig speichern, ist das Schreiben eines optimierten Stapels möglicherweise zu viel des Guten.
Mit einem ordnungsgemäßen Komponententest können Sie ihn so schreiben, wie Sie ihn oben implementiert haben. Wenn Sie feststellen, dass Sie ihn optimieren müssen, können Sie dies mit dem Sicherheitsnetz eines automatisierten Tests tun, um etwaige Fehler aufzufangen. Dies hängt natürlich von der Qualität der Tests ab. Testen Sie also großzügig und gut.
Der sekundäre Vorteil des Unit-Testens dieses Verhaltens besteht darin, dass es als Dokumentation für dessen Zweck dient. Dieses triviale Beispiel mag offensichtlich sein, aber im Hinblick auf den nächsten Punkt könnte es sehr wichtig sein.
Der dritte Vorteil, auf den andere hingewiesen haben, besteht darin, dass Sie verdeckte Details testen können, die mit Integrations- oder Abnahmetests nur sehr schwer zu testen sind. Wenn zum Beispiel die Anforderung besteht, dass alle Benutzer atomar gespeichert werden, können Sie einen Testfall dafür schreiben, der Ihnen Aufschluss über das erwartete Verhalten gibt und auch als Dokumentation für eine möglicherweise nicht offensichtliche Anforderung dient an neue Entwickler.
Ich werde einen Gedanken hinzufügen, den ich von einem TDD-Lehrer bekommen habe. Testen Sie die Methode nicht. Testen Sie das Verhalten. Mit anderen Worten, Sie testen nicht, dass dies
savePeople
funktioniert. Sie testen, dass mehrere Benutzer in einem einzigen Anruf gespeichert werden können.Ich fand meine Fähigkeit Qualität Unit - Tests zu tun und TDD zu verbessern , wenn ich darüber nachzudenken , Unit - Tests gestoppt, Überprüfung, ob ein Programm funktioniert, sondern sie überprüfen, ob eine Einheit des Code tut , was ich erwarte . Das sind andere. Sie verifizieren nicht, dass es funktioniert, aber sie verifizieren, dass es das tut, was ich denke. Als ich anfing, so zu denken, änderte sich meine Perspektive.
quelle
savePerson
ihn für jede Person in der Liste aufgerufen - würde jedoch mit einem Bulk Insert Refactoring brechen. Was für mich bedeutet, dass es ein schlechter Komponententest ist. Ich sehe jedoch keine Alternative, die sowohl die Massenimplementierung als auch die Einzel-Sicherungsimplementierung durchläuft, ohne eine tatsächliche Testdatenbank zu verwenden und dagegen zu behaupten, was falsch zu sein scheint. Können Sie einen Test bereitstellen, der für beide Implementierungen funktioniert?savePerson
Methode für jede Eingabe aufgerufen wurde. Wenn Sie die Schleife durch eine Masseneinfügung ersetzen, würden Sie diese Methode nicht mehr aufrufen. Ihr Test würde also brechen. Wenn Sie etwas anderes im Sinn haben, bin ich offen dafür, aber ich sehe es noch nicht. (Und es nicht zu sehen, war mein Punkt.)Sollte
bakeCookies()
getestet werden? Ja.Nicht wirklich. Schauen Sie sich genau an, WAS die Funktion tun soll - sie soll das
oven
Objekt in einen bestimmten Zustand versetzen. Betrachtet man den Code, so scheint es, dass die Zustände derpan
unddough
-Objekte nicht wirklich wichtig sind. Übergeben Sie also einoven
Objekt (oder verspotten Sie es) und stellen Sie am Ende des Funktionsaufrufs fest, dass es sich in einem bestimmten Zustand befindet.Mit anderen Worten, Sie sollten behaupten, dass
bakeCookies()
die Kekse gebacken haben .Bei sehr kurzen Funktionen scheinen Komponententests nicht viel mehr zu sein als Tautologie. Aber vergessen Sie nicht, Ihr Programm wird viel länger dauern als die Zeit, in der Sie es schreiben. Diese Funktion kann sich in Zukunft ändern oder auch nicht.
Unit-Tests haben zwei Funktionen:
Es testet, dass alles funktioniert. Dies ist die am wenigsten nützliche Funktion, für die Unit-Tests durchgeführt werden, und es scheint, dass Sie diese Funktionalität nur bei der Frage berücksichtigen.
Es wird überprüft, ob zukünftige Änderungen des Programms die zuvor implementierte Funktionalität beeinträchtigen. Dies ist die nützlichste Funktion von Komponententests und verhindert die Einführung von Fehlern in große Programme. Es ist nützlich beim normalen Codieren, wenn Features zum Programm hinzugefügt werden, aber es ist nützlicher beim Refactoring und bei Optimierungen, bei denen die Kernalgorithmen, die das Programm implementieren, dramatisch geändert werden, ohne das beobachtbare Verhalten des Programms zu ändern.
Testen Sie nicht den Code innerhalb der Funktion. Testen Sie stattdessen, ob die Funktion das tut, was sie sagt. Wenn Sie Unit-Tests auf diese Weise betrachten (Funktionen testen, nicht Code), werden Sie feststellen, dass Sie niemals Sprachkonstrukte oder gar Anwendungslogik testen. Sie testen eine API.
quelle
bakeCookies
die auf diese Weise getestet werden, bei Refaktoren, die sich nicht auf das beobachtbare Verhalten der Anwendung auswirken, zum Absturz neigen.Ja. Aber Sie könnten es auf eine Weise tun, die das Konstrukt erneut testet.
Hier ist zu beachten, wie sich diese Funktion verhält, wenn ein
savePerson
Fehler auf halbem Weg auftritt. Wie soll es funktionieren?Das ist die Art subtilen Verhaltens, die die Funktion bietet, die Sie mit Unit-Tests erzwingen sollten.
quelle
savePeople
nicht für die Fehlerbehandlung verantwortlich sein sollte. Um wieder zu klären, unter der Annahme , dasssavePeople
verantwortlich ist nur für Sie durch die Liste iterieren und die Speicherung der einzelnen Elemente an einem anderen Methode zu delegieren, sollte es noch getestet werden?foreach
Konstrukt zu beschränken und keine Bedingungen, Nebenwirkungen oder Verhaltensweisen außerhalb des Konstrukts, dann haben Sie Recht. Der neue Unit-Test ist eigentlich gar nicht so interessant.Der Schlüssel hier ist Ihre Sicht auf eine bestimmte Funktion als Trivialität. Der größte Teil der Programmierung ist trivial: einen Wert zuweisen, etwas rechnen, eine Entscheidung treffen: Wenn dies dann der Fall ist, eine Schleife fortsetzen, bis ... Alle trivial. Sie sind gerade durch die ersten 5 Kapitel eines Buches gekommen, das eine Programmiersprache unterrichtet.
Die Tatsache, dass das Schreiben eines Tests so einfach ist, sollte ein Zeichen dafür sein, dass Ihr Design nicht so schlecht ist. Wünschen Sie ein Design, das nicht einfach zu testen ist?
"Das wird sich nie ändern." So beginnen die meisten gescheiterten Projekte. Ein Einheitentest bestimmt nur, ob die Einheit unter bestimmten Umständen wie erwartet funktioniert. Lass es passieren und dann kannst du die Implementierungsdetails vergessen und es einfach benutzen. Nutzen Sie diesen Gehirnraum für die nächste Aufgabe.
Zu wissen, dass die Dinge wie erwartet funktionieren, ist sehr wichtig und bei großen Projekten und insbesondere bei großen Teams nicht trivial. Wenn Programmierer eines gemeinsam haben, ist die Tatsache, dass wir uns alle mit dem schrecklichen Code eines anderen auseinandersetzen mussten. Das Mindeste, was wir tun können, sind einige Tests. Wenn Sie Zweifel haben, schreiben Sie einen Test und fahren Sie fort.
quelle
Dies wurde bereits von @BryanOakley beantwortet, aber ich habe einige zusätzliche Argumente (denke ich):
Zunächst dient ein Unit-Test zum Testen der Vertragserfüllung und nicht der Implementierung einer API. Der Test sollte Vorbedingungen setzen, dann aufrufen und auf Auswirkungen, Nebenwirkungen, etwaige Invarianten und Post-Bedingungen prüfen. Wenn Sie entscheiden, was getestet werden soll, spielt die Implementierung der API keine Rolle (und sollte es auch nicht sein) .
Zweitens wird Ihr Test dort sein, um die Invarianten zu überprüfen, wenn sich die Funktion ändert . Die Tatsache, dass es sich jetzt nicht ändert, bedeutet nicht, dass Sie den Test nicht haben sollten.
Drittens ist es wertvoll, einen trivialen Test sowohl in einem TDD-Ansatz (der ihn vorschreibt) als auch außerhalb davon implementiert zu haben.
Beim Schreiben von C ++ neige ich dazu, für meine Klassen einen einfachen Test zu schreiben, der ein Objekt instanziiert und Invarianten überprüft (zuweisbar, regulär usw.). Ich fand es überraschend, wie oft dieser Test während der Entwicklung unterbrochen wurde (zum Beispiel durch versehentliches Hinzufügen eines nicht beweglichen Elements in einer Klasse).
quelle
Ich denke, Ihre Frage lautet:
Wie teste ich eine Void-Funktion, ohne dass es sich um einen Integrationstest handelt?
Wenn wir zum Beispiel Ihre Cookie-Backfunktion so ändern, dass Cookies zurückgegeben werden, wird sofort klar, wie der Test aussehen soll.
Wenn wir pan.GetCookies aufrufen müssen, nachdem wir die Funktion aufgerufen haben, können wir uns fragen, ob es sich wirklich um einen Integrationstest handelt oder ob wir nicht nur das pan-Objekt testen.
Ich denke, Sie haben Recht damit, dass Unit-Tests mit allem, was verspottet wurde, und nur die Überprüfung der Funktionen xy und z als Mangelwert bezeichnet wurden.
Aber! Ich würde argumentieren, dass Sie in diesem Fall Ihre Void-Funktionen überarbeiten sollten, um ein testbares Ergebnis zurückzugeben, ODER reale Objekte verwenden und einen Integrationstest durchführen sollten
--- Update für das createNewUser-Beispiel
OK, diesmal wird das Ergebnis der Funktion nicht einfach zurückgegeben. Wir wollen den Zustand der Parameter ändern.
Hier werde ich etwas kontrovers. Ich erstelle konkrete Mock-Implementierungen für die Stateful-Parameter
Bitte, liebe Leser, versuchen Sie, Ihre Wut unter Kontrolle zu halten!
damit...
Dies trennt das Implementierungsdetail der zu testenden Methode vom gewünschten Verhalten. Eine alternative Implementierung:
Wird immer noch den Unit-Test bestehen. Außerdem haben Sie den Vorteil, dass Sie die Scheinobjekte testübergreifend wiederverwenden und sie für UI- oder Integrationstests in Ihre Anwendung einfügen können.
quelle
bakeCookies
Argument, die gebackenen Kekse zurückzugeben, ist genau richtig, und ich hatte einige Gedanken, nachdem ich sie veröffentlicht habe. Ich denke, es ist wieder kein gutes Beispiel. Ich habe ein weiteres Update hinzugefügt, das hoffentlich ein realistischeres Beispiel dafür liefert, was meine Frage motiviert. Ich würde mich über Ihre Beiträge freuen.Sie sollten auch testen
bakeCookies
- was würde / sollte e..gbakeCookies(egg, pan, oven)
ergeben? Spiegelei oder eine Ausnahme? Weder für sichpan
noch für sichoven
selbst interessieren die eigentlichen Zutaten, da keiner von ihnen eigentlichbakeCookies
Kekse liefern sollte, sondern soll. Im Allgemeinen kann es davon abhängen, wiedough
es beschafft wird und ob die Chance besteht, dass es einfach wirdegg
oder zwater
. B. stattdessen.quelle