Мій поточний проект, лаконічно, передбачає створення "обмежено випадкових подій". Я в основному генерую графік перевірок. Деякі з них базуються на суворих обмеженнях графіку; Ви проводите огляд раз на тиждень у п’ятницю о 10:00 ранку. Інші перевірки є "випадковими"; є основні налаштовані вимоги, такі як "інспекція повинна відбуватися 3 рази на тиждень", "інспекція повинна відбуватися між годинами 9 AM-9PM", і "не повинно бути двох перевірок протягом одного 8-годинного періоду", але в межах будь-яких обмежень, налаштованих для певного набору перевірок, отримані дати та час не повинні бути передбачуваними.
Тестові одиниці та TDD, IMO мають велику цінність у цій системі, оскільки їх можна використовувати для поступового її побудови, поки її повний набір вимог ще неповний, і переконайтесь, що я не "переробляю" це для того, щоб робити те, що мені не подобається На даний момент я не знаю, що мені потрібно. Суворі графіки були торт-торт до TDD. Однак мені важко реально визначити те, що я тестую, коли пишу тести для випадкової частини системи. Я можу стверджувати, що всі часи, що виробляються планувальником, повинні підпадати під обмеження, але я міг би реалізувати алгоритм, який проходить усі такі тести, не будучи фактичними "випадковими" часом. Насправді саме так і сталося; Я знайшов проблему, коли час, хоча і не передбачуваний точно, потрапляв у невелику підмножину допустимих діапазонів дати / часу. Алгоритм все-таки передав усі твердження, які я вважав, що можу розумно висловитись, і не міг розробити автоматизований тест, який би вийшов з ладу в цій ситуації, але здав, коли давали "більш випадкові" результати. Мені довелося продемонструвати, що проблема була вирішена шляхом реструктуризації деяких існуючих тестів, щоб повторити їх кілька разів, і візуально перевірити, чи згенерований час потрапив у повний допустимий діапазон.
Хтось має поради щодо розробки тестів, які повинні очікувати недетермінованої поведінки?
Дякую всім за пропозиції. Основна думка здається, що мені потрібен детермінований тест, щоб отримати детерміновані, повторювані, підтверджувані результати . Має сенс.
Я створив набір тестів «пісочниці», що містять алгоритми-кандидати для обмежувального процесу (процес, за допомогою якого байтовий масив, який міг би бути будь-яким довгим, стає довгим між хв і макс. Потім я запускаю цей код через цикл FOR, який дає алгоритму кілька відомих байтових масивів (значення від 1 до 10 000 000 лише для початку), і алгоритм обмежує кожен до значення між 1009 і 7919 (я використовую прості числа для забезпечення алгоритм не проходив би через деякий безпрограшний GCF між вхідним та вихідним діапазонами). Отримані обмежені значення підраховуються та виробляється гістограма. Щоб "пройти", всі вхідні дані повинні бути відображені у гістограмі (обґрунтовано, щоб ми не "втратили" жодне), і різниця між будь-якими двома відрами в гістограмі не може бути більшою за 2 (вона дійсно повинна бути <= 1 , але будьте в курсі). Алгоритм виграшу, якщо такий є, можна вирізати та вставити безпосередньо у виробничий код та встановити постійний тест для регресії.
Ось код:
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;
}
... і ось результати:
Відхилення LSB (зміщення біта числом, поки воно не потрапить у діапазон) було ТЕРМІННО, з дуже простої для пояснення причини; коли ви розділите будь-яке число на 2, поки воно не буде меншим за максимум, ви вийдете, як тільки воно є, і для будь-якого нетривіального діапазону це змістить результати до верхньої третини (як це було видно в детальних результатах гістограми ). Саме таку поведінку я бачив із закінчених дат; всі часи були вдень, у дуже конкретні дні.
Відхилення MSB (видалення найбільш значущого біта за номер один за один раз, поки воно не буде в межах діапазону) краще, але знову ж таки, тому що ви відсікаєте дуже великі числа з кожного біта, воно не розподіляється рівномірно; Ви навряд чи отримаєте цифри у верхньому та нижньому кінцях, тому ви отримаєте ухил до середньої третини. Це може принести користь тому, хто хоче "нормалізувати" випадкові дані в криву дзвінку, але сума двох або більше менших випадкових чисел (подібних до кидання кісток) дасть вам більш природну криву. У моїх цілях це не вдається.
Єдиний, хто пройшов цей тест, був обмежений модульним поділом, який також виявився найшвидшим із трьох. Модуло, за своїм визначенням, виробляє максимально рівномірний розподіл, враховуючи наявні входи.