Unit Testing mit Funktionen, die zufällige Ergebnisse liefern

70

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?

Michael Stum
quelle
Verwenden Sie beispielsweise QuickCheck, um die Eingabe für die Zufallsfunktion zu generieren, und überprüfen Sie dann, ob die Ausgabe mit bestimmten Eigenschaften übereinstimmt.

Antworten:

33

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.

SquareCog
quelle
1
Tatsächlich gibt es statistische Tests zum Testen gegen eine spezielle Verteilung (zum Beispiel Pearson's Chi-Quadrat-Test). Sie arbeiten in Grenzen mit weniger Werten als Bill erwähnt. Da es sich um einen statistischen Test handelt, kann der Test von Zeit zu Zeit fehlschlagen (falsch negativ).
Tobias Langner
2
Stimmen Sie dieser Antwort nicht zu - Sie testen effektiv den Zufallszahlengenerator, auch bekannt als "Code eines anderen". Das Fälschen des Generators wie in einer anderen Antwort beschrieben ist der richtige Ansatz. Ihr Test sollte lediglich überprüfen, ob der Zufallsgenerator aufgerufen wird und ob seine Ergebnisse wie erwartet verarbeitet werden.
Chris Peacock
57

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.

Brian Genisio
quelle
1
Abgesehen davon haben viele Sprachen Verspottungs-Frameworks, mit denen Sie das Verspotten vereinfachen können. Leistungsstärkere (z. B. PowerMock) können sogar das Überschreiben von Anrufen an das RNG ermöglichen, ohne dass eine Abhängigkeitsinjektion erforderlich ist.
Kat
+1 für den DI und um den Unit-Test tatsächlich zu ermöglichen. Unit-Tests sollten schnell, unabhängig und immer immer fehlerfrei das gleiche Ergebnis liefern, unabhängig von Tageszeit, Ausführungsreihenfolge usw. Ein Test, der besagt, dass "die meiste Zeit etwas Ähnliches wie dieses Ergebnis geben sollte", ist kein vertrauenswürdiger Komponententest. Zugegeben, in diesem Fall wird es kaum wirklich scheitern, aber Sie müssen Ihren Tests zu 100% vertrauen, sonst sind sie irgendwie nutzlos.
Sara
9

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.

Robert Rossney
quelle
8

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.

Ned Batchelder
quelle
5

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.

flolo
quelle
4

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 .

Daniel Rikowski
quelle
2

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.

mseery
quelle
2

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.

Philant
quelle
2

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.

thekingoftruth
quelle
1

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.

user263976
quelle
0

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.

ddd
quelle