Ich denke nicht, dass dies spezifisch für eine Sprache oder ein Framework ist, aber ich verwende xUnit.net und C #.
Ich habe eine Funktion, die ein zufälliges Datum in einem bestimmten Bereich zurückgibt. Ich gebe ein Datum ein und das Rückgabedatum liegt immer im Bereich von 1 bis 40 Jahren vor dem angegebenen Datum.
Jetzt frage ich mich nur, ob es einen guten Weg gibt, dies zu testen. Der beste Ansatz scheint darin zu bestehen, eine Schleife zu erstellen und die Funktion 100 Mal laufen zu lassen und zu behaupten, dass jedes dieser 100 Ergebnisse im gewünschten Bereich liegt, was mein aktueller Ansatz ist.
Mir ist auch klar, dass es keine perfekte Lösung gibt, wenn ich meinen Zufallsgenerator nicht steuern kann (schließlich ist das Ergebnis zufällig), aber ich frage mich, welche Ansätze Sie wählen, wenn Sie Funktionen testen müssen, die ein zufälliges Ergebnis zurückgeben ein bestimmter Bereich?
quelle
Antworten:
Sie möchten nicht nur testen, ob die Funktion ein Datum im gewünschten Bereich zurückgibt, sondern auch sicherstellen, dass das Ergebnis gut verteilt ist. Der von Ihnen beschriebene Test würde eine Funktion bestehen, die einfach das Datum zurückgibt, das Sie gesendet haben!
Ich würde also nicht nur die Funktion mehrmals aufrufen und testen, ob das Ergebnis im gewünschten Bereich bleibt, sondern auch versuchen, die Verteilung zu bewerten, indem ich die Ergebnisse möglicherweise in Buckets einsetze und überprüfe, ob die Buckets nach Ihnen ungefähr die gleiche Anzahl von Ergebnissen aufweisen getan. Möglicherweise benötigen Sie mehr als 100 Aufrufe, um stabile Ergebnisse zu erzielen. Dies klingt jedoch nicht nach einer teuren (zur Laufzeit) Funktion, sodass Sie sie problemlos für einige K-Iterationen ausführen können.
Ich hatte schon einmal ein Problem mit ungleichmäßigen "zufälligen" Funktionen. Sie können ein echtes Problem sein. Es lohnt sich, sie frühzeitig zu testen.
quelle
Verspotten oder fälschen Sie den Zufallsgenerator
Machen Sie so etwas ... Ich habe es nicht kompiliert, daher kann es zu einigen Syntaxfehlern kommen.
public interface IRandomGenerator { double Generate(double max); } public class SomethingThatUsesRandom { private readonly IRandomGenerator _generator; private class DefaultRandom : IRandomGenerator { public double Generate(double max) { return (new Random()).Next(max); } } public SomethingThatUsesRandom(IRandomGenerator generator) { _generator = generator; } public SomethingThatUsesRandom() : this(new DefaultRandom()) {} public double MethodThatUsesRandom() { return _generator.Generate(40.0); } }
In Ihrem Test fälschen oder verspotten Sie einfach den IRandomGenerator, um etwas in Dosen zurückzugeben.
quelle
Ich denke, Sie testen drei verschiedene Aspekte dieses Problems.
Der erste: Ist mein Algorithmus der richtige? Das heißt, wenn ein ordnungsgemäß funktionierender Zufallszahlengenerator Daten erzeugt, die zufällig über den Bereich verteilt sind?
Der zweite: Behandelt der Algorithmus Kantenfälle richtig? Das heißt, wenn der Zufallszahlengenerator die höchsten oder niedrigsten zulässigen Werte erzeugt, bricht etwas?
Der dritte: Funktioniert meine Implementierung des Algorithmus? Das heißt, bei einer bekannten Liste von Pseudozufalls-Eingaben wird die erwartete Liste von Pseudozufallsdaten erzeugt?
Die ersten beiden Dinge würde ich nicht in die Unit-Testing-Suite einbauen. Das würde ich beim Entwerfen des Systems beweisen. Ich würde dies wahrscheinlich tun, indem ich ein Testgeschirr schreibe, das zig Daten generiert und einen Chi-Quadrat-Test durchführt, wie daniel.rikowski vorgeschlagen hat. Ich würde auch sicherstellen, dass dieses Testgeschirr nicht beendet wird, bis es beide Randfälle behandelt hat (vorausgesetzt, mein Bereich von Zufallszahlen ist klein genug, damit ich damit durchkommen kann). Und ich würde dies dokumentieren, damit jeder, der mitkommt und versucht, den Algorithmus zu verbessern, weiß, dass dies eine bahnbrechende Änderung ist.
Der letzte ist etwas, für das ich einen Unit-Test machen würde. Ich muss wissen, dass sich nichts in den Code eingeschlichen hat, der die Implementierung dieses Algorithmus unterbricht. Das erste Anzeichen dafür ist, dass der Test fehlschlägt. Dann gehe ich zurück zum Code und finde heraus, dass jemand anderes dachte, er würde etwas reparieren und brach es stattdessen. Wenn jemand hat den Algorithmus beheben, würde es auf sie zu diesem Test zu beheben.
quelle
Sie müssen das System nicht steuern, um die Ergebnisse deterministisch zu machen. Sie sind auf dem richtigen Weg: Entscheiden Sie, was an der Ausgabe der Funktion wichtig ist, und testen Sie dies. In diesem Fall ist es wichtig, dass das Ergebnis in einem Bereich von 40 Tagen liegt, und Sie testen dies. Es ist auch wichtig, dass nicht immer das gleiche Ergebnis zurückgegeben wird. Testen Sie dies auch. Wenn Sie schicker sein möchten, können Sie testen, ob die Ergebnisse eine Art Zufälligkeitstest bestehen.
quelle
Normalerweise verwende ich genau Ihren vorgeschlagenen Ansatz: Steuern Sie den Zufallsgenerator. Initialisieren Sie es für den Test mit einem Standard-Seed (oder ersetzen Sie ihn durch einen Proxy, der Zahlen zurückgibt, die zu meinen Testfällen passen), damit ich ein deterministisches / testbares Verhalten habe.
quelle
Wenn Sie die Qualität der Zufallszahlen (in Bezug auf die Unabhängigkeit) überprüfen möchten, gibt es verschiedene Möglichkeiten, dies zu tun. Ein guter Weg ist der Chi-Quadrat-Test .
quelle
Abhängig davon, wie Ihre Funktion das zufällige Datum erstellt, möchten Sie möglicherweise auch nach illegalen Daten suchen: unmöglichen Schaltjahren oder dem 31. Tag eines 30-Tage-Monats.
quelle
Methoden, die kein deterministisches Verhalten aufweisen, können nicht ordnungsgemäß in Einheiten getestet werden, da die Ergebnisse von Ausführung zu Ausführung unterschiedlich sind. Eine Möglichkeit, dies zu umgehen, besteht darin, den Zufallszahlengenerator mit einem festen Wert für den Komponententest zu versehen. Sie können auch die Zufälligkeit der Datumsgenerierungsklasse extrahieren (und damit das Prinzip der Einzelverantwortung anwenden ) und bekannte Werte für die Komponententests einfügen.
quelle
Sicher, die Verwendung eines Zufallszahlengenerators mit festem Startwert funktioniert einwandfrei, aber selbst dann versuchen Sie einfach, auf das zu testen, was Sie nicht vorhersagen können. Welches ist in Ordnung. Dies entspricht einer Reihe fester Tests. Denken Sie jedoch daran - testen Sie, was wichtig ist, aber versuchen Sie nicht, alles zu testen. Ich glaube, zufällige Tests sind eine Möglichkeit, alles zu testen, und sie sind nicht effizient (oder schnell). Möglicherweise müssen Sie sehr viele randomisierte Tests durchführen, bevor Sie auf einen Fehler stoßen.
Ich versuche hier zu erreichen, dass Sie einfach einen Test für jeden Fehler schreiben sollten, den Sie in Ihrem System finden. Sie testen Randfälle, um sicherzustellen, dass Ihre Funktion auch unter extremen Bedingungen ausgeführt wird. Dies ist jedoch das Beste, was Sie tun können, ohne entweder zu viel Zeit zu verbringen oder die Ausführung der Komponententests zu verlangsamen oder einfach Prozessorzyklen zu verschwenden.
quelle
Ich würde empfehlen, die Zufallsfunktion zu überschreiben. Ich teste Unit in PHP, also schreibe ich diesen Code:
// If we are unit testing, then... if (defined('UNIT_TESTING') && UNIT_TESTING) { // ...make our my_rand() function deterministic to aid testing. function my_rand($min, $max) { return $GLOBALS['random_table'][$min][$max]; } } else { // ...else make our my_rand() function truly random. function my_rand($min = 0, $max = PHP_INT_MAX) { if ($max === PHP_INT_MAX) { $max = getrandmax(); } return rand($min, $max); } }
Ich setze dann die random_table so, wie ich es pro Test benötige.
Das Testen der wahren Zufälligkeit einer Zufallsfunktion ist insgesamt ein separater Test. Ich würde es vermeiden, die Zufälligkeit in Unit-Tests zu testen, und stattdessen separate Tests durchführen und die wahre Zufälligkeit der Zufallsfunktion in der von Ihnen verwendeten Programmiersprache googeln. Nicht deterministische Tests (falls überhaupt) sollten von Unit-Tests ausgeschlossen werden. Möglicherweise haben Sie eine separate Suite für diese Tests, die menschliche Eingaben oder viel längere Laufzeiten erfordern, um die Wahrscheinlichkeit eines Fehlers zu minimieren, der wirklich erfolgreich ist.
quelle
Ich denke nicht, dass Unit-Tests dafür gedacht sind. Sie können Unit-Tests für Funktionen verwenden, die einen stochastischen Wert zurückgeben, aber einen festen Startwert verwenden. In diesem Fall sind sie sozusagen nicht stochastisch. Für zufällige Startwerte halte ich Unit-Tests beispielsweise nicht für das, was Sie möchten Für RNGs ist ein Systemtest gemeint, bei dem Sie das RNG viele Male ausführen und die Verteilung oder Momente davon betrachten.
quelle