Мой текущий проект, кратко, включает создание «случайно-случайных событий». Я в основном генерирую график проверок. Некоторые из них основаны на строгих расписаниях; Вы проводите проверку один раз в неделю в пятницу в 10:00. Другие проверки являются «случайными»; Существуют основные настраиваемые требования, такие как «проверка должна проводиться 3 раза в неделю», «проверка должна проводиться в период с 9 до 9 часов вечера» и «не должно быть двух проверок в течение одного 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 (удаление наиболее значимого бита из номера один за раз, пока он не окажется в пределах диапазона) лучше, но опять же, поскольку вы отбираете очень большие числа с каждым битом, он распределяется неравномерно; маловероятно, что вы получите числа в верхнем и нижнем концах, поэтому вы получаете уклон в сторону средней трети. Это может принести пользу кому-то, кто хочет «нормализовать» случайные данные в кривую округлости, но сумма двух или более случайных чисел меньшего размера (аналогично бросанию игральных костей) даст вам более естественную кривую. Для моих целей это не удается.
Единственным, кто прошел этот тест, было ограничение по модулю, которое также оказалось самым быстрым из трех. По определению, модуль будет производить как можно более равномерное распределение с учетом доступных входных данных.