Unit-Test von inhärent zufälligen / nicht deterministischen Algorithmen


41

In meinem aktuellen Projekt geht es kurz gesagt um die Schaffung von "zwangsläufig zufälligen Ereignissen". Grundsätzlich erstelle ich einen Inspektionsplan. Einige von ihnen basieren auf strengen Zeitplanbeschränkungen. Sie führen einmal pro Woche am Freitag um 10:00 Uhr eine Inspektion durch. Andere Inspektionen sind "zufällig"; Es gibt grundlegende konfigurierbare Anforderungen wie "Eine Inspektion muss dreimal pro Woche stattfinden", "Die Inspektion muss zwischen 9 und 21 Uhr stattfinden" und "Es sollten nicht zwei Inspektionen innerhalb desselben Zeitraums von 8 Stunden stattfinden" Unabhängig von den Einschränkungen, die für einen bestimmten Satz von Inspektionen konfiguriert wurden, sollten die resultierenden Daten und Zeiten nicht vorhersehbar sein.

Unit-Tests und TDD (IMO) sind in diesem System von großem Wert, da sie verwendet werden können, um es schrittweise zu erstellen, während die gesamten Anforderungen noch unvollständig sind weiß momentan nicht was ich brauche. Die strengen Zeitpläne waren für TDD ein Kinderspiel. Es fällt mir jedoch schwer zu definieren, was ich teste, wenn ich Tests für den zufälligen Teil des Systems schreibe. Ich kann behaupten, dass alle vom Scheduler erzeugten Zeiten innerhalb der Beschränkungen liegen müssen, aber ich könnte einen Algorithmus implementieren, der alle derartigen Tests besteht, ohne dass die tatsächlichen Zeiten sehr "zufällig" sind. Genau das ist passiert. Ich fand ein Problem, bei dem die Zeiten, obwohl nicht genau vorhersehbar, in eine kleine Teilmenge der zulässigen Datums- / Zeitbereiche fielen. Der Algorithmus hat immer noch alle Behauptungen bestanden, die ich vernünftigerweise machen konnte, und ich konnte keinen automatisierten Test entwerfen, der in dieser Situation fehlschlagen würde, sondern bestand, wenn "zufälligere" Ergebnisse gegeben wurden. Ich musste nachweisen, dass das Problem behoben wurde, indem einige vorhandene Tests so umstrukturiert wurden, dass sie sich mehrmals wiederholen. Außerdem musste ich visuell überprüfen, ob die generierten Zeiten innerhalb des zulässigen Bereichs lagen.

Hat jemand Tipps zum Entwerfen von Tests, die nicht deterministisches Verhalten erwarten sollten?


Vielen Dank an alle für die Vorschläge. Die Hauptmeinung scheint zu sein, dass ich einen deterministischen Test brauche, um deterministische, wiederholbare und durchsetzbare Ergebnisse zu erhalten . Macht Sinn.

Ich habe eine Reihe von "Sandbox" -Tests erstellt, die Kandidatenalgorithmen für den Einschränkungsprozess enthalten (der Prozess, bei dem ein beliebig langes Byte-Array zu einer Länge zwischen einem Minimum und einem Maximum wird). Ich führe diesen Code dann durch eine FOR-Schleife, die dem Algorithmus mehrere bekannte Bytearrays (Werte von 1 bis 10.000.000, nur um zu beginnen) gibt und den Algorithmus jeweils auf einen Wert zwischen 1009 und 7919 einschränkt (ich verwende Primzahlen, um sicherzustellen, dass ein Der Algorithmus würde keine zufällige GCF zwischen dem Eingangs- und dem Ausgangsbereich passieren. Die resultierenden eingeschränkten Werte werden gezählt und ein Histogramm erstellt. Um zu "bestehen", müssen alle Eingaben im Histogramm wiedergegeben werden (um sicherzustellen, dass wir keine verloren haben), und der Unterschied zwischen zwei Buckets im Histogramm darf nicht größer als 2 sein (er sollte tatsächlich <= 1 sein) , aber bleib dran). Der Gewinnalgorithmus kann, falls vorhanden, direkt in den Produktionscode eingefügt und ein permanenter Test für die Regression durchgeführt werden.

Hier ist der Code:

    private void TestConstraintAlgorithm(int min, int max, Func<byte[], long, long, long> constraintAlgorithm)
    {
        var histogram = new int[max-min+1];
        for (int i = 1; i <= 10000000; i++)
        {
            //This is the stand-in for the PRNG; produces a known byte array
            var buffer = BitConverter.GetBytes((long)i);

            long result = constraintAlgorithm(buffer, min, max);

            histogram[result - min]++;
        }

        var minCount = -1;
        var maxCount = -1;
        var total = 0;
        for (int i = 0; i < histogram.Length; i++)
        {
            Console.WriteLine("{0}: {1}".FormatWith(i + min, histogram[i]));
            if (minCount == -1 || minCount > histogram[i])
                minCount = histogram[i];
            if (maxCount == -1 || maxCount < histogram[i])
                maxCount = histogram[i];
            total += histogram[i];
        }

        Assert.AreEqual(10000000, total);
        Assert.LessOrEqual(maxCount - minCount, 2);
    }

    [Test, Explicit("sandbox, does not test production code")]
    public void TestRandomizerDistributionMSBRejection()
    {
        TestConstraintAlgorithm(1009, 7919, ConstrainByMSBRejection);
    }

    private long ConstrainByMSBRejection(byte[] buffer, long min, long max)
    {
        //Strip the sign bit (if any) off the most significant byte, before converting to long
        buffer[buffer.Length-1] &= 0x7f;
        var orig = BitConverter.ToInt64(buffer, 0);
        var result = orig;
        //Apply a bitmask to the value, removing the MSB on each loop until it falls in the range.
        var mask = long.MaxValue;
        while (result > max - min)
        {
            mask >>= 1;
            result &= mask;
        }
        result += min;

        return result;
    }

    [Test, Explicit("sandbox, does not test production code")]
    public void TestRandomizerDistributionLSBRejection()
    {
        TestConstraintAlgorithm(1009, 7919, ConstrainByLSBRejection);
    }

    private long ConstrainByLSBRejection(byte[] buffer, long min, long max)
    {
        //Strip the sign bit (if any) off the most significant byte, before converting to long
        buffer[buffer.Length - 1] &= 0x7f;
        var orig = BitConverter.ToInt64(buffer, 0);
        var result = orig;

        //Bit-shift the number 1 place to the right until it falls within the range
        while (result > max - min)
            result >>= 1;

        result += min;
        return result;
    }

    [Test, Explicit("sandbox, does not test production code")]
    public void TestRandomizerDistributionModulus()
    {
        TestConstraintAlgorithm(1009, 7919, ConstrainByModulo);
    }

    private long ConstrainByModulo(byte[] buffer, long min, long max)
    {
        buffer[buffer.Length - 1] &= 0x7f;
        var result = BitConverter.ToInt64(buffer, 0);

        //Modulo divide the value by the range to produce a value that falls within it.
        result %= max - min + 1;

        result += min;
        return result;
    }

... und hier sind die Ergebnisse:

Bildbeschreibung hier eingeben

Die LSB-Zurückweisung (Bitverschiebung der Zahl, bis sie in den Bereich fällt) war aus einem sehr einfach zu erklärenden Grund SCHRECKLICH. Wenn Sie eine Zahl durch 2 teilen, bis sie kleiner als ein Maximum ist, beenden Sie sie, sobald dies der Fall ist, und für jeden nicht trivialen Bereich werden die Ergebnisse in Richtung des oberen Drittels verschoben (wie in den detaillierten Ergebnissen des Histogramms zu sehen war) ). Dies war genau das Verhalten, das ich von den fertigen Daten aus gesehen habe; Alle Zeiten waren nachmittags, an ganz bestimmten Tagen.

MSB-Zurückweisung (Entfernen des höchstwertigen Bits von der Nummer eins zu einem Zeitpunkt, bis es innerhalb des Bereichs liegt) ist besser, aber auch hier ist es nicht gleichmäßig verteilt, da Sie mit jedem Bit sehr große Zahlen abhacken. Es ist unwahrscheinlich, dass Sie am oberen und unteren Ende Zahlen erhalten, sodass Sie eine Tendenz zum mittleren Drittel haben. Das mag jemandem zugute kommen, der Zufallsdaten in eine glockenhafte Kurve "normalisieren" möchte, aber eine Summe von zwei oder mehr kleineren Zufallszahlen (ähnlich wie beim Würfeln) würde eine natürlichere Kurve ergeben. Für meine Zwecke scheitert es.

Der einzige, der diesen Test bestand, war die Einschränkung durch Modulo-Division, die sich auch als die schnellste der drei herausstellte. Modulo wird nach seiner Definition bei den verfügbaren Eingaben eine möglichst gleichmäßige Verteilung erzeugen.


2
Sie möchten also letztendlich ein Programm, das die Ausgabe eines Zufallszahlengenerators überprüft und entscheidet, ob es zufällig ist? Wie in "5,4,10,31,120,390,2,3,4" war zufällig, aber "49,39,1,10,103,12,4,189" war nicht?
PSR

Nein, aber es wäre schön, keine Verzerrung zwischen dem tatsächlichen PRNG und dem Endergebnis einzuführen.
KeithS

Dann klingt es so, als wäre es in Ordnung, PRNG zu verspotten. Sie benötigen keine tatsächlichen Zufallswerte, um zu testen, ob Sie keine Werte verfälschen. Wenn Sie einen Fehler quetschen Zufallswerte in eine zu kleine Teilmenge der zulässigen Bereiche haben, müssen Sie bekommen einige spezifische Werte falsch.
PSR

Sie sollten auch auf Kombinationen testen. Ungefähr gleich hohe erwartete Inspektionen pro Stunde sind kein Schutz für den Fall, dass auf eine Inspektion um 11 Uhr am Dienstag immer eine Inspektion um 14 Uhr am Donnerstag und eine Inspektion um 10 Uhr am Freitag folgt.
David Thornley

Das ist eher ein Test des PRNG selbst; Der Test der Einschränkungsmechanismen, wie oben beschrieben, würde einen solchen Test immer nicht bestehen, da er mit einem durch und durch nicht zufälligen Datensatz versehen wird. Angenommen, der Beschränkungsmechanismus ist nicht bestrebt, die Zufallsdaten zu "ordnen". Ich würde dies "Testen von Externen" nennen, was bei einem Komponententest nicht der Fall sein sollte.
KeithS

Antworten:


17

Was Sie hier tatsächlich testen möchten, ist vermutlich, dass der Rest Ihrer Methode bei bestimmten Ergebnissen des Randomisierers korrekt ausgeführt wird.

Wenn es das ist, wonach Sie suchen, dann verspotten Sie den Zufallsgenerator, um ihn im Rahmen des Tests deterministisch zu machen.

Ich habe im Allgemeinen Scheinobjekte für alle Arten von nicht deterministischen oder unvorhersehbaren Daten (zum Zeitpunkt des Schreibens des Tests), einschließlich GUID-Generatoren und DateTime.Now.

Bearbeiten, aus Kommentaren: Sie müssen den PRNG (dieser Begriff ist mir letzte Nacht entgangen) auf der niedrigstmöglichen Ebene verspotten - dh. wenn es das Array von Bytes generiert, nicht nachdem Sie diese in Int64s verwandeln. Oder sogar auf beiden Ebenen, sodass Sie testen können, ob Ihre Konvertierung in ein Array von Int64 wie beabsichtigt funktioniert, und dann separat testen können, ob Ihre Konvertierung in ein Array von DateTimes wie beabsichtigt funktioniert. Wie Jonathon sagte, können Sie das einfach tun, indem Sie ihm einen festgelegten Startwert geben, oder Sie können ihm das Array von Bytes geben, die zurückgegeben werden sollen.

Ich bevorzuge letzteres, weil es nicht kaputt geht, wenn sich die Framework-Implementierung eines PRNG ändert. Ein Vorteil bei der Vergabe des Startwerts ist jedoch, dass Sie, wenn Sie in der Produktion einen Fall finden, der nicht wie beabsichtigt funktioniert hat, nur eine Nummer protokollieren müssen, um ihn replizieren zu können, und nicht das gesamte Array.

Trotzdem müssen Sie bedenken, dass es aus einem bestimmten Grund ein Pseudo- Zufallszahlengenerator ist. Es kann sogar auf dieser Ebene eine gewisse Voreingenommenheit geben.


1
Nein. Was ich in diesem Fall testen möchte, ist der Zufallsgenerator selbst, und ich versichere, dass die vom Zufallsgenerator generierten "zufälligen" Werte innerhalb der angegebenen Einschränkungen liegen, während sie immer noch "zufällig" sind, da sie nicht auf eine ungleichmäßige Verteilung über das Zulässige abzielen Zeitbereiche. Ich kann und kann deterministisch testen, ob ein bestimmtes Datum / eine bestimmte Uhrzeit eine bestimmte Bedingung erfüllt oder nicht erfüllt, aber das eigentliche Problem, auf das ich gestoßen bin, war, dass die vom Randomizer erzeugten Daten voreingenommen und daher vorhersehbar waren.
KeithS

Ich kann mir nur vorstellen, dass der Zufallsgenerator eine Reihe von Datumsangaben ausspuckt und ein Histogramm erstellt und dann bestätigt, dass die Werte relativ gleichmäßig verteilt sind. Das scheint extrem hartnäckig und immer noch nicht deterministisch zu sein, da jeder wirklich zufällige Datensatz eine offensichtliche Verzerrung aufweisen kann, die ein größerer Datensatz dann widerlegen würde.
KeithS

1
Das ist ein Test, der gelegentlich und unvorhersehbar abbricht. Das willst du nicht. Ich denke, Sie haben meinen Standpunkt falsch verstanden, um ehrlich zu sein. Irgendwo in dem, was Sie als Zufallsgenerator bezeichnen, muss eine Codezeile vorhanden sein, die eine Zufallszahl generiert, oder? Diese Zeile ist das, was ich als Zufallsgenerator bezeichne, und der Rest von dem, was Sie als Zufallsgenerator bezeichnen (die Verteilung von Daten basierend auf "zufälligen" Daten), ist das, was Sie testen möchten. Oder vermisse ich etwas?
pdr

Ordnungsgemäße statistische Metriken der Zufallssequenzen (Korrelation, Blockkorrelation, Durchschnitt, Standardabweichung usw.) erfüllen Ihre Erwartungen nur dann nicht, wenn Sie wirklich kleine Stichproben machen. Erhöhen Sie Ihre Stichprobensätze und / oder erhöhen Sie die zulässigen Fehlerbalken
lurscher

1
"Einige Verzerrungen sogar auf dieser Ebene" Wenn Sie ein gutes PRNG verwenden, werden Sie keinen Test (mit realistischen Rechengrenzen) finden können, der es von der tatsächlichen Zufälligkeit unterscheidet. In der Praxis kann man also davon ausgehen, dass ein guter PRNG keinerlei Verzerrung aufweist.
CodesInChaos

23

Das wird sich nach einer dummen Antwort anhören, aber ich werde es da rausschmeißen, weil ich es so gesehen habe:

Entkoppeln Sie Ihren Code vom PRNG - übergeben Sie den Startwert für die Randomisierung an den gesamten Code, der die Randomisierung verwendet. Dann können Sie die Arbeitswerte aus einem einzelnen Samen bestimmen (oder aus mehreren Samen, bei denen Sie sich besser fühlen würden). Auf diese Weise können Sie Ihren Code angemessen testen, ohne sich auf das Gesetz der großen Zahlen verlassen zu müssen.

Es klingt verrückt, aber so macht es das Militär (entweder das oder sie benutzen eine 'zufällige Tabelle', die überhaupt nicht zufällig ist)


Genau: Wenn Sie ein Element eines Algorithmus nicht testen können, extrahieren Sie es und verspotten Sie es
Steve Greatrex

Ich habe keinen deterministischen Startwert angegeben. Stattdessen habe ich das "zufällige" Element vollständig entfernt, damit ich mich nicht einmal auf den spezifischen PRNG-Algorithmus verlassen musste. Ich konnte testen, dass der Algorithmus, mit dem ich vorging, bei einem großen Bereich gleichmäßig verteilter Zahlen diese auf einen kleineren Bereich beschränken konnte, ohne eine Verzerrung einzuführen. Der PRNG selbst sollte von jedem Entwickler angemessen getestet werden (ich verwende RNGCryptoServiceProvider).
KeithS

In Bezug auf den "Zufallstabellen" -Ansatz können Sie auch eine Testimplementierung verwenden, die einen "umkehrbaren" Algorithmus zur Erzeugung von Zahlen enthält. Auf diese Weise können Sie das PRNG "zurückspulen" oder es sogar abfragen, um die letzten N Ausgaben zu sehen. Dies würde in bestimmten Szenarien ein viel gründlicheres Debuggen ermöglichen.
Darien

Dies ist nicht so dumm - es ist die gleiche Methode, die Google verwendet, um die Fehlerinjektion in Spanner-Tests nach ihrem
Artikel

6

"Ist es zufällig (genug)?" Stellt sich als unglaublich subtile Frage heraus. Die kurze Antwort lautet: Ein herkömmlicher Komponententest reicht nicht aus - Sie müssen eine Reihe von Zufallswerten generieren und sie verschiedenen statistischen Tests unterziehen, die Ihnen die Gewissheit geben, dass sie für Ihre Anforderungen zufällig genug sind.

Es wird ein Muster geben - wir verwenden schließlich Pseudozufallszahlengeneratoren. Aber irgendwann werden die Dinge "gut genug" für Ihre Anwendung sein (wo gut genug eine Menge zwischen den einzelnen Spielen an einem Ende variiert, wo relativ einfache Generatoren ausreichen, bis hin zur Kryptographie, wo man wirklich Sequenzen braucht, die nicht bestimmbar sind) von einem entschlossenen und gut ausgerüsteten Angreifer).

Weitere Informationen finden Sie im Wikipedia-Artikel http://en.wikipedia.org/wiki/Randomness_tests .


Selbst mittelmäßige PRNGs zeigen in statistischen Tests keine Muster. Bei guten PRNGs ist es praktisch unmöglich, sie von reellen Zufallszahlen zu unterscheiden.
CodesInChaos

4

Ich habe zwei Antworten für Sie.

=== ERSTE ANTWORT ===

Sobald ich den Titel Ihrer Frage sah, kam ich, um die Lösung vorzuschlagen. Meine Lösung war die gleiche wie die, die mehrere andere vorgeschlagen haben: Ihren Zufallszahlengenerator zu verspotten. Immerhin habe ich verschiedene Programme erstellt, die diesen Trick benötigten, um gute Komponententests zu schreiben, und ich habe damit begonnen, den spöttischen Zugriff auf Zufallszahlen zu einer Standardpraxis in meiner gesamten Codierung zu machen.

Aber dann habe ich deine Frage gelesen. Und für das spezielle Problem, das Sie beschreiben, ist das nicht die Antwort. Ihr Problem bestand nicht darin, dass Sie einen Prozess vorhersehbar machen mussten, der Zufallszahlen verwendete (damit er testbar wäre). Vielmehr bestand Ihr Problem darin, zu überprüfen, ob Ihr Algorithmus die gleichmäßig zufällige Ausgabe Ihres RNG auf die von Ihrem Algorithmus ausgegebenen RNGs innerhalb der Einschränkungen abbildet. Wenn das zugrunde liegende RNG einheitlich wäre, würde dies zu gleichmäßig verteilten Inspektionszeiten führen (vorbehaltlich der problematische Randbedingungen).

Das ist ein sehr schwieriges (aber ziemlich genau definiertes) Problem. Was bedeutet, dass es ein interessantes Problem ist. Sofort fielen mir einige wirklich gute Ideen zur Lösung dieses Problems ein. Als ich noch ein heißer Programmierer war, hätte ich vielleicht angefangen, etwas mit diesen Ideen zu machen. Aber ich bin kein Hotshot-Programmierer mehr ... Ich mag es, dass ich jetzt erfahrener und erfahrener bin.

Anstatt mich auf das schwierige Problem einzulassen, dachte ich mir: Was ist der Wert davon? Und die Antwort war enttäuschend. Ihr Fehler wurde bereits behoben, und Sie werden sich in Zukunft um dieses Problem bemühen. Externe Umstände können das Problem nicht auslösen, nur Änderungen an Ihrem Algorithmus. Der EINZIGE Grund, dieses interessante Problem anzugehen, bestand darin, die Praktiken von TDD (Test Driven Design) zu erfüllen. Wenn ich eines gelernt habe, ist es, dass das blinde Festhalten an einer Übung, die nicht wertvoll ist, Probleme verursacht. Mein Vorschlag ist folgender: Schreiben Sie einfach keinen Test dafür und fahren Sie fort.


=== ZWEITE ANTWORT ===

Wow ... was für ein cooles Problem!

Sie müssen hier einen Test schreiben, der bestätigt, dass Ihr Algorithmus zur Auswahl von Inspektionsdaten und -zeiten eine gleichmäßig verteilte Ausgabe (innerhalb der Problembeschränkungen) liefert, wenn der von ihm verwendete RNG gleichmäßig verteilte Zahlen erzeugt. Hier finden Sie verschiedene Ansätze, sortiert nach Schwierigkeitsgrad.

  1. Sie können rohe Gewalt anwenden. Führen Sie den Algorithmus einfach ein paar Mal aus, mit einem echten RNG als Eingabe. Überprüfen Sie die Ausgabeergebnisse, um festzustellen, ob sie gleichmäßig verteilt sind. Ihr Test muss fehlschlagen, wenn die Verteilung um mehr als einen bestimmten Schwellenwert von der vollkommenen Gleichmäßigkeit abweicht. Um sicherzustellen, dass Sie Probleme feststellen, kann der Schwellenwert nicht ZU niedrig eingestellt werden. Das bedeutet, dass Sie eine GROSSE Anzahl von Läufen benötigen, um sicherzugehen, dass die Wahrscheinlichkeit eines falschen Positivs (eines zufälligen Testfehlers) sehr gering ist (gut <1% für eine mittelgroße Codebasis; noch weniger für eine große Code-Basis).

  2. Betrachten Sie Ihren Algorithmus als eine Funktion, die die Verkettung aller RNG-Ausgaben als Eingabe verwendet und dann Inspektionszeiten als Ausgabe erzeugt. Wenn Sie wissen, dass diese Funktion stückweise fortlaufend ist, können Sie Ihre Eigenschaft auf eine Weise testen. Ersetzen Sie das RNG durch ein nachahmbares RNG und führen Sie den Algorithmus mehrmals aus, um eine gleichmäßig verteilte RNG-Ausgabe zu erzielen. Wenn für Ihren Code also 2 RNG-Aufrufe erforderlich sind, die sich jeweils im Bereich von [0..1] befinden, kann der Test den Algorithmus 100-mal ausführen und die Werte [(0.0.0.0), (0.0.0.1), (0.0, 0,2), ... (0,0,0,9), (0,1,0,0), (0,1,0,1), ... (0,9,0,9)]. Dann können Sie prüfen, ob die Leistung der 100 Läufe (ungefähr) gleichmäßig im zulässigen Bereich verteilt war.

  3. Wenn Sie den Algorithmus WIRKLICH auf zuverlässige Weise überprüfen müssen und keine Annahmen über den Algorithmus treffen ODER nicht häufig ausführen können, können Sie das Problem trotzdem angreifen, müssen jedoch möglicherweise einige Einschränkungen in Bezug auf die Programmierung des Algorithmus beachten . Schauen Sie sich als Beispiel PyPy und seinen Object Space- Ansatz an. Sie könnten einen Objektraum erstellen, der statt des eigentlichen Ausführens des Algorithmus lediglich die Form der Ausgabeverteilung berechnet (vorausgesetzt, die RNG-Eingabe ist einheitlich). Dies setzt natürlich voraus, dass Sie ein solches Tool erstellen und Ihr Algorithmus in PyPy oder einem anderen Tool erstellt ist, mit dem es einfach ist, drastische Änderungen am Compiler vorzunehmen und den Code zu analysieren.


3

Ersetzen Sie für die Komponententests den Zufallsgenerator durch eine Klasse, die vorhersagbare Ergebnisse für alle Eckfälle generiert . Das heißt, Sie müssen sicherstellen, dass Ihr Pseudo-Randomizer mehrmals hintereinander den niedrigstmöglichen und den höchstmöglichen Wert sowie das gleiche Ergebnis generiert.

Sie möchten nicht, dass Ihre Komponententests beispielsweise einzelne Fehler übersehen, die auftreten, wenn Random.nextInt (1000) 0 oder 999 zurückgibt.


3

Vielleicht möchten Sie einen Blick auf Sevcikova et al. Werfen: "Automatisiertes Testen stochastischer Systeme: Ein statistisch fundierter Ansatz" ( PDF ).

Die Methodik ist in verschiedenen Testfällen für die UrbanSim- Simulationsplattform implementiert .


Das ist gutes Zeug.
KeithS

2

Ein einfacher Histogrammansatz ist ein guter erster Schritt, reicht jedoch nicht aus, um die Zufälligkeit zu beweisen. Für ein einheitliches PRNG würden Sie (zumindest) auch ein zweidimensionales Streudiagramm erstellen (wobei x der vorherige Wert und y der neue Wert ist). Diese Handlung sollte auch einheitlich sein. Dies ist in Ihrer Situation kompliziert, da das System absichtliche Nichtlinearitäten aufweist.

Mein Ansatz wäre:

  1. validieren (oder als gegeben annehmen), dass das Quellen-PRNG ausreichend zufällig ist (unter Verwendung statistischer Standardmaße)
  2. Stellen Sie sicher, dass eine nicht eingeschränkte PRNG-zu-Datum-Zeit-Konvertierung im gesamten Ausgaberaum ausreichend zufällig ist (dies bestätigt, dass die Konvertierung keine Abweichung aufweist). Hier sollte Ihr einfacher Homogenitätstest erster Ordnung ausreichen.
  3. Vergewissern Sie sich, dass die beschränkten Fälle ausreichend einheitlich sind (ein einfacher Einheitlichkeitstest erster Ordnung über die gültigen Klassen).

Jeder dieser Tests ist statistisch und erfordert eine große Anzahl von Stichprobenpunkten, um falsch-positive und falsch-negative Ergebnisse mit einem hohen Maß an Zuverlässigkeit zu vermeiden.

Über die Art des Umwandlungs- / Beschränkungsalgorithmus:

Gegeben: Methode zur Erzeugung eines Pseudozufallswerts p mit 0 <= p <= M

Need: Ausgabe y im (möglicherweise diskontinuierlichen) Bereich 0 <= y <= N <= M

Algorithmus:

  1. Berechnen Sie r = floor(M / N)also die Anzahl der vollständigen Ausgabebereiche, die in den Eingabebereich passen.
  2. Berechnen Sie den maximal zulässigen Wert für p :p_max = r * N
  3. Werte für p erzeugen, bis ein Wert kleiner oder gleich p_maxgefunden wird
  4. Berechnung y = p / r
  5. Wenn y akzeptabel ist, geben Sie es zurück, andernfalls wiederholen Sie mit Schritt 3

Der Schlüssel besteht darin, nicht akzeptable Werte zu verwerfen, anstatt sie ungleichmäßig zu falten.

im Pseudocode:

# assume prng generates non-negative values
def randomInRange(min, max, prng):
    range = max - min
    factor = prng.max / range

    do:
        value = prng()
    while value > range * factor
    return (value / factor) + min

def constrainedRandom(constraint, prng):
    do:
        value = randomInRange(constraint.min, constraint.max, prng)
    while not constraint.is_acceptable(value)

1

Abgesehen von der Überprüfung, dass Ihr Code nicht fehlschlägt oder an den richtigen Stellen richtige Ausnahmen auslöst, können Sie gültige Eingabe- / Antwortpaare erstellen (auch manuell berechnen), die Eingabe in den Test einspeisen und sicherstellen, dass die erwartete Antwort zurückgegeben wird. Nicht großartig, aber das ist so ziemlich alles, was Sie tun können, imho. In Ihrem Fall ist dies jedoch kein Zufall. Sobald Sie Ihren Zeitplan erstellt haben, können Sie die Regelkonformität testen. Sie müssen 3 Inspektionen pro Woche zwischen 9 und 9 durchführen. Es gibt keine wirkliche Notwendigkeit oder Fähigkeit, nach genauen Zeitpunkten zu suchen, zu denen die Inspektion stattgefunden hat.


1

Es gibt wirklich keinen besseren Weg, als ein paar Mal damit zu arbeiten und zu sehen, ob Sie die Distribution erhalten, die Sie wollen. Wenn Sie 50 mögliche Inspektionspläne zugelassen haben, führen Sie den Test 500 Mal durch und stellen Sie sicher, dass jeder Plan fast 10 Mal verwendet wird. Sie können Ihre Zufallsgenerator-Seeds steuern, um sie deterministischer zu machen. Dadurch werden Ihre Tests jedoch auch enger an die Implementierungsdetails gekoppelt.


Aber wenn es wirklich zufällig ist, wird gelegentlich ein Zeitplan überhaupt nicht verwendet. und gelegentlich wird ein Zeitplan mehr als 20 Mal verwendet. Ich weiß nicht, wie Sie testen möchten, ob jeder Zeitplan "fast zehnmal" verwendet wird. Unabhängig von der hier getesteten Bedingung wird jedoch ein Test durchgeführt, der manchmal fehlschlägt, wenn das Programm ordnungsgemäß ausgeführt wird.
Dawood sagt, Monica

@DawoodibnKareem Mit einer ausreichenden Stichprobengröße (und angemessenen Grenzen für die Homogenität) können Sie die Wahrscheinlichkeit, dass der Test fehlschlägt, auf 1 Milliarde reduzieren. Und im Allgemeinen sind solche Statistiken mit n exponentiell, so dass es weniger dauert, als Sie erwarten, um zu diesen Zahlen zu gelangen.
29.

1

Es ist nicht möglich, eine nebulöse Bedingung zu testen, für die keine konkrete Definition vorliegt. Wenn die generierten Daten alle Tests bestehen, funktioniert Ihre Anwendung theoretisch ordnungsgemäß. Der Computer kann Ihnen nicht sagen, ob die Daten "zufällig genug" sind, da er die Kriterien für einen solchen Test nicht anerkennen kann. Wenn alle Tests bestanden wurden, das Verhalten der Anwendung jedoch immer noch nicht geeignet ist, ist Ihre Testabdeckung empirisch unzureichend (aus TDD-Sicht).

Meiner Ansicht nach ist es am besten, einige willkürliche Einschränkungen für die Datumsgenerierung zu implementieren, damit die Verteilung einen menschlichen Geruchstest besteht.


2
Sie können die Zufälligkeit durch automatisiertes Testen absolut bestimmen. Sie generieren einfach eine ausreichend große Anzahl von Stichproben und wenden Standardtests der Zufälligkeit an, um Verzerrungen im System zu erkennen. Dies ist eine ziemlich übliche Programmierübung für Studenten.
Frank Szczerba

0

Zeichnen Sie einfach die Ausgabe Ihres Zufallsgenerators auf (egal ob Pseudo- oder Quanten- / Chaos- oder reale Welt). Speichern und wiederholen Sie dann die "zufälligen" Sequenzen, die Ihren Testanforderungen entsprechen oder die potenzielle Probleme und Fehler aufdecken, während Sie Ihre Komponententestfälle erstellen.


0

Dieser Fall scheint ideal für das eigenschaftsbasierte Testen zu sein .

Kurz gesagt, es ist ein Testmodus, bei dem das Testframework Eingaben für den zu testenden Code generiert und Testzusagen die Eigenschaften der Ausgaben validieren . Das Framework kann dann intelligent genug sein, um den getesteten Code "anzugreifen" und zu versuchen, ihn in einen Fehler zu verwandeln. Das Framework ist normalerweise auch intelligent genug, um Ihren Startwert für den Zufallszahlengenerator zu entführen. In der Regel können Sie das Framework so konfigurieren, dass höchstens N Testfälle generiert oder höchstens N Sekunden ausgeführt werden. Außerdem können Sie fehlgeschlagene Testfälle aus der letzten Ausführung speichern und diese zuerst gegen eine neuere Codeversion erneut ausführen. Dies ermöglicht einen schnellen Iterationszyklus während der Entwicklung und ein langsames, umfassendes Testen außerhalb des Bandes / in CI.

Hier ist ein (dummes, fehlgeschlagenes) Beispiel zum Testen der sumFunktion:

@given(lists(floats()))
def test_sum(alist):
    result = sum(alist)
    assert isinstance(result, float)
    assert result > 0
  • Das Test-Framework generiert jeweils eine Liste
  • Inhalt der Liste sind Gleitkommazahlen
  • sum wird aufgerufen und Eigenschaften des Ergebnisses werden validiert
  • Ergebnis ist immer float
  • Ergebnis ist positiv

Dieser Test findet eine Reihe von "Bugs" in sum(Kommentar, wenn Sie in der Lage waren, all diese selbst zu erraten ):

  • sum([]) is 0 (int, kein float)
  • sum([-0.9]) ist negativ
  • sum([0.0]) ist nicht streng positiv
  • sum([..., nan]) is nan das ist nicht positiv

hpythesisBricht den Test mit den Standardeinstellungen ab, nachdem 1 "schlechte" Eingabe gefunden wurde, was für TDD gut ist. Ich dachte, es wäre möglich, es so zu konfigurieren, dass es viele / alle "schlechten" Eingaben meldet, aber ich kann diese Optionen jetzt nicht finden.

Im OP-Fall sind die validierten Eigenschaften komplexer: Inspektionsart A vorhanden, Inspektionsart A dreimal pro Woche, Inspektionszeit B immer um 12 Uhr, Inspektionsart C von 9 bis 9, [vorgegebener Zeitplan für eine Woche] Inspektionsarten A, B, C alle anwesend usw.

Die bekannteste Bibliothek ist QuickCheck für Haskell. Eine Liste solcher Bibliotheken in anderen Sprachen finden Sie auf der folgenden Wikipedia-Seite:

https://en.wikipedia.org/wiki/QuickCheck

Hypothese (für Python) hat eine gute Zusammenfassung über diese Art von Tests:

https://hypothesis.works/articles/what-is-property-based-testing/


-1

Was Sie testen können ist, dass die Logik feststellt, ob die zufälligen Daten gültig sind oder ob ein anderes zufälliges Datum ausgewählt werden muss.

Es gibt keine Möglichkeit, einen Generator für zufällige Daten zu testen, ohne eine Reihe von Daten zu erhalten und zu entscheiden, ob sie geeignet zufällig sind.


-1

Ihr Ziel ist es nicht, Komponententests zu schreiben und diese zu bestehen, sondern sicherzustellen, dass Ihr Programm den Anforderungen entspricht. Sie können dies nur tun, indem Sie Ihre Anforderungen genau definieren. Zum Beispiel haben Sie "drei wöchentliche Inspektionen zu zufälligen Zeiten" erwähnt. Ich würde sagen, die Anforderungen sind: (a) 3 Inspektionen (nicht 2 oder 4), (b) zu Zeiten, die von Personen, die nicht unerwartet inspiziert werden möchten, nicht vorhersehbar sind, und (c) nicht zu nahe beieinander - Zwei Inspektionen im Abstand von fünf Minuten sind wahrscheinlich sinnlos, vielleicht auch nicht zu weit voneinander entfernt.

Sie schreiben die Anforderungen also genauer auf als ich. (a) und (c) sind einfach. Für (b) könnten Sie einen Code schreiben, der so clever wie möglich ist und versucht, die nächste Inspektion vorherzusagen, und um den Komponententest zu bestehen, darf dieser Code nicht besser als reine Vermutungen vorhersagen können.

Und natürlich müssen Sie sich bewusst sein , dass , wenn Ihre Kontrollen wirklich zufällig sind, jeder Vorhersage - Algorithmus könnte rein zufällig richtig sein, so dass Sie sicher sein , dass Sie und Ihre Unit - Tests nicht in Panik geraten , wenn das passiert. Vielleicht noch ein paar Tests durchführen. Ich würde mich nicht darum kümmern, den Zufallszahlengenerator zu testen, denn am Ende zählt der Inspektionsplan, und es spielt keine Rolle, wie er erstellt wurde.


Nein einfach nein. Die Unit-Tests belegen, dass das Programm seinen Anforderungen entspricht, sodass beide gleich sind. Und ich schreibe keine Predictive-Software, um zufällige Algorithmen rückzuentwickeln. Wenn ich es Ihnen nicht erzählen würde, würde ich sicherheitsrelevante Websites töten, indem ich deren Schlüssel vorhersage und die Geheimnisse an den Höchstbietenden verkaufe. Mein Unternehmen schreibt einen Zeitplan, der Zeiten erstellt, die einschränkbar, aber nicht vorhersehbar sind, und ich benötige deterministische Tests, um zu beweisen, dass ich dies getan habe, und keine probabilistischen Tests, die besagen, dass ich ziemlich sicher bin.
KeithS
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.