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:
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.