Was sollten Sie mit Unit-Tests testen?

122

Ich habe gerade mein College beendet und fange nächste Woche irgendwo an zu studieren. Wir haben Unit-Tests gesehen, aber wir haben sie nicht oft benutzt. und alle reden über sie, also dachte ich mir, ich sollte vielleicht welche machen.

Das Problem ist, ich weiß nicht, was ich testen soll. Soll ich den allgemeinen Fall testen? Der Randfall? Woher weiß ich, dass eine Funktion ausreichend abgedeckt ist?

Ich habe immer das schreckliche Gefühl, dass ein Test zwar beweisen wird, dass eine Funktion für einen bestimmten Fall funktioniert, es aber völlig nutzlos ist, zu beweisen, dass die Funktion funktioniert, Punkt.

zneak
quelle
Schauen Sie sich Roy Osheroves Blog an . Dort finden Sie zahlreiche Informationen zu Unit-Tests, einschließlich Videos. Er hat auch ein Buch geschrieben, "The art of Unit Testing", das sehr gut ist.
Piers Myers
9
Ich frage mich, was denkst du nach fast 5 Jahren darüber? Weil ich immer mehr der Meinung bin, dass die Leute heutzutage besser wissen sollten, "was man nicht in einer Einheit testen soll". Verhaltensorientierte Entwicklung hat sich aus den Fragen entwickelt, die Sie gestellt haben.
Remigijus Pankevičius

Antworten:

121

Meine persönliche Philosophie war bisher:

  1. Testen Sie den allgemeinen Fall von allem, was Sie können. Hier erfahren Sie, wann dieser Code nach einer Änderung abbricht (was meiner Meinung nach der größte Vorteil automatisierter Komponententests ist).
  2. Testen Sie die Randfälle einiger ungewöhnlich komplexer Codes, von denen Sie annehmen, dass sie wahrscheinlich Fehler enthalten.
  3. Wenn Sie einen Fehler finden, schreiben Sie einen Testfall, um ihn abzudecken, bevor Sie ihn beheben
  4. Fügen Sie Edge-Case-Tests zu weniger kritischem Code hinzu, wenn jemand Zeit zum Töten hat.
Fischtoaster
quelle
1
Vielen Dank dafür, ich war hier mit den gleichen Fragen wie der OP zappelig.
Stephen
5
+1, obwohl ich auch die Edge-Cases aller Funktionen vom Typ Bibliothek / Dienstprogramm testen würde, um sicherzustellen, dass Sie eine logische API haben. zB was passiert, wenn eine Null übergeben wird? was ist mit leerer Eingabe? Auf diese Weise stellen Sie sicher, dass Ihr Entwurf logisch ist, und dokumentieren das Verhalten des Eckfalles.
Mikera
7
# 3 scheint eine sehr solide Antwort zu sein, da es sich um ein Beispiel aus der Praxis handelt, wie ein Komponententest hätte helfen können. Wenn es einmal kaputt gegangen ist, kann es wieder kaputt gehen.
Ryan Griffith
Ich habe gerade erst angefangen und finde, dass ich mit der Entwicklung von Tests nicht sehr kreativ bin. Daher benutze ich sie als Nummer 3 oben, um sicherzugehen, dass diese Bugs nie wieder unentdeckt bleiben.
Ankush981
Ihre Antwort wurde in diesem beliebten Medienartikel
vorgestellt
67

Unter der Fülle von Antworten hat bisher niemand die Äquivalenzaufteilung und die Grenzwertanalyse angesprochen , wichtige Überlegungen bei der Beantwortung der vorliegenden Frage. Alle anderen Antworten sind zwar nützlich, aber qualitativ, aber es ist möglich - und vorzuziehen -, hier quantitativ zu sein. @fishtoaster enthält einige konkrete Richtlinien, die sich nur mit der Testquantifizierung befassen. Mit der Äquivalenzpartitionierung und der Grenzwertanalyse können wir jedoch bessere Ergebnisse erzielen.

Bei der Äquivalenzpartitionierung teilen Sie die Menge aller möglichen Eingaben basierend auf den erwarteten Ergebnissen in Gruppen auf. Jede Eingabe von einer Gruppe führt zu äquivalenten Ergebnissen. Daher werden solche Gruppen Äquivalenzklassen genannt . (Beachten Sie, dass äquivalente Ergebnisse nicht identische Ergebnisse bedeuten.)

Betrachten Sie als einfaches Beispiel ein Programm, das ASCII-Kleinbuchstaben in Großbuchstaben umwandeln soll. Andere Charaktere sollten einer Identitätsumwandlung unterzogen werden, dh unverändert bleiben. Hier ist eine mögliche Unterteilung in Äquivalenzklassen:

| # |  Equivalence class    | Input        | Output       | # test cases |
+------------------------------------------------------------------------+
| 1 | Lowercase letter      | a - z        | A - Z        | 26           |
| 2 | Uppercase letter      | A - Z        | A - Z        | 26           |
| 3 | Non-alphabetic chars  | 0-9!@#,/"... | 0-9!@#,/"... | 42           |
| 4 | Non-printable chars   | ^C,^S,TAB... | ^C,^S,TAB... | 34           |

In der letzten Spalte wird die Anzahl der Testfälle angegeben, wenn Sie alle auflisten. Technisch gesehen würden Sie nach @ fishtoasters Regel 1 52 Testfälle einschließen - alle diese für die ersten beiden oben angegebenen Zeilen fallen unter den "allgemeinen Fall". @ fishtoasters Regel 2 würde auch einige oder alle der obigen Zeilen 3 und 4 hinzufügen. Beim Testen der Äquivalenzpartitionierung ist jedoch ein Testfall in jeder Äquivalenzklasse ausreichend. Wenn Sie "a" oder "g" oder "w" auswählen, testen Sie denselben Codepfad. Sie haben also insgesamt 4 Testfälle statt mehr als 52.

Die Grenzwertanalyse empfiehlt eine geringfügige Verfeinerung: Im Wesentlichen deutet dies darauf hin, dass nicht jedes Mitglied einer Äquivalenzklasse äquivalent ist. Das heißt, Werte an Grenzen sollten auch für sich genommen als eines Testfalls würdig angesehen werden. (Eine einfache Rechtfertigung dafür ist der berüchtigte Fehler , bei dem es sich um einen Fehler handelt !) Sie könnten also für jede Äquivalenzklasse 3 Testeingaben haben. Wenn ich mir die obige Eingabedomäne anschaue - und die ASCII-Werte einigermaßen kenne -, fallen mir möglicherweise folgende Testfall-Eingaben ein:

| # | Input                | # test cases |
| 1 | a, w, z              | 3            |
| 2 | A, E, Z              | 3            |
| 3 | 0, 5, 9, !, @, *, ~  | 7            |
| 4 | nul, esc, space, del | 4            |

(Sobald Sie mehr als 3 Grenzwerte erhalten, die darauf hindeuten, dass Sie möglicherweise Ihre ursprünglichen Äquivalenzklassen-Abgrenzungen überdenken möchten, war dies jedoch so einfach, dass ich nicht zurückgegangen bin, um sie zu überarbeiten.) Daher bringt uns die Grenzwertanalyse auf den Punkt 17 Testfälle - mit einem hohen Vertrauen in die vollständige Abdeckung - verglichen mit 128 Testfällen, um umfassende Tests durchzuführen. (Ganz zu schweigen davon, dass die Kombinatorik vorschreibt, dass umfassende Tests für jede reale Anwendung einfach nicht durchführbar sind!)

Michael Sorens
quelle
3
+1 Genau so schreibe ich intuitiv meine Tests. Jetzt kann ich einen Namen darauf setzen :) Danke, dass du das geteilt hast.
Guillaume31
+1 für "qualitative Antworten sind nützlich, aber es ist möglich - und vorzuziehen - quantitativ zu sein"
Jimmy Breck-McKye
Ich denke, dies ist eine gute Antwort, wenn die Richtlinie lautet: "Wie kann ich mit meinen Tests eine gute Abdeckung erzielen?". Ich denke, es wäre nützlich, darüber hinaus einen pragmatischen Ansatz zu finden - ist das Ziel, dass jeder Zweig jeder Logik in jeder Schicht auf diese Weise gründlich getestet wird?
Kieren Johnstone
18

Wahrscheinlich ist meine Meinung nicht allzu beliebt. Aber ich schlage vor, dass Sie mit Unit-Tests sparsam umgehen. Wenn Sie zu viele Komponententests haben, verbringen Sie die Hälfte Ihrer Zeit oder mehr damit, Tests zu verwalten, anstatt die eigentliche Codierung vorzunehmen.

Ich empfehle Ihnen, Tests für Dinge zu schreiben, bei denen Sie ein schlechtes Gefühl im Bauch haben oder für Dinge, die sehr wichtig und / oder elementar sind. IMHO-Komponententests sind kein Ersatz für gutes Engineering und defensive Codierung. Derzeit arbeite ich an einem Projekt, das mehr oder weniger unbrauchbar ist. Es ist wirklich stabil, aber ein Schmerz für die Umgestaltung. Tatsächlich hat niemand diesen Code in einem Jahr berührt und der Software-Stack, auf dem er basiert, ist 4 Jahre alt. Warum? Weil es voller Unit-Tests ist, um genau zu sein: Unit-Tests und automatisierte Integrationstests. (Schon mal was von Gurken gehört?) Und hier ist das Beste: Diese (noch) nicht verwendbare Software wurde von einem Unternehmen entwickelt, dessen Mitarbeiter Vorreiter in der testgetriebenen Entwicklungsszene sind. : D

Also mein Vorschlag ist:

  • Beginnen Sie mit dem Schreiben von Tests, nachdem Sie das Grundgerüst entwickelt haben. Andernfalls kann Refactoring schmerzhaft sein. Als Entwickler, der für andere entwickelt, bekommt man die Anforderungen nie gleich zu Beginn.

  • Stellen Sie sicher, dass Ihre Unit-Tests schnell durchgeführt werden können. Wenn Sie Integrationstests (wie Gurken) haben, ist es in Ordnung, wenn sie etwas länger dauern. Aber Langzeittests machen keinen Spaß, glauben Sie mir. (Die Leute vergessen alle Gründe, warum C ++ weniger populär geworden ist ...)

  • Überlassen Sie dieses TDD-Zeug den TDD-Experten.

  • Und ja, manchmal konzentrieren Sie sich auf Randfälle, manchmal auf die häufigsten Fälle, je nachdem, wo Sie das Unerwartete erwarten. Wenn Sie jedoch immer das Unerwartete erwarten, sollten Sie Ihren Workflow und Ihre Disziplin wirklich überdenken. ;-)

Philip
quelle
2
Können Sie näher erläutern, warum die Tests diese Software zu einem schmerzhaften Umgestalter machen?
Mike Partridge
6
Groß +1. Bei Unit-Tests, bei denen die Implementierung anstelle der Regeln getestet wird, sind 2-3-mal so viele Änderungen erforderlich
TheLQ
9
Wie schlecht geschriebener Produktionscode sind schlecht geschriebene Komponententests schwer zu warten. "Zu viele Unit-Tests" klingt wie ein Fehler, trocken zu bleiben; Jeder Test sollte sich mit einem bestimmten Teil des Systems befassen.
Allan
1
Jeder Komponententest sollte eines überprüfen, damit es nicht zu viele Komponententests gibt, sondern fehlende Tests. Wenn Ihre Komponententests komplex sind, ist das ein weiteres Problem.
Verkehr
1
-1: Ich denke, dieser Beitrag ist schlecht geschrieben. Es gibt mehrere Dinge, die erwähnt werden, und ich weiß nicht, wie sie alle zusammenhängen. Wenn der Punkt der Antwort "sparsam sein" lautet, wie hängt Ihr Beispiel dann überhaupt zusammen? Es klingt so, als ob Ihre Beispielsituation (obwohl real) schlechte Komponententests hat. Erklären Sie mir bitte, welche Lehren ich daraus ziehen soll und wie ich damit sparsam umgehen kann. Außerdem weiß ich ehrlich gesagt einfach nicht, was Sie meinen, wenn Sie sagen Leave this TDD stuff to the TDD-experts.
Alexander Bird
8

Wenn Sie zuerst mit Test Driven Development testen, wird Ihre Abdeckung im Bereich von 90% oder höher liegen, da Sie keine Funktionalität hinzufügen werden, ohne zuerst einen fehlgeschlagenen Komponententest dafür zu schreiben.

Wenn Sie später Tests hinzufügen, kann ich nicht genug empfehlen, dass Sie eine Kopie von Working Effectively With Legacy Code von Michael Feathers erhalten und sich einige der Techniken ansehen, mit denen Sie Ihrem Code Tests hinzufügen und Ihren Code umgestalten können um es testbarer zu machen.

Paddyslacker
quelle
Wie berechnen Sie diesen Deckungsgrad? Was bedeutet es überhaupt, 90% Ihres Codes abzudecken?
Zneak
2
@zneak: Es gibt Tools für die Codeabdeckung, die sie für Sie berechnen. Eine schnelle Google-Suche nach "Code Coverage" sollte eine Reihe von ihnen bringen. Das Tool verfolgt die Codezeilen, die während der Ausführung der Tests ausgeführt werden, und berechnet anhand der Gesamtzahl der Codezeilen in den Assemblys den Erfassungsprozentsatz.
Steven Evers
-1. Beantwortet keine Frage:The problem is, I don't know _what_ to test
Alexander Bird
6

Wenn Sie beginnen folgende Test Driven Development Praktiken, werden sie sortieren führen Sie durch den Prozess und zu wissen , was natürlich kommt zu testen. Einige Orte zum Starten:

Tests kommen zuerst

Schreiben Sie niemals Code, bevor Sie die Tests schreiben. Eine Erklärung finden Sie unter Rot-Grün-Refaktor-Wiederholung .

Schreiben Sie Regressionstests

Wenn Sie auf einen Fehler stoßen, schreiben Sie einen Testfall und vergewissern Sie sich, dass er fehlschlägt . Wenn Sie einen Fehler nicht durch einen fehlerhaften Testfall reproduzieren können, haben Sie ihn nicht wirklich gefunden.

Rot-Grün-Refaktor-Repeat

Rot : Schreiben Sie zunächst einen einfachen Test für das Verhalten, das Sie implementieren möchten. Stellen Sie sich diesen Schritt so vor, dass Sie einen Beispielcode schreiben, der die Klasse oder Funktion verwendet, an der Sie arbeiten. Stellen Sie sicher, dass es kompiliert / keine Syntaxfehler aufweist und dass es fehlschlägt . Dies sollte offensichtlich sein: Sie haben keinen Code geschrieben, also muss er fehlschlagen, oder? Das Wichtigste, was Sie hier lernen müssen, ist, dass Sie niemals sicher sein können, dass der Test, wenn er nicht bestanden wird, aufgrund einer falschen Begründung durchgeführt wird.

Grün : Schreiben Sie den einfachsten und dümmsten Code, der den Test tatsächlich besteht. Versuche nicht schlau zu sein. Selbst wenn Sie feststellen, dass es einen offensichtlichen Edge-Fall gibt, der Test dies jedoch berücksichtigt, schreiben Sie keinen Code, um damit umzugehen (aber vergessen Sie den Edge-Fall nicht: Sie werden ihn später benötigen). Die Idee ist, dass jeder Code, den Sie schreiben, jeder if, jeder try: ... except: ...durch einen Testfall gerechtfertigt sein sollte. Der Code muss nicht elegant, schnell oder optimiert sein. Sie möchten nur, dass der Test bestanden wird.

Refactor : Bereinigen Sie Ihren Code und geben Sie die richtigen Methodennamen ein. Überprüfen Sie, ob der Test noch besteht. Optimieren. Führen Sie den Test erneut aus.

Wiederholen : Sie erinnern sich an den Randfall, den der Test nicht behandelt hat, oder? Nun ist es also ein großer Moment. Schreiben Sie einen Testfall, der diese Situation abdeckt, beobachten Sie, wie er fehlschlägt, schreiben Sie Code, sehen Sie, wie er erfolgreich ist, und überarbeiten Sie ihn.

Testen Sie Ihren Code

Sie arbeiten an einem bestimmten Code, und genau das möchten Sie testen. Dies bedeutet, dass Sie keine Bibliotheksfunktionen, die Standardbibliothek oder Ihren Compiler testen sollten. Versuchen Sie auch zu vermeiden, die "Welt" zu testen. Dies beinhaltet: Aufrufen von externen Web-APIs, datenbankintensives Material usw. Wann immer Sie versuchen können, ein Modell zu erstellen (erstellen Sie ein Objekt, das der gleichen Schnittstelle folgt, jedoch statische, vordefinierte Daten zurückgibt).

Ryszard Szopa
quelle
1
Angenommen, ich habe bereits eine bestehende und (soweit ich sehen kann) funktionierende Codebasis, was mache ich dann?
Zneak
Das kann etwas schwieriger sein (je nachdem, wie der Code geschrieben ist). Beginnen Sie mit Regressionstests (sie sind immer sinnvoll), und versuchen Sie dann, Komponententests zu schreiben, um sich selbst zu beweisen, dass Sie verstehen, was der Code tut. Es ist leicht, sich von der Menge an Arbeit, die (scheinbar) zu tun ist, überwältigen zu lassen, aber: Einige Tests sind immer besser als gar keine Tests.
Ryszard Szopa
3
-1 Ich denke nicht, dass es eine sehr gute Antwort auf diese Frage ist . Bei der Frage geht es nicht um TDD, sondern darum, was beim Schreiben von Komponententests getestet werden soll. Ich denke, eine gute Antwort auf die eigentliche Frage sollte für eine Nicht-TDD-Methodik gelten.
Bryan Oakley
1
Wenn Sie es berühren, testen Sie es. Und Clean Code (Robert C Martin) schlägt vor, dass Sie "Lerntests" für Code von Drittanbietern schreiben. Auf diese Weise lernen Sie, es zu verwenden, und Sie haben Tests, falls eine neue Version das von Ihnen verwendete Verhalten ändert.
Roger Willcocks
3

Beginnen Sie bei Komponententests mit dem Testen, ob das Gerät das tut, wofür es entwickelt wurde. Das sollte der allererste Fall sein, den Sie schreiben. Wenn ein Teil des Entwurfs lautet "Es sollte eine Ausnahme auslösen, wenn Sie Junk übergeben", testen Sie dies ebenfalls, da dies Teil des Entwurfs ist.

Fang damit an. Wenn Sie mit den Grundlagen des Testens vertraut sind, lernen Sie, ob dies ausreicht, und lernen andere Aspekte Ihres Codes kennen, die getestet werden müssen.

Bryan Oakley
quelle
0

Die Standardantwort lautet "alles testen, was brechen könnte" .

Was ist zu einfach zu brechen? Datenfelder, hirntote Zugriffsmethoden für Objekte und ähnliches. Alles andere implementiert wahrscheinlich einen identifizierbaren Teil einer Anforderung und kann vom Testen profitieren.

Natürlich können Ihre Laufleistung - und die Praktiken Ihres Arbeitsumfelds - variieren.

Jeffrey Hantin
quelle
Okay. Welche Fälle sollte ich testen? Der "normale" Fall? Der Randfall?
Zneak
3
Faustregel? Eins oder zwei genau in der Mitte des goldenen Pfades und nur innerhalb und nur außerhalb von Kanten.
Jeffrey Hantin
@ JeffreyHantin Das ist die "Grenzwertanalyse" in einer anderen Antwort.
Roger Willcocks