Neu bei Unit-Tests, wie schreibe ich großartige Tests? [geschlossen]

266

Ich bin ziemlich neu in der Welt der Unit-Tests und habe gerade beschlossen, diese Woche eine Testabdeckung für meine vorhandene App hinzuzufügen.

Dies ist eine große Aufgabe, vor allem wegen der Anzahl der zu testenden Klassen, aber auch, weil das Schreiben von Tests für mich völlig neu ist.

Ich habe bereits Tests für eine Reihe von Klassen geschrieben, aber jetzt frage ich mich, ob ich es richtig mache.

Wenn ich Tests für eine Methode schreibe, habe ich das Gefühl, ein zweites Mal neu zu schreiben, was ich bereits in der Methode selbst geschrieben habe.
Meine Tests scheinen nur so eng an die Methode gebunden zu sein (Testen aller Codepfade, wobei erwartet wird, dass einige innere Methoden mit bestimmten Argumenten mehrmals aufgerufen werden), dass es den Anschein hat, als würden die Tests fehlschlagen, wenn ich die Methode jemals umgestalte, selbst wenn die Das endgültige Verhalten der Methode hat sich nicht geändert.

Dies ist nur ein Gefühl, und wie bereits erwähnt, habe ich keine Erfahrung mit Tests. Wenn einige erfahrene Tester mir Ratschläge geben könnten, wie man großartige Tests für eine vorhandene App schreibt, wäre das sehr dankbar.

Bearbeiten: Ich würde mich gerne bei Stack Overflow bedanken. Ich hatte großartige Eingaben in weniger als 15 Minuten, die mehr von den Stunden des Online-Lesens beantworteten, die ich gerade gemacht habe.

pixelastisch
quelle
1
Dies ist das beste Buch für Unit-Tests: manning.com/osherove. Hier werden die Best Practices, Do's und Dont's für Unit-Tests erläutert.
Ervi B
Eine Sache, die all diese Antworten auslassen, ist, dass Unit-Tests wie Dokumentation sind. Ergo, wenn Sie eine Funktion schreiben, würden Sie ihre Absicht dokumentieren, indem Sie ihre Ein- und Ausgänge (und möglicherweise Nebenwirkungen) beschreiben. Ein Unit-Test soll dies dann überprüfen. Und wenn Sie (oder jemand anderes) später Änderungen am Code vornehmen, sollten die Dokumente die Grenzen der Änderungen erläutern, und die Komponententests stellen sicher, dass die Grenzen beibehalten werden.
Thomas Tempelmann

Antworten:

187

Meine Tests scheinen nur so eng an die Methode gebunden zu sein (Testen aller Codepfade, wobei erwartet wird, dass einige innere Methoden mit bestimmten Argumenten mehrmals aufgerufen werden), dass es den Anschein hat, als würden die Tests fehlschlagen, wenn ich die Methode jemals umgestalte, selbst wenn die Das endgültige Verhalten der Methode hat sich nicht geändert.

Ich denke du machst es falsch.

Ein Unit-Test sollte:

  • Testen Sie eine Methode
  • Geben Sie einige spezifische Argumente für diese Methode an
  • Testen Sie, ob das Ergebnis wie erwartet ist

Es sollte nicht in die Methode schauen, um zu sehen, was sie tut, daher sollte das Ändern der Interna nicht dazu führen, dass der Test fehlschlägt. Sie sollten nicht direkt testen, ob private Methoden aufgerufen werden. Wenn Sie herausfinden möchten, ob Ihr privater Code getestet wird, verwenden Sie ein Tool zur Codeabdeckung. Aber seien Sie nicht besessen davon: Eine 100% ige Abdeckung ist keine Voraussetzung.

Wenn Ihre Methode öffentliche Methoden in anderen Klassen aufruft und diese Aufrufe von Ihrer Schnittstelle garantiert werden, können Sie mithilfe eines Verspottungsframeworks testen, ob diese Aufrufe ausgeführt werden.

Sie sollten die Methode selbst (oder den von ihr verwendeten internen Code) nicht verwenden, um das erwartete Ergebnis dynamisch zu generieren. Das erwartete Ergebnis sollte in Ihrem Testfall fest codiert sein, damit es sich nicht ändert, wenn sich die Implementierung ändert. Hier ist ein vereinfachtes Beispiel dafür, was ein Komponententest tun sollte:

testAdd()
{
    int x = 5;
    int y = -2;
    int expectedResult = 3;
    Calculator calculator = new Calculator();
    int actualResult = calculator.Add(x, y);
    Assert.AreEqual(expectedResult, actualResult);
}

Beachten Sie, dass die Berechnung des Ergebnisses nicht überprüft wird - nur, dass das Ergebnis korrekt ist. Fügen Sie immer einfachere Testfälle wie die oben genannten hinzu, bis Sie so viele Szenarien wie möglich behandelt haben. Verwenden Sie Ihr Code-Coverage-Tool, um festzustellen, ob Sie interessante Pfade verpasst haben.

Mark Byers
quelle
13
Vielen Dank, Ihre Antwort war umso vollständiger. Ich verstehe jetzt besser, wofür Scheinobjekte wirklich sind: Ich muss nicht jeden Aufruf anderer Methoden geltend machen, sondern nur der relevanten. Ich muss auch nicht wissen, wie Dinge erledigt werden, aber dass sie richtig gemacht werden.
Pixelastic
2
Ich denke respektvoll, dass du es falsch machst. Bei Unit-Tests geht es um den Ablauf der Codeausführung (White-Box-Test). Black-Box-Tests (was Sie vorschlagen) sind normalerweise die Techniken, die beim Funktionstest (System- und Integrationstest) verwendet werden.
Wes
1
"Ein Unit-Test sollte eine Methode testen" Ich bin eigentlich anderer Meinung. Ein Unit-Test sollte ein logisches Konzept testen. Während das oft als eine Methode dargestellt wird, ist das nicht immer der Fall
Robertmain
35

Für Unit-Tests empfand ich sowohl Test Driven (Tests zuerst, Code Second) als auch Code First, Test Second als äußerst nützlich.

Anstatt Code zu schreiben, dann Test schreiben. Schreiben Sie Code und schauen Sie sich an, was Sie denken, dass der Code tun sollte. Denken Sie über alle Verwendungszwecke nach und schreiben Sie dann für jeden einen Test. Ich finde das Schreiben von Tests schneller, aber aufwändiger als das Codieren selbst. Die Tests sollten die Absicht testen. Denken Sie auch an die Absichten, mit denen Sie in der Testschreibphase Eckfälle finden. Und natürlich kann es beim Schreiben von Tests vorkommen, dass eine der wenigen Verwendungszwecke einen Fehler verursacht (etwas, das ich häufig finde, und ich bin sehr froh, dass dieser Fehler keine Daten beschädigt und nicht aktiviert hat).

Testen ist jedoch fast wie zweimaliges Codieren. Tatsächlich hatte ich Anwendungen, bei denen es mehr Testcode (Menge) als Anwendungscode gab. Ein Beispiel war eine sehr komplexe Zustandsmaschine. Ich musste sicherstellen, dass das Ganze nach dem Hinzufügen weiterer Logik immer bei allen vorherigen Anwendungsfällen funktionierte. Und da es ziemlich schwierig war, diese Fälle anhand des Codes zu verfolgen, hatte ich eine so gute Testsuite für diese Maschine, dass ich mir sicher war, dass sie nach Änderungen nicht die Gewinnschwelle erreichen würde, und die Tests haben mir ein paar Mal den Arsch gerettet . Und da Benutzer oder Tester Fehler fanden, bei denen der Flow oder die Eckfälle nicht berücksichtigt wurden, raten Sie mal, fügten Sie Tests hinzu und traten nie wieder auf. Dies gab den Benutzern wirklich Vertrauen in meine Arbeit und machte das Ganze super stabil. Und wenn es aus Performancegründen neu geschrieben werden musste, raten Sie mal,

Alle einfachen Beispiele wie function square(number)sind großartig und alle und sind wahrscheinlich schlechte Kandidaten, um viel Zeit mit Tests zu verbringen. Diejenigen, die wichtige Geschäftslogik betreiben, hier sind die Tests wichtig. Testen Sie die Anforderungen. Testen Sie nicht nur die Rohrleitungen. Wenn sich die Anforderungen ändern, raten Sie mal, was auch die Tests müssen.

Das Testen sollte nicht buchstäblich das dreimalige Aufrufen dieser Funktion sein. Das ist falsch. Überprüfen Sie, ob das Ergebnis und die Nebenwirkungen korrekt sind, nicht die innere Mechanik.

Dmitriy Likhten
quelle
2
Gute Antwort, gab mir das Vertrauen, dass das Schreiben von Tests nach dem Code immer noch nützlich und möglich sein kann.
Pixelastic
2
Ein perfektes aktuelles Beispiel. Ich hatte eine sehr einfache Funktion. Gib es wahr, es macht eine Sache, falsch, es macht eine andere. SEHR EINFACH. Hatte gerne 4 Tests, um sicherzustellen, dass die Funktion das tut, was sie vorhat. Ich ändere das Verhalten ein bisschen. Führen Sie Tests durch, POW ein Problem. Das Lustige ist, dass sich das Problem bei der Verwendung der Anwendung nicht manifestiert, sondern nur in einem komplexen Fall. Der Testfall hat es gefunden und ich habe mir stundenlange Kopfschmerzen erspart.
Dmitriy Likhten
"Die Tests sollten die Absicht testen." Ich denke, das fasst zusammen, dass Sie die beabsichtigten Verwendungen des Codes durchgehen und sicherstellen sollten, dass der Code sie aufnehmen kann. Es weist auch auf den Umfang dessen hin, was der Test tatsächlich testen sollte, und auf die Idee, dass Sie bei einer Codeänderung in dem Moment möglicherweise nicht später überlegen, wie sich diese Änderung auf alle vorgeschriebenen Verwendungen des Codes auswirkt - den Test verteidigt sich gegen eine Änderung, die nicht alle beabsichtigten Anwendungsfälle erfüllt.
Greenstick
18

Es ist erwähnenswert, dass das Nachrüsten von Komponententests in vorhandenen Code weitaus schwieriger ist, als die Erstellung dieses Codes zunächst mit Tests voranzutreiben. Das ist eine der großen Fragen im Umgang mit Legacy-Anwendungen ... wie man Unit-Tests durchführt? Dies wurde schon oft gestellt (so dass Sie möglicherweise als betrogene Frage geschlossen werden), und die Leute landen normalerweise hier:

Verschieben von vorhandenem Code in Test Driven Development

Ich stimme der Buchempfehlung der akzeptierten Antwort zu, aber darüber hinaus sind in den Antworten dort weitere Informationen verlinkt.

David
quelle
3
Wenn Sie zuerst oder zweitens Tests schreiben, ist beides in Ordnung, aber wenn Sie Tests schreiben, stellen Sie sicher, dass Ihr Code testbar ist, damit Sie Tests schreiben können. Sie denken oft: "Wie kann ich das testen?" Oft führt dies dazu, dass besserer Code geschrieben wird. Das Nachrüsten von Testfällen ist immer ein großes Nein-Nein. Sehr schwer. Es ist kein Zeitproblem, es ist ein Mengen- und Testbarkeitsproblem. Ich kann momentan nicht zu meinem Chef kommen und sagen, dass ich Testfälle für unsere über tausend Tabellen und Verwendungen schreiben möchte. Es ist jetzt zu viel, würde ein Jahr dauern, und einige der Logik / Entscheidungen werden vergessen. Also nicht zu lange
aufschieben
2
Vermutlich hat sich die akzeptierte Antwort geändert. Es gibt eine Antwort von Linx, die die Kunst des Unit-Tests von Roy Osherove empfiehlt, manning.com/osherove
thelem
15

Schreiben Sie keine Tests, um eine vollständige Abdeckung Ihres Codes zu erhalten. Schreiben Sie Tests, die Ihre Anforderungen garantieren. Möglicherweise finden Sie unnötige Codepfade. Umgekehrt, wenn sie notwendig sind, sind sie da, um irgendeine Art von Anforderung zu erfüllen; Finden Sie es, was es ist, und testen Sie die Anforderung (nicht den Pfad).

Halten Sie Ihre Tests klein: ein Test pro Anforderung.

Wenn Sie später eine Änderung vornehmen (oder neuen Code schreiben müssen), schreiben Sie zuerst einen Test. Nur einer. Dann haben Sie den ersten Schritt in der testgetriebenen Entwicklung getan.

Jon Reid
quelle
Vielen Dank, es ist sinnvoll, nur kleine Tests für kleine Anforderungen einzeln durchzuführen. Lektion gelernt.
Pixelastic
13

Beim Unit-Test geht es um die Ausgabe, die Sie von einer Funktion / Methode / Anwendung erhalten. Es spielt überhaupt keine Rolle, wie das Ergebnis erzeugt wird, es ist nur wichtig, dass es korrekt ist. Daher ist Ihr Ansatz, Aufrufe an innere Methoden und dergleichen zu zählen, falsch. Ich neige dazu, mich hinzusetzen und zu schreiben, was eine Methode bei bestimmten Eingabewerten oder einer bestimmten Umgebung zurückgeben soll, und dann einen Test zu schreiben, der den tatsächlich zurückgegebenen Wert mit dem vergleicht, was ich mir ausgedacht habe.

Fresskoma
quelle
Vielen Dank ! Ich hatte das Gefühl, dass ich es falsch gemacht habe, aber es ist besser, wenn mir jemand davon erzählt.
Pixelastic
8

Versuchen Sie, einen Komponententest zu schreiben, bevor Sie die zu testende Methode schreiben.

Das wird Sie definitiv dazu zwingen, ein wenig anders darüber nachzudenken, wie die Dinge gemacht werden. Sie haben keine Ahnung, wie die Methode funktionieren wird, nur was sie tun soll.

Sie sollten immer die Ergebnisse der Methode testen und nicht, wie die Methode diese Ergebnisse erhält.

Justin Niessner
quelle
Ja, ich würde das gerne tun können, außer dass die Methoden bereits geschrieben sind. Ich möchte sie nur testen. Ich werde in Zukunft Tests vor Methoden schreiben, tho.
Pixelastic
2
@pixelastic so tun, als wären die Methoden nicht geschrieben worden?
committedandroider
4

Tests sollen die Wartbarkeit verbessern. Wenn Sie eine Methode ändern und ein Test unterbrochen wird, kann dies eine gute Sache sein. Wenn Sie Ihre Methode hingegen als Black Box betrachten, sollte es keine Rolle spielen, was sich in der Methode befindet. Tatsache ist, dass Sie sich für einige Tests lustig machen müssen, und in diesen Fällen können Sie die Methode wirklich nicht als Black Box behandeln. Das einzige, was Sie tun können, ist, einen Integrationstest zu schreiben. Sie laden eine vollständig instanziierte Instanz des zu testenden Dienstes und lassen ihn so arbeiten, wie er in Ihrer App ausgeführt wird. Dann können Sie es als Black Box behandeln.

When I'm writing tests for a method, I have the feeling of rewriting a second time what I          
already wrote in the method itself.
My tests just seems so tightly bound to the method (testing all codepath, expecting some    
inner methods to be called a number of times, with certain arguments), that it seems that
if I ever refactor the method, the tests will fail even if the final behavior of the   
method did not change.

Dies liegt daran, dass Sie Ihre Tests schreiben, nachdem Sie Ihren Code geschrieben haben. Wenn Sie es umgekehrt machen würden (haben Sie die Tests zuerst geschrieben), würde es sich nicht so anfühlen.

hvgotcodes
quelle
Vielen Dank für das Black-Box-Beispiel, ich habe es nicht so gesehen. Ich wünschte, ich hätte Unit-Tests früher entdeckt, aber leider ist das nicht der Fall, und ich habe keine Legacy- App mehr, um Tests hinzuzufügen. Gibt es keine Möglichkeit, einem vorhandenen Projekt Tests hinzuzufügen, ohne dass sie sich kaputt fühlen?
Pixelastic
1
Das Schreiben von Tests danach unterscheidet sich vom Schreiben von Tests zuvor, sodass Sie nicht weiterkommen. Sie können die Tests jedoch so einrichten, dass sie zuerst fehlschlagen, und sie dann bestehen, indem Sie Ihre Klasse testen. Führen Sie so etwas aus, und testen Sie Ihre Instanz, nachdem der Test anfänglich fehlgeschlagen ist. Das Gleiche gilt für Mocks - anfangs hat der Mock keine Erwartungen und schlägt fehl, weil die zu testende Methode etwas mit dem Mock macht und dann den Test besteht. Es würde mich nicht wundern, wenn Sie auf diese Weise viele Fehler finden.
Hvgotcodes
Seien Sie auch sehr genau mit Ihren Erwartungen. Stellen Sie nicht nur sicher, dass der Test ein Objekt zurückgibt, sondern testen Sie, ob das Objekt verschiedene Werte enthält. Testen Sie, ob ein Wert null sein soll oder nicht. Sie können es auch ein wenig auflösen, indem Sie einige Refactoring-Vorgänge durchführen, die Sie beabsichtigt haben, nachdem Sie einige Tests hinzugefügt haben.
Hvgotcodes