Schreiben Sie Unit-Tests manuell anhand eines Beispiels?

9

Wir wissen, dass das Schreiben von JUnit- Tests einen bestimmten Pfad durch Ihren Code zeigt.

Einer meiner Mitarbeiter kommentierte:

Das manuelle Schreiben von Komponententests ist Proof By Example .

Er kam aus dem Hintergrund von Haskell, das Tools wie Quickcheck und die Fähigkeit hat, über das Programmverhalten mit Typen nachzudenken .

Seine Implikation war, dass es viele andere Kombinationen von Eingaben gibt, die von dieser Methode nicht ausprobiert werden und für die Ihr Code nicht getestet wurde.

Meine Frage ist: Schreiben Sie Unit-Tests manuell anhand eines Beispiels?

Falkenauge
quelle
3
Nein, keine Tests schreiben / verwenden . Die Behauptung, dass Ihre Komponententests beweisen, dass mit dem Programm nichts falsch ist, ist Proof by Example (eine unangemessene Verallgemeinerung). Bei Tests geht es nicht darum, die Korrektheit des Codes mathematisch zu beweisen - Tests sind von Natur aus experimentelle Überprüfungen. Es ist ein Sicherheitsnetz, das Ihnen hilft, Vertrauen aufzubauen, indem es Ihnen etwas über den Code erzählt. Aber Sie sind derjenige, der eine gute Strategie wählen muss, um den Code zu prüfen, und Sie sind derjenige, der interpretieren muss, was diese Daten bedeuten.
Filip Milovanović

Antworten:

10

Wenn Sie nach dem Zufallsprinzip Eingaben zum Testen auswählen, ist es möglicherweise möglich, dass Sie einen logischen Proof By Example-Irrtum ausüben.

Aber gute Unit-Tests machen das nie. Stattdessen befassen sie sich mit Bereichen und Randfällen.

Wenn Sie beispielsweise Komponententests für eine Absolutwertfunktion schreiben würden, die eine Ganzzahl als Eingabe akzeptiert, müssten Sie nicht jeden möglichen Eingabewert testen, um zu beweisen, dass der Code funktioniert. Um einen umfassenden Test zu erhalten, benötigen Sie nur fünf Werte: -1, 0, 1 sowie die Max- und Min-Werte für die Eingabe-Ganzzahl.

Diese fünf Werte testen jeden möglichen Bereich und Kantenfall der Funktion. Sie müssen nicht jeden anderen möglichen Eingabewert testen (dh jede Zahl, die der Integer-Typ darstellen kann), um ein hohes Konfidenzniveau zu erhalten, dass die Funktion für alle Eingabewerte funktioniert.

Robert Harvey
quelle
11
Ein Code-Tester betritt eine Bar und bestellt ein Bier. 5 Biere. -1 Biere, MAX_VALUE Biere, ein Huhn. eine Null.
Neil
2
Die "5 Werte" sind purer Unsinn. Betrachten Sie eine triviale Funktion wie int foo(int x) { return 1234/(x - 100); }. Beachten Sie auch, dass Sie (je nachdem, was Sie testen) möglicherweise sicherstellen müssen, dass eine ungültige ("außerhalb des Bereichs") Eingabe korrekte Ergebnisse zurückgibt (z. B. dass "find_thing (thing)" korrekt eine Art "nicht gefunden" -Status zurückgibt wenn das Ding nicht gefunden wurde).
Brendan
3
@Brendan: Es ist nichts Bedeutendes daran, fünf Werte zu haben. In meinem Beispiel sind es nur fünf Werte. Ihr Beispiel hat eine andere Anzahl von Tests, da Sie eine andere Funktion testen. Ich sage nicht, dass jede Funktion genau fünf Tests erfordert; Sie haben das aus dem Lesen meiner Antwort abgeleitet.
Robert Harvey
1
Generative Testbibliotheken können Randfälle normalerweise besser testen als Sie. Wenn zum Beispiel wurden Sie Schwimmer anstelle von ganzen Zahlen verwenden, würde Ihre Bibliothek auch überprüfen -Inf, Inf, NaN, 1e-100, -1e-100, -0, 2e200... Ich würde lieber nicht alle manuell zu tun.
Hovercouch
@Hovercouch: Wenn du einen guten kennst, würde ich gerne davon hören. Das beste, das ich gesehen habe, war Pex; es war jedoch unglaublich instabil. Denken Sie daran, wir sprechen hier von relativ einfachen Funktionen. Es wird schwieriger, wenn Sie sich mit Dingen wie der realen Geschäftslogik befassen.
Robert Harvey
8

Jeder Softwaretest ist wie "Proof By Example", nicht nur Unit-Tests mit einem Tool wie JUnit. Und das ist keine neue Weisheit, es gibt ein Zitat von Dijkstra aus dem Jahr 1960, das im Wesentlichen dasselbe sagt:

"Tests zeigen das Vorhandensein, nicht das Fehlen von Fehlern"

(Ersetzen Sie einfach die Wörter "Shows" durch "Beweise"). Dies gilt jedoch auch für Tools, die zufällige Testdaten generieren. Die Anzahl der möglichen Eingaben für eine reale Funktion ist in der Regel um Größenordnungen größer als die Anzahl der Testfälle, die unabhängig von der Methode zur Erzeugung dieser Fälle erstellt und anhand eines erwarteten Ergebnisses im Zeitalter des Universums überprüft werden können Selbst wenn man ein Generator-Tool zum Erzeugen vieler Testdaten verwendet, gibt es keine Garantie, den einen Testfall nicht zu verpassen, der einen bestimmten Fehler hätte erkennen können.

Zufällige Tests können manchmal einen Fehler aufdecken, der von manuell erstellten Testfällen übersehen wurde. Im Allgemeinen ist es jedoch effizienter, Tests für die zu testende Funktion sorgfältig zu erstellen und sicherzustellen, dass eine vollständige Code- und Zweigabdeckung mit möglichst wenigen Testfällen erhalten wird. Manchmal ist es eine praktikable Strategie, manuell und zufällig generierte Tests zu kombinieren. Darüber hinaus muss bei der Verwendung von Zufallstests darauf geachtet werden, dass die Ergebnisse reproduzierbar sind.

Manuell erstellte Tests sind also keineswegs schlechter als zufällig generierte Tests, oft im Gegenteil.

Doc Brown
quelle
1
Jede praktische Testsuite, die eine Stichprobenprüfung verwendet, verfügt auch über Komponententests. (Technisch gesehen sind Unit-Tests nur ein entarteter Fall von Zufallstests.) Ihre Formulierung legt nahe, dass randomisierte Tests schwer zu erreichen sind oder dass die Kombination von randomisierten Tests und Unit-Tests schwierig ist. Dies ist normalerweise nicht der Fall. Meiner Meinung nach besteht einer der größten Vorteile von randomisierten Tests darin, dass das Schreiben von Tests als Eigenschaften des Codes, die immer gültig sein sollen, nachdrücklich empfohlen wird. Ich möchte diese Eigenschaften lieber explizit angeben (und überprüfen!), Als auf einige Punktetests schließen zu müssen.
Derek Elkins verließ SE
@DerekElkins: "schwierig" ist meiner Meinung nach der falsche Begriff. Zufällige Tests erfordern einige Anstrengungen, und diese Anstrengungen reduzieren die verfügbare Zeit für Handarbeitstests (und wenn Sie Leute haben, die nur Slogans wie dem in der Frage genannten folgen, werden sie wahrscheinlich überhaupt keine Handarbeit machen). Nur eine Menge zufälliger Testdaten auf einen Code zu werfen, ist nur die halbe Arbeit. Man muss auch die erwarteten Ergebnisse für jede dieser Testeingaben erstellen. In einigen Szenarien kann dies automatisch erfolgen. Für andere nicht.
Doc Brown
Während es definitiv Zeiten gibt, in denen einige Überlegungen erforderlich sind, um eine gute Verteilung auszuwählen, ist dies normalerweise kein großes Problem. Ihr Kommentar deutet darauf hin, dass Sie falsch darüber nachdenken. Die Eigenschaften, die Sie für die zufällige Prüfung schreiben, sind die gleichen Eigenschaften, die Sie für die Modellprüfung oder für formale Beweise schreiben würden. In der Tat können und wurden sie gleichzeitig für all diese Dinge verwendet. Es gibt keine "erwarteten Ergebnisse", die Sie ebenfalls erzielen müssen. Stattdessen geben Sie einfach eine Eigenschaft an, die immer gültig sein sollte. Einige Beispiele: 1) etwas auf einen Stapel schieben und ...
Derek Elkins verließ SE
... dann sollte Knallen dasselbe sein wie nichts tun; 2) Jeder Kunde mit einem Kontostand von mehr als 10.000 USD sollte den hohen Kontostand erhalten, und zwar nur dann. 3) Die Position des Sprites befindet sich immer innerhalb des Begrenzungsrahmens des Bildschirms. Einige Eigenschaften können durchaus Punkttests entsprechen, z. B. "Wenn der Kontostand 0 US-Dollar beträgt, wird die Null-Kontostand-Warnung ausgegeben". Die Eigenschaften sind Teilspezifikationen mit dem Ideal, eine Gesamtspezifikation zu erhalten. Wenn Sie Schwierigkeiten haben, sich diese Eigenschaften auszudenken, sind Sie sich nicht sicher, was die Spezifikation ist, und oft haben Sie Schwierigkeiten, sich gute Komponententests auszudenken.
Derek Elkins verließ SE
0

Das manuelle Schreiben von Tests ist "Beweis durch Beispiel". Dies gilt jedoch auch für QuickCheck und in begrenztem Umfang für Typsysteme. Alles, was keine direkte formale Überprüfung ist, wird in den Informationen über Ihren Code eingeschränkt sein. Stattdessen muss man über den relativen Wert von Ansätzen nachdenken.

Generative Tests wie QuickCheck eignen sich sehr gut, um einen großen Bereich von Eingaben zu erfassen. Es ist auch viel besser, um Randfälle anzugehen als manuelle Tests: Generative Testbibliotheken werden damit mehr Erfahrung haben als Sie. Auf der anderen Seite erzählen sie nur von Invarianten, nicht von bestimmten Ausgaben. Um zu validieren, dass Ihr Programm die richtigen Ergebnisse liefert, benötigen Sie noch einige manuelle Tests, um dies tatsächlich zu überprüfen foo(bar) = baz.

Hovercouch
quelle