Zufällige Daten in Unit Tests?


136

Ich habe einen Kollegen, der Komponententests für Objekte schreibt, die ihre Felder mit zufälligen Daten füllen. Sein Grund ist, dass es einen größeren Testbereich bietet, da es viele verschiedene Werte testet, während ein normaler Test nur einen einzigen statischen Wert verwendet.

Ich habe ihm verschiedene Gründe dafür genannt, die wichtigsten sind:

  • Zufällige Werte bedeuten, dass der Test nicht wirklich wiederholbar ist (was auch bedeutet, dass der Test, wenn er zufällig fehlschlagen kann, dies auf dem Build-Server tun und den Build abbrechen kann).
  • Wenn es sich um einen zufälligen Wert handelt und der Test fehlschlägt, müssen wir a) das Objekt reparieren und b) uns zwingen, jedes Mal auf diesen Wert zu testen, damit wir wissen, dass er funktioniert, aber da er zufällig ist, wissen wir nicht, was der Wert war

Ein anderer Mitarbeiter fügte hinzu:

  • Wenn ich eine Ausnahme teste, stellen zufällige Werte nicht sicher, dass der Test im erwarteten Zustand endet
  • Zufallsdaten werden zum Ausspülen eines Systems und zum Testen der Last verwendet, nicht für Komponententests

Kann jemand andere zusätzliche Gründe hinzufügen, die ich ihm geben kann, damit er damit aufhört?

(Oder ist dies alternativ eine akzeptable Methode zum Schreiben von Komponententests, und ich und mein anderer Mitarbeiter liegen falsch?)


32
"Zufallswerte bedeuten, dass der Test nicht wirklich wiederholbar ist" nicht wahr, da die Tets Pseudozufallszahlen verwenden. Geben Sie den gleichen Startwert an und erhalten Sie die gleiche Folge von "zufälligen" Tests.
Raedwald

11
Anekdote: Ich habe einmal eine CSV-Exportklasse geschrieben, und zufällige Tests haben einen Fehler ergeben, als Steuerzeichen am Ende einer Zelle platziert wurden. Ohne zufällige Tests hätte ich nie gedacht, dies als Testfall hinzuzufügen. Ist es immer gescheitert? Ist es ein perfekter Test? Hat es mir geholfen, einen Fehler zu finden und zu beheben? Ja.
Tyzoid

1
Tests können auch als Dokumentation dienen, um zu erklären, wann der Code als Eingabe erwartet und was als Ausgabe erwartet wird. Ein Test mit eindeutigen Daten kann einfacher und erklärender sein als Code, der zufällige Daten generiert.
Schiene

Wenn Ihr Komponententest aufgrund eines zufällig generierten Werts fehlschlägt und dieser Wert nicht Teil einer Bestätigung ist, wünschen wir Ihnen viel Glück beim Debuggen Ihres Komponententests.
eriksmith200

Antworten:


72

Es gibt einen Kompromiss. Ihr Mitarbeiter ist eigentlich auf etwas, aber ich denke, er macht es falsch. Ich bin mir nicht sicher, ob völlig zufällige Tests sehr nützlich sind, aber es ist sicherlich nicht ungültig.

Eine Programm- (oder Einheiten-) Spezifikation ist eine Hypothese, dass es ein Programm gibt, das diese erfüllt. Das Programm selbst ist dann ein Beweis für diese Hypothese. Was Unit-Tests sein sollten, ist ein Versuch, Gegenbeweise zu liefern, um zu widerlegen, dass das Programm gemäß der Spezifikation funktioniert.

Jetzt können Sie die Komponententests von Hand schreiben, aber es ist wirklich eine mechanische Aufgabe. Es kann automatisiert werden. Alles, was Sie tun müssen, ist die Spezifikation zu schreiben, und eine Maschine kann viele, viele Komponententests generieren, die versuchen, Ihren Code zu brechen.

Ich weiß nicht, welche Sprache Sie verwenden, aber siehe hier:

Java http://functionaljava.org/

Scala (oder Java) http://github.com/rickynils/scalacheck

Haskell http://www.cs.chalmers.se/~rjmh/QuickCheck/

.NET: http://blogs.msdn.com/dsyme/archive/2008/08/09/fscheck-0-2.aspx

Diese Tools verwenden Ihre wohlgeformte Spezifikation als Eingabe und generieren automatisch so viele Komponententests wie Sie möchten, mit automatisch generierten Daten. Sie verwenden "Schrumpf" -Strategien (die Sie optimieren können), um den einfachsten Testfall zu finden, um Ihren Code zu brechen und um sicherzustellen, dass er die Randfälle gut abdeckt.

Viel Spaß beim Testen!


1
+1 dazu. ScalaCheck leistet einen phänomenalen Job, indem es auf wiederholbare Weise minimierte, zufällige Testdaten generiert.
Daniel Spiewak

17
Es ist nicht zufällig. Es ist willkürlich. Großer Unterschied :)
Apocalisp

reductiotest.org scheint nun länger zu existieren, und Google hat mich nirgendwo anders gezeigt. Irgendeine Idee, wo es jetzt ist?
Raedwald

Es ist jetzt Teil der Functional Java-Bibliothek. Link bearbeitet. Aber ich würde nur Scalacheck verwenden, um Java-Code zu testen.
Apocalisp

ScalaCheck ist zu GitHub umgezogen. Link in Antwort aktualisiert. Es ist auch nützlich für Java, nicht nur für Scala. (Alter Link war code.google.com/p/scalacheck )
RobertB

38

Diese Art des Testens wird als Affentest bezeichnet . Wenn es richtig gemacht wird, kann es Insekten aus den wirklich dunklen Ecken rauchen.

Um Ihre Bedenken hinsichtlich der Reproduzierbarkeit auszuräumen: Der richtige Weg, dies zu erreichen, besteht darin, die fehlgeschlagenen Testeinträge aufzuzeichnen und einen Komponententest zu generieren, der die gesamte Familie des spezifischen Fehlers untersucht. und in den Komponententest die eine spezifische Eingabe (aus den Zufallsdaten) einbeziehen, die den anfänglichen Fehler verursacht hat.


26

Hier gibt es ein Haus auf halbem Weg, das eine Verwendung hat, nämlich Ihr PRNG mit einer Konstanten zu besäen. Auf diese Weise können Sie zufällige Daten generieren, die wiederholbar sind.

Persönlich denke ich, dass es Orte gibt, an denen (konstante) Zufallsdaten beim Testen nützlich sind - nachdem Sie glauben, alle sorgfältig durchdachten Ecken erledigt zu haben, kann die Verwendung von Stimuli aus einem PRNG manchmal andere Dinge finden.


4
Ich habe gesehen, dass dies auf einem System mit vielen Verriegelungen und Threads gut funktioniert. Der 'zufällige' Startwert wurde bei jedem Lauf in eine Datei geschrieben. Wenn ein Lauf fehlschlug, konnten wir den Pfad des Codes ermitteln und einen handgeschriebenen Komponententest für den Fall schreiben, den wir verpasst hatten.
Ian Ringrose

Wofür steht PRNG?
Systemovich

Pseudo-Zufallszahlengenerator
Will Dean

16

In dem Buch Beautiful Code gibt es ein Kapitel namens "Beautiful Tests", in dem er eine Teststrategie für den binären Suchalgorithmus durchläuft . Ein Absatz heißt "Random Acts of Testing", in dem er zufällige Arrays erstellt, um den Algorithmus gründlich zu testen. Sie können einige davon online in Google Books, Seite 95, lesen , , aber es ist ein großartiges Buch, das sich lohnt.

Im Grunde zeigt dies nur, dass das Generieren von Zufallsdaten zum Testen eine praktikable Option ist.


16

Ich bin für zufällige Tests und schreibe sie. Ob sie in einer bestimmten Build-Umgebung geeignet sind und in welchen Testsuiten sie enthalten sein sollten, ist jedoch eine differenziertere Frage.

Lokale Tests (z. B. über Nacht auf Ihrer Entwicklungsbox) haben ergeben, dass zufällige und offensichtliche Fehler vorliegen. Die obskuren sind arkan genug, dass ich denke, dass zufällige Tests wirklich die einzig realistische waren, um sie auszuspülen. Als Test nahm ich einen schwer zu findenden Fehler, der durch randomisierte Tests entdeckt wurde, und ließ ein halbes Dutzend Crack-Entwickler die Funktion (etwa ein Dutzend Codezeilen) überprüfen, in der sie auftrat. Keiner konnte es erkennen.

Viele Ihrer Argumente gegen randomisierte Daten sind Aromen von "Der Test ist nicht reproduzierbar". Ein gut geschriebener randomisierter Test erfasst jedoch den Samen, der zum Starten des randomisierten Samens verwendet wurde, und gibt ihn bei einem Fehler aus. Auf diese Weise können Sie den Test nicht nur manuell wiederholen, sondern auch trivial einen neuen Test erstellen, der das spezifische Problem testet, indem Sie den Startwert für diesen Test fest codieren. Natürlich ist es wahrscheinlich besser, einen expliziten Test, der diesen Fall abdeckt, von Hand zu codieren, aber Faulheit hat ihre Tugenden, und dies ermöglicht es Ihnen sogar, neue Testfälle im Wesentlichen automatisch aus einem fehlerhaften Startwert zu generieren.

Der einzige Punkt, den Sie ansprechen, über den ich jedoch nicht diskutieren kann, ist, dass er die Build-Systeme zerstört. Die meisten Build- und Continuous-Integration-Tests erwarten, dass die Tests jedes Mal dasselbe tun. Ein Test, der zufällig fehlschlägt, erzeugt Chaos, schlägt zufällig fehl und zeigt mit den Fingern auf harmlose Änderungen.

Eine Lösung besteht dann darin, Ihre randomisierten Tests weiterhin als Teil der Build- und CI-Tests auszuführen, sie jedoch mit einem festen Startwert für eine feste Anzahl von Iterationen auszuführen . Daher macht der Test immer das Gleiche, untersucht jedoch immer noch einen Teil des Eingabebereichs (wenn Sie ihn für mehrere Iterationen ausführen).

Vor Ort, z. B. wenn Sie die betreffende Klasse ändern, können Sie sie für weitere Iterationen oder mit anderen Seeds ausführen. Wenn randomisierte Tests jemals populärer werden, können Sie sich sogar eine bestimmte Reihe von Tests vorstellen, von denen bekannt ist, dass sie zufällig sind, die mit unterschiedlichen Samen durchgeführt werden können (daher mit zunehmender Abdeckung im Laufe der Zeit) und bei denen Fehler nicht dasselbe bedeuten würden als deterministische CI-Systeme (dh Läufe sind nicht 1: 1 mit Codeänderungen verbunden, sodass Sie nicht mit dem Finger auf eine bestimmte Änderung zeigen, wenn etwas fehlschlägt).

Für randomisierte Tests, insbesondere für gut geschriebene, gibt es viel zu sagen. Seien Sie also nicht zu schnell, um sie zu verwerfen!


14

Wenn Sie TDD machen, würde ich argumentieren, dass zufällige Daten ein ausgezeichneter Ansatz sind. Wenn Ihr Test mit Konstanten geschrieben ist, können Sie nur garantieren, dass Ihr Code für den bestimmten Wert funktioniert. Wenn Ihr Test auf dem Build-Server zufällig fehlschlägt, liegt wahrscheinlich ein Problem mit der Schreibweise des Tests vor.

Durch zufällige Daten wird sichergestellt, dass künftiges Refactoring nicht auf einer magischen Konstante beruht. Wenn Ihre Tests Ihre Dokumentation sind, bedeutet das Vorhandensein von Konstanten dann nicht, dass sie nur für diese Konstanten funktionieren müssen?

Ich übertreibe, aber ich ziehe es vor, zufällige Daten in meinen Test einzufügen, als Zeichen dafür, dass "der Wert dieser Variablen das Ergebnis dieses Tests nicht beeinflussen sollte".

Ich werde jedoch sagen, dass wenn Sie eine Zufallsvariable verwenden und dann Ihren Test basierend auf dieser Variablen abspalten, das ein Geruch ist.


10

Ein Vorteil für jemanden, der sich die Tests ansieht, ist, dass willkürliche Daten eindeutig nicht wichtig sind. Ich habe zu viele Tests mit Dutzenden von Daten gesehen, und es kann schwierig sein zu sagen, was so sein muss und was gerade so ist. Wenn beispielsweise eine Adressvalidierungsmethode mit einer bestimmten Postleitzahl getestet wird und alle anderen Daten zufällig sind, können Sie ziemlich sicher sein, dass die Postleitzahl der einzige wichtige Teil ist.


9
  • Wenn es sich um einen zufälligen Wert handelt und der Test fehlschlägt, müssen wir a) das Objekt reparieren und b) uns zwingen, jedes Mal auf diesen Wert zu testen, damit wir wissen, dass er funktioniert, aber da er zufällig ist, wissen wir nicht, was der Wert war

Wenn Ihr Testfall nicht genau aufzeichnet, was er testet, müssen Sie den Testfall möglicherweise neu codieren. Ich möchte immer Protokolle haben, auf die ich für Testfälle zurückgreifen kann, damit ich genau weiß, warum es fehlgeschlagen ist, ob statische oder zufällige Daten verwendet werden.


9

Ihr Mitarbeiter führt Fuzz-Tests durch , obwohl er nichts davon weiß. Sie sind besonders wertvoll in Serversystemen.


2
Aber ist das nicht eine grundlegend andere Sache als Unit-Tests? und zu einer anderen Zeit gemacht?
Endolith

1
@endolith Es gibt kein Gesetz der Physik, das Sie zwingt, bestimmte Tests zu bestimmten Zeiten
durchzuführen

1
@immibis Aber es gibt gute Gründe, bestimmte Tests zu bestimmten Zeiten durchzuführen. Sie führen nicht jedes Mal eine Reihe von Komponententests durch, wenn ein Benutzer auf die Schaltfläche "OK" klickt.
Endolith

5

Können Sie einige zufällige Daten einmal generieren (ich meine genau einmal, nicht einmal pro Testlauf) und sie anschließend in allen Tests verwenden?

Ich kann definitiv erkennen, welchen Wert es hat, zufällige Daten zu erstellen, um Fälle zu testen, an die Sie nicht gedacht haben, aber Sie haben Recht, Unit-Tests, die zufällig bestanden oder nicht bestanden werden können, sind eine schlechte Sache.


5

Sie sollten sich fragen, was das Ziel Ihres Tests ist.
Bei Unit-Tests geht es darum, Logik, Codefluss und Objektinteraktionen zu überprüfen. Durch die Verwendung von Zufallswerten wird versucht, ein anderes Ziel zu erreichen, wodurch der Testfokus und die Einfachheit verringert werden. Es ist aus Gründen der Lesbarkeit akzeptabel (Generieren von UUID, IDs, Schlüsseln usw.)
Speziell für Unit-Tests kann ich mich nicht erinnern, auch wenn diese Methode erfolgreich Probleme gefunden hat. Aber ich habe viele Determinismusprobleme (in den Tests) gesehen, die versucht haben, mit zufälligen Werten und hauptsächlich mit zufälligen Daten klug zu sein.
Fuzz-Tests sind ein gültiger Ansatz für Integrationstests und End-to-End-Tests .


Ich würde hinzufügen, dass die Verwendung von Zufallseingaben für das Fuzzing ein schlechter Ersatz für deckungsgesteuertes Fuzzing ist, wenn dies möglich ist.
Gobenji

1

Wenn Sie für Ihre Tests zufällige Eingaben verwenden, müssen Sie die Eingaben protokollieren, damit Sie die Werte sehen können. Auf diese Weise können Sie den Test schreiben, um ihn zu reproduzieren , wenn Sie auf einen Randfall stoßen . Ich habe die gleichen Gründe von Leuten gehört, die keine zufälligen Eingaben verwenden, aber sobald Sie einen Einblick in die tatsächlichen Werte haben, die für einen bestimmten Testlauf verwendet wurden, ist dies kein so großes Problem.

Der Begriff "willkürliche" Daten ist auch sehr nützlich, um etwas zu kennzeichnen, das nicht wichtig ist. Wir haben einige Abnahmetests, die in den Sinn kommen, wenn es viele Rauschdaten gibt, die für den vorliegenden Test nicht relevant sind.


0

Abhängig von Ihrem Objekt / Ihrer App haben zufällige Daten einen Platz im Lasttest. Ich denke, wichtiger wäre es, Daten zu verwenden, die die Randbedingungen der Daten explizit testen.


0

Wir sind heute gerade darauf gestoßen. Ich wollte Pseudozufalls (also würde es in Bezug auf die Größe wie komprimierte Audiodaten aussehen). Ich wollte, dass ich auch deterministisch wollte . rand () war unter OSX anders als unter Linux. Und wenn ich nicht wieder gesät habe, kann sich das jederzeit ändern. Deshalb haben wir es so geändert, dass es deterministisch, aber immer noch pseudozufällig ist: Der Test ist wiederholbar, genauso wie die Verwendung von vordefinierten Daten (aber bequemer geschrieben).

Dies wurde NICHT durch zufällige Brute Force durch Codepfade getestet. Das ist der Unterschied: Immer noch deterministisch, immer noch wiederholbar, immer noch Daten verwendet, die wie echte Eingaben aussehen, um eine Reihe interessanter Überprüfungen von Kantenfällen in komplexer Logik durchzuführen. Noch Unit-Tests.

Ist das noch zufällig? Reden wir über Bier. :-)


0

Ich kann mir drei Lösungen für das Testdatenproblem vorstellen:

  • Test mit festen Daten
  • Test mit zufälligen Daten
  • Generieren Sie einmal zufällige Daten und verwenden Sie sie dann als feste Daten

Ich würde empfehlen, all das zu tun . Das heißt, schreiben Sie wiederholbare Komponententests mit einigen Randfällen, die mit Ihrem Gehirn erarbeitet wurden, und einigen randomisierten Daten, die Sie nur einmal generieren. Eine Reihe von randomisierten Tests Dann schreiben Sie, dass Sie laufen auch .

Von den randomisierten Tests sollte niemals erwartet werden, dass sie etwas auffangen, was Ihre wiederholbaren Tests verpassen. Sie sollten versuchen, alles mit wiederholbaren Tests abzudecken, und die randomisierten Tests als Bonus betrachten. Wenn sie etwas finden, sollte es etwas sein, das Sie nicht vernünftigerweise vorhergesagt haben könnten; eine echte Kuriosität.


-2

Wie kann Ihr Mann den Test erneut ausführen, wenn nicht festgestellt wurde, ob er ihn behoben hat? Dh er verliert die Wiederholbarkeit von Tests.

Während ich denke, dass es wahrscheinlich einen gewissen Wert hat, eine Menge zufälliger Daten bei Tests zu schleudern, fällt dies, wie in anderen Antworten erwähnt, mehr unter die Überschrift Lasttest als alles andere. Es ist so ziemlich eine "Prüfung durch Hoffnung" -Praxis. Ich denke, dass Ihr Typ in Wirklichkeit einfach nicht darüber nachdenkt, was er zu testen versucht, und diesen Mangel an Gedanken auszugleichen, indem er hofft, dass Zufälligkeit irgendwann einen mysteriösen Fehler einfängt.

Das Argument, das ich mit ihm verwenden würde, ist, dass er faul ist. Oder anders ausgedrückt: Wenn er sich nicht die Zeit nimmt, um zu verstehen, was er zu testen versucht, zeigt dies wahrscheinlich, dass er den Code, den er schreibt, nicht wirklich versteht.


3
Es ist möglich, die zufälligen Daten oder den zufälligen Startwert zu protokollieren, damit der Test reproduziert werden kann.
cbp
Durch die Nutzung unserer Website bestätigen Sie, dass Sie unsere Cookie-Richtlinie und Datenschutzrichtlinie gelesen und verstanden haben.
Licensed under cc by-sa 3.0 with attribution required.