In dem Kommentar zu diesem großartigen Beitrag erwähnte Roy Osherove das OAPT- Projekt, mit dem jede Zusicherung in einem einzigen Test ausgeführt werden soll.
Folgendes steht auf der Homepage des Projekts:
Richtige Komponententests sollten aus genau einem Grund fehlschlagen. Aus diesem Grund sollten Sie eine Zusicherung pro Komponententest verwenden.
Und auch Roy schrieb in Kommentaren:
Meine Richtlinie ist normalerweise, dass Sie ein logisches KONZEPT pro Test testen. Sie können mehrere Zusicherungen für dasselbe Objekt haben . In der Regel handelt es sich dabei um dasselbe Konzept, das getestet wird.
Ich denke, dass es einige Fälle gibt, in denen mehrere Behauptungen erforderlich sind (z. B. Behauptung der Wache ), aber im Allgemeinen versuche ich, dies zu vermeiden. Was ist deine Meinung? Bitte geben Sie ein Beispiel aus der Praxis an, in dem mehrere Asserts wirklich benötigt werden .
quelle
RowTest
Zusicherungen anstelle von (MbUnit) /TestCase
(NUnit) verwendet wurden, um eine Vielzahl von Randfallverhalten zu testen. Verwenden Sie die richtigen Werkzeuge für den Job! (Leider scheint MSTest noch keine Zeilentestfunktion zu haben.)RowTest
undTestCase
mit Testdatenquellen erhalten . Ich verwende eine einfache CSV-Datei mit großem Erfolg.Antworten:
Ich denke nicht, dass es notwendigerweise eine schlechte Sache ist , aber ich denke, wir sollten uns bemühen, nur einzelne Aussagen in unseren Tests zu haben. Das bedeutet, dass Sie viel mehr Tests schreiben und unsere Tests immer nur eine Sache gleichzeitig testen.
Davon abgesehen würde ich sagen, dass vielleicht die Hälfte meiner Tests tatsächlich nur eine Aussage enthält. Ich denke, es wird nur dann zu einem Code (Test?) - Geruch, wenn Sie ungefähr fünf oder mehr Asserts in Ihrem Test haben.
Wie lösen Sie mehrere Behauptungen?
quelle
Tests sollten nur aus einem Grund fehlschlagen, aber das bedeutet nicht immer, dass es nur eine
Assert
Aussage geben sollte. IMHO ist es wichtiger, sich an das Muster " Anordnen, Handeln, Bestätigen " zu halten .Der Schlüssel ist, dass Sie nur eine Aktion haben und dann die Ergebnisse dieser Aktion mit Asserts überprüfen. Aber es ist „anordnen, Act, Assert, Testende “. Wenn Sie versucht sind, mit dem Testen fortzufahren, indem Sie eine weitere Aktion ausführen, und anschließend weitere Asserts ausführen, führen Sie stattdessen einen separaten Test durch.
Ich freue mich über mehrere Aussagen, die zum Testen derselben Aktion gehören. z.B
oder
Sie könnten diese in einer Aussage kombinieren, aber das ist etwas anderes, als darauf zu bestehen, dass Sie sollten oder müssen . Es gibt keine Verbesserung, wenn man sie kombiniert.
zB der erste könnte sein
Dies ist jedoch nicht besser - die daraus resultierende Fehlermeldung ist weniger spezifisch und hat keine weiteren Vorteile. Ich bin sicher, Sie können sich andere Beispiele vorstellen, bei denen die Kombination von zwei oder drei (oder mehr) Asserts zu einer großen booleschen Bedingung das Lesen erschwert, das Ändern erschwert und das Ermitteln der Fehlerursache erschwert. Warum tun Sie dies nur aus Gründen der Regel?
NB : Der Code, den ich hier schreibe, ist C # mit NUnit, aber die Prinzipien gelten für andere Sprachen und Frameworks. Die Syntax kann auch sehr ähnlich sein.
quelle
Assert.IsBetween(10, 100, value)
das drucktExpected 8 to be between 10 and 100
ist besser als zwei meiner Meinung nach getrennt behauptet. Sie können sicher argumentieren, dass dies nicht notwendig ist, aber es lohnt sich normalerweise zu überlegen, ob es einfach ist, sich auf eine einzelne Aussage zu beschränken, bevor Sie eine ganze Reihe davon erstellen.Ich hätte nie gedacht, dass mehr als eine Behauptung eine schlechte Sache ist.
Das mache ich die ganze Zeit:
Hier verwende ich mehrere Asserts, um sicherzustellen, dass komplexe Bedingungen in das erwartete Prädikat umgewandelt werden können.
Ich teste nur eine Einheit (die
ToPredicate
Methode), aber ich decke alles ab, was mir im Test einfällt.quelle
Wenn ich Unit-Tests verwende, um das Verhalten auf hoher Ebene zu validieren, füge ich auf jeden Fall mehrere Behauptungen zu einem einzigen Test zusammen. Hier ist ein Test, den ich für einen Notfallbenachrichtigungscode verwende. Der Code, der vor dem Test ausgeführt wird, versetzt das System in einen Zustand, in dem ein Alarm gesendet wird, wenn der Hauptprozessor ausgeführt wird.
Es stellt die Bedingungen dar, die in jedem Schritt des Prozesses vorhanden sein müssen, damit ich sicher sein kann, dass sich der Code wie erwartet verhält. Wenn eine einzelne Behauptung fehlschlägt, ist es mir egal, dass die verbleibenden nicht einmal ausgeführt werden. Da der Status des Systems nicht mehr gültig ist, sagen mir diese nachfolgenden Behauptungen nichts Wertvolles. * Wenn dies
assertAllUnitsAlerting()
fehlschlägt, weiß ich nicht, was ich von seinemassertAllNotificationSent()
Erfolg oder Misserfolg halten soll, bis ich herausgefunden habe, was den vorherigen Fehler verursacht hat und korrigiert es.(* - Okay, möglicherweise sind sie beim Debuggen des Problems hilfreich. Die wichtigsten Informationen, dass der Test fehlgeschlagen ist, sind jedoch bereits eingegangen.)
quelle
Ein weiterer Grund, warum ich denke, dass mehrere Asserts in einer Methode keine schlechte Sache sind, ist im folgenden Code beschrieben:
In meinem Test möchte ich einfach testen, dass
service.process()
inInner
Klasseninstanzen die richtige Anzahl zurückgegeben wird .Anstatt zu testen ...
Ich mache
quelle
Assert.notNull
s sind redundant, Ihr Test schlägt mit einem NPE fehl, wenn sie null sind.if
) wird passieren , wennres
istnull
Assert.assertEquals(..., service.process().getInner());
, der mit extrahierten Variablen möglich ist, wenn die Zeile "zu lang" wirdIch denke, es gibt viele Fälle, in denen das Schreiben mehrerer Behauptungen innerhalb der Regel gültig ist, dass ein Test nur aus einem Grund fehlschlagen sollte.
Stellen Sie sich zum Beispiel eine Funktion vor, die eine Datumszeichenfolge analysiert:
Wenn der Test aus einem Grund fehlschlägt, ist die Analyse falsch. Wenn Sie argumentieren würden, dass dieser Test aus drei verschiedenen Gründen fehlschlagen kann, wären Sie meiner Meinung nach in Ihrer Definition von "einem Grund" zu feinkörnig.
quelle
Ich kenne keine Situation, in der es eine gute Idee wäre, mehrere Behauptungen innerhalb der [Test] -Methode selbst zu haben. Der Hauptgrund, warum Leute gerne mehrere Assertions haben, ist, dass sie versuchen, für jede getestete Klasse eine [TestFixture] -Klasse zu haben. Stattdessen können Sie Ihre Tests in weitere [TestFixture] -Klassen aufteilen. Auf diese Weise können Sie mehrere Methoden anzeigen, bei denen der Code möglicherweise nicht wie erwartet reagiert hat, anstatt nur die, bei der die erste Zusicherung fehlgeschlagen ist. Auf diese Weise haben Sie mindestens ein Verzeichnis pro Klasse, das mit vielen [TestFixture] -Klassen getestet wird. Jede [TestFixture] -Klasse wird nach dem spezifischen Status eines zu testenden Objekts benannt. Die Methode [SetUp] versetzt das Objekt in den durch den Klassennamen beschriebenen Zustand. Dann haben Sie mehrere [Test] -Methoden, von denen jede verschiedene Dinge bestätigt, von denen Sie erwarten, dass sie wahr sind, wenn der aktuelle Status des Objekts gegeben ist. Jede [Test] -Methode ist nach dem, was sie behauptet, benannt, es sei denn, sie ist möglicherweise nach dem Konzept benannt, anstatt nur den Code auf Englisch auszulesen. Dann benötigt jede Implementierung der [Test] -Methode nur eine einzige Codezeile, in der etwas behauptet wird. Ein weiterer Vorteil dieses Ansatzes besteht darin, dass die Tests sehr gut lesbar sind, da deutlich wird, was Sie testen und was Sie erwarten, wenn Sie sich nur die Klassen- und Methodennamen ansehen. Dies wird auch besser skaliert, wenn Sie anfangen, all die kleinen Randfälle zu erkennen, die Sie testen möchten, und wenn Sie Fehler finden. außer vielleicht könnte es nach dem Konzept benannt werden, anstatt nur einen englischen Code auszulesen. Dann benötigt jede Implementierung der [Test] -Methode nur eine einzige Codezeile, in der etwas behauptet wird. Ein weiterer Vorteil dieses Ansatzes besteht darin, dass die Tests sehr gut lesbar sind, da deutlich wird, was Sie testen und was Sie erwarten, wenn Sie sich nur die Klassen- und Methodennamen ansehen. Dies wird auch besser skaliert, wenn Sie anfangen, all die kleinen Randfälle zu erkennen, die Sie testen möchten, und wenn Sie Fehler finden. außer vielleicht könnte es nach dem Konzept benannt werden, anstatt nur einen englischen Code auszulesen. Dann benötigt jede Implementierung der [Test] -Methode nur eine einzige Codezeile, in der etwas behauptet wird. Ein weiterer Vorteil dieses Ansatzes besteht darin, dass die Tests sehr gut lesbar sind, da deutlich wird, was Sie testen und was Sie erwarten, wenn Sie sich nur die Klassen- und Methodennamen ansehen. Dies wird auch besser skaliert, wenn Sie anfangen, all die kleinen Randfälle zu erkennen, die Sie testen möchten, und wenn Sie Fehler finden. und was Sie erwarten, wenn Sie sich die Klassen- und Methodennamen ansehen. Dies wird auch besser skaliert, wenn Sie anfangen, all die kleinen Randfälle zu erkennen, die Sie testen möchten, und wenn Sie Fehler finden. und was Sie erwarten, wenn Sie sich die Klassen- und Methodennamen ansehen. Dies wird auch besser skaliert, wenn Sie anfangen, all die kleinen Randfälle zu erkennen, die Sie testen möchten, und wenn Sie Fehler finden.
Normalerweise bedeutet dies, dass die letzte Codezeile in der [SetUp] -Methode einen Eigenschaftswert oder einen Rückgabewert in einer privaten Instanzvariablen der [TestFixture] speichern sollte. Dann können Sie verschiedene Dinge über diese Instanzvariable aus verschiedenen [Test] -Methoden bestätigen. Sie können auch Aussagen darüber treffen, welche unterschiedlichen Eigenschaften des zu testenden Objekts auf den gewünschten Zustand eingestellt sind.
Manchmal müssen Sie während des Versuchs, das zu testende Objekt in den gewünschten Zustand zu versetzen, Behauptungen aufstellen, um sicherzustellen, dass Sie nichts falsch gemacht haben, bevor Sie das Objekt in den gewünschten Zustand versetzt haben. In diesem Fall sollten diese zusätzlichen Zusicherungen in der [SetUp] -Methode erscheinen. Wenn in der [SetUp] -Methode etwas schief geht, ist klar, dass mit dem Test etwas nicht stimmte, bevor das Objekt in den gewünschten Zustand kam, den Sie testen wollten.
Ein weiteres Problem ist, dass Sie möglicherweise eine Ausnahmebedingung testen, deren Auslösung Sie erwartet haben. Dies kann Sie dazu verleiten, dem obigen Modell nicht zu folgen. Dies kann jedoch trotzdem erreicht werden, indem die Ausnahme in der [SetUp] -Methode abgefangen und in einer Instanzvariablen gespeichert wird. Auf diese Weise können Sie verschiedene Dinge über die Ausnahme behaupten, und zwar jeweils mit einer eigenen [Test] -Methode. Sie können dann auch andere Dinge über das zu prüfende Objekt behaupten, um sicherzustellen, dass die Ausnahmebedingung keine unbeabsichtigten Nebenwirkungen hat.
Beispiel (dies würde in mehrere Dateien aufgeteilt):
quelle
[Test]
Methode wird diese Klasse erneut instanziiert und die[SetUp]
Methode erneut ausgeführt. Dadurch wird der .NET-Garbage-Collector beendet und die Tests werden extrem langsam ausgeführt: mehr als 5 Minuten lokal, mehr als 20 Minuten auf dem Buildserver. 20K-Tests sollten in ca. 2-3 Minuten durchgeführt werden. Ich würde diesen Teststil überhaupt nicht empfehlen, insbesondere für eine große Testsuite.Es ist nur dann ein Problem, wenn der Test fehlschlägt, wenn mehrere Behauptungen im selben Test enthalten sind. Dann müssen Sie möglicherweise den Test debuggen oder die Ausnahme analysieren, um herauszufinden, welche Behauptung fehlschlägt. Mit einer Aussage in jedem Test ist es normalerweise einfacher festzustellen, was falsch ist.
Ich kann mir kein Szenario vorstellen, in dem mehrere Zusicherungen wirklich benötigt werden , da Sie sie immer als mehrere Bedingungen in derselben Zusicherung umschreiben können. Es kann jedoch vorzuziehen sein, wenn Sie beispielsweise mehrere Schritte ausführen, um die Zwischendaten zwischen den Schritten zu überprüfen, anstatt zu riskieren, dass die späteren Schritte aufgrund einer schlechten Eingabe abstürzen.
quelle
Wenn Ihr Test fehlschlägt, wissen Sie nicht, ob die folgenden Aussagen ebenfalls stimmen. Oft bedeutet dies, dass Sie wertvolle Informationen verpassen, um die Ursache des Problems herauszufinden. Meine Lösung besteht darin, eine Zusicherung mit mehreren Werten zu verwenden:
Dadurch kann ich alle fehlgeschlagenen Behauptungen auf einmal sehen. Ich verwende mehrere Zeilen, da die meisten IDEs in einem Vergleichsdialog nebeneinander Zeichenfolgenunterschiede anzeigen.
quelle
Wenn Sie mehrere Asserts in einer einzelnen Testfunktion haben, sind diese meiner Meinung nach direkt für den Test relevant, den Sie durchführen. Zum Beispiel,
Es ist keine schlechte Sache, viele Tests zu machen (auch wenn Sie das Gefühl haben, dass es wahrscheinlich ein Overkill ist). Sie können argumentieren, dass es wichtiger ist, über die wichtigsten und wichtigsten Tests zu verfügen. Wenn Sie also eine Behauptung erstellen, stellen Sie sicher, dass Ihre Behauptungsanweisungen korrekt platziert sind, anstatt sich über mehrere Behauptungen Gedanken zu machen. Wenn Sie mehr als einen benötigen, verwenden Sie mehr als einen.
quelle
Das Ziel des Komponententests ist es, Ihnen so viele Informationen wie möglich über die Fehler zu geben, aber auch zu helfen, die grundlegendsten Probleme zuerst genau zu lokalisieren. Wenn Sie logisch wissen, dass eine Zusicherung fehlschlägt, wenn eine andere Zusicherung fehlschlägt oder mit anderen Worten eine Abhängigkeitsbeziehung zwischen dem Test besteht, ist es sinnvoll, diese als mehrere Zusicherungen in einem einzigen Test zu rollen. Dies hat den Vorteil, dass die Testergebnisse nicht mit offensichtlichen Fehlern verunreinigt werden, die hätten beseitigt werden können, wenn wir die erste Behauptung innerhalb eines einzelnen Tests aufgehoben hätten. In dem Fall, in dem diese Beziehung nicht existiert, wäre es natürlich vorzuziehen, diese Behauptungen in einzelne Tests zu unterteilen, da das Auffinden dieser Fehler andernfalls mehrere Iterationen von Testläufen erfordern würde, um alle Probleme zu lösen.
Wenn Sie dann auch die Einheiten / Klassen so entwerfen, dass übermäßig komplexe Tests geschrieben werden müssten, wird dies beim Testen weniger belastet und möglicherweise ein besseres Design gefördert.
quelle
Ja, es ist in Ordnung, mehrere Behauptungen zu haben, solange ein fehlgeschlagener Test genügend Informationen liefert, um den Fehler diagnostizieren zu können. Dies hängt davon ab, was Sie testen und welche Fehlermodi vorliegen.
Ich habe solche Formulierungen nie als hilfreich empfunden (dass eine Klasse einen Grund haben sollte, sich zu ändern, ist ein Beispiel für solch ein nicht hilfreiches Sprichwort). Angenommen, zwei Zeichenfolgen sind gleich, entspricht dies semantisch der Behauptung, dass die Länge der beiden Zeichenfolgen gleich ist und jedes Zeichen am entsprechenden Index gleich ist.
Wir könnten verallgemeinern und sagen, dass jedes System aus mehreren Behauptungen als eine einzige Behauptung umgeschrieben und jede einzelne Behauptung in eine Menge kleinerer Behauptungen zerlegt werden könnte.
Konzentrieren Sie sich also nur auf die Klarheit des Codes und die Klarheit der Testergebnisse und lassen Sie sich von der Anzahl der von Ihnen verwendeten Behauptungen leiten, anstatt umgekehrt.
quelle
Die Antwort ist sehr einfach: Wenn Sie eine Funktion testen, die mehr als ein Attribut desselben Objekts oder sogar zwei verschiedene Objekte ändert, und die Richtigkeit der Funktion von den Ergebnissen all dieser Änderungen abhängt, möchten Sie dies bestätigen dass jede dieser Änderungen ordnungsgemäß durchgeführt wurde!
Ich habe die Idee eines logischen Konzepts, aber die umgekehrte Schlussfolgerung besagt, dass keine Funktion mehr als ein Objekt verändern darf. Aber das ist meiner Erfahrung nach unmöglich umzusetzen.
Nehmen Sie das logische Konzept einer Banküberweisung - das Abheben eines Betrags von einem Bankkonto MUSS in den meisten Fällen das Hinzufügen dieses Betrags zu einem anderen Konto umfassen. Sie wollen diese beiden Dinge NIEMALS trennen, sie bilden eine atomare Einheit. Vielleicht möchten Sie zwei Funktionen ausführen (Geld abheben / hinzufügen) und damit zwei verschiedene Unit-Tests schreiben - zusätzlich. Diese beiden Aktionen müssen jedoch innerhalb einer Transaktion stattfinden, und Sie möchten auch sicherstellen, dass die Transaktion funktioniert. In diesem Fall reicht es einfach nicht aus, den Erfolg der einzelnen Schritte zu überprüfen. Sie müssen beide Bankkonten in Ihrem Test überprüfen.
Möglicherweise gibt es komplexere Beispiele, die Sie nicht in einem Komponententest, sondern in einem Integrations- oder Abnahmetest testen würden. Aber diese Grenzen sind fließend, IMHO! Es ist nicht so einfach zu entscheiden, es ist eine Frage der Umstände und vielleicht der persönlichen Vorlieben. Das Abheben von Geld von einem Konto und das Aufladen auf ein anderes Konto ist immer noch eine sehr einfache Funktion und definitiv ein Kandidat für Unit-Tests.
quelle
Diese Frage steht im Zusammenhang mit dem klassischen Problem des Ausgleichs zwischen Problemen mit Spaghetti und Lasagne-Code.
Wenn Sie mehrere Asserts haben, können Sie leicht das Spaghetti-Problem lösen, bei dem Sie keine Ahnung haben, worum es beim Test geht. Wenn Sie jedoch nur einen Assert pro Test haben, können Sie Ihre Tests gleichermaßen unlesbar machen, wenn Sie mehrere Tests in einer großen Lasagne durchführen, um herauszufinden, welcher Test was unmöglich macht .
Es gibt einige Ausnahmen, aber in diesem Fall ist es die Antwort, das Pendel in der Mitte zu halten.
quelle
Ich bin überhaupt nicht mit dem "nur aus einem Grund scheitern" einverstanden. Was wichtiger ist, ist, dass die Tests kurz sind und klar imo lesen.
Dies ist jedoch nicht immer möglich, und wenn ein Test kompliziert ist, ist es sinnvoller, einen (langen) beschreibenden Namen und weniger Dinge zu testen.
quelle