Ruby: использование rand () в коде, но написание тестов для проверки вероятностей - PullRequest
7 голосов
/ 21 мая 2011

У меня есть код, который доставляет вещи на основе взвешенного случайного числа.Вещи с большим весом чаще выбираются случайным образом.Теперь, будучи хорошим рубиистом, я хочу покрыть весь этот код тестами.И я хочу проверить, что вещи выбираются в соответствии с правильными вероятностями.

Итак, как мне это проверить?Создание тестов для чего-то, что должно быть случайным, очень затрудняет сравнение с ожидаемым.Несколько идей, которые у меня есть, и почему они не будут отлично работать:

  • Stub Kernel.rand в моих тестах для возврата фиксированных значений.Это круто, но rand () вызывается несколько раз, и я не уверен, что смогу настроить это с достаточным контролем, чтобы проверить, что мне нужно.

  • Получить случайный элемент ОГРОМНОколичество раз и сравните фактическое соотношение с ожидаемым.Но если я не смогу выполнить его бесконечное число раз, это никогда не будет идеальным и может периодически потерпеть неудачу, если мне повезет в ГСЧ.

  • Используйте последовательное случайное начальное число.Это делает повторяемость ГСЧ, но все равно не дает мне никакой уверенности в том, что пункт А будет происходить в 80% случаев (например).

Так, какой подход я могу использоватьнаписать тестовое покрытие для случайных вероятностей?

Ответы [ 6 ]

9 голосов
/ 21 мая 2011

Я думаю, вы должны отделить свои цели. Один из них - заглушить ядро. И как вы упомянули. С rspec, например, вы можете сделать что-то вроде этого:

test_values = [1, 2, 3]
Kernel.stub!(:rand).and_return( *test_values )

Обратите внимание, что эта заглушка не будет работать, если вы не вызовете rand с Kernel в качестве получателя. Если вы просто позвоните «rand», тогда текущее «self» получит сообщение, и вы получите случайное число вместо test_values.

Вторая цель - сделать что-то вроде полевого теста, в котором вы на самом деле генерируете случайные числа. Затем вы должны использовать некоторую терпимость, чтобы убедиться, что вы приблизились к желаемому проценту. Это никогда не будет идеальным, хотя, вероятно, потребуется человек, чтобы оценить результаты. Но это все же полезно сделать, потому что вы можете понять, что другой генератор случайных чисел может быть лучше, например, чтение из / dev / random. Кроме того, хорошо иметь такой вид тестирования, потому что, допустим, вы решили перейти на платформу нового типа, системные библиотеки которой не так хороши в генерации случайности, или в определенной версии есть какая-то ошибка. Тест может быть предупреждающим знаком.

Это действительно зависит от ваших целей. Вы хотите проверить только свой алгоритм взвешивания или случайность?

8 голосов
/ 21 мая 2011

Лучше всего заглушить Kernel.rand для возврата фиксированных значений.

Ядро.и не твой код. Вы должны предположить, что это работает, а не пытаться писать тесты, которые проверяют это, а не ваш код. А использовать фиксированный набор значений, которые вы выбрали и явно закодировали, лучше, чем добавлять зависимость от того, что производит rand для конкретного семени.

3 голосов
/ 21 мая 2011

Если вы хотите пойти по последовательному начальному маршруту, посмотрите на Kernel#srand:

http://www.ruby -doc.org / ядро ​​/ классов / Kernel.html # M001387

Цитировать документы (выделение добавлено):

Семена псевдослучайного числа генератор на значение числа. Если число опущено или ноль, семена генератор с использованием комбинации время, идентификатор процесса и последовательность число. (Это также поведение, если Kernel :: rand вызывается без ранее звонил сранд, но без последовательность.) путем установки семян к известному значению, скрипты могут быть сделаны детерминирован во время тестирования. предыдущее начальное значение возвращается. Также см. Kernel :: rand.

0 голосов
/ 26 апреля 2019

Довольно часто, когда мне нужны предсказуемые результаты чего-то, полученного из случайного числа, я обычно хочу контролировать ГСЧ, что означает, что проще всего сделать его инъекционным.Хотя переопределение / заглушение rand может быть выполнено, Ruby предоставляет прекрасный способ передать вашему коду ГСЧ, который заселен с некоторым значением:

def compute_random_based_value(input_value, random: Random.new)
   # ....
end

, а затем внедрить случайный объект, который я создаю на месте втест, с известным семенем:

rng = Random.new(782199) # Scientific dice roll
compute_random_based_value(your_input, random: rng)
0 голосов
/ 30 ноября 2018

Мое предложение: объединить № 2 и № 3.Установите случайное начальное число, а затем запускайте тесты очень много раз.

Мне не нравится # 1, потому что это означает, что ваш тест очень тесно связан с вашей реализацией.Если вы измените способ использования rand (), тест будет прерван, даже если результат верный.Суть модульного теста заключается в том, что вы можете реорганизовать метод и положиться на него, чтобы убедиться, что он все еще работает.

Опция # 3 сама по себе имеет ту же проблему, что и # 1.Если вы измените способ использования rand (), вы получите другие результаты.

Вариант № 2 - это единственный способ получить настоящее решение «черного ящика», которое не зависит от знания ваших внутренних элементов.Если вы запускаете его достаточно большое количество раз, вероятность случайного сбоя незначительна.(Вы можете найти учителя статистики, который поможет вам вычислить «достаточно высокое», или вы можете просто выбрать действительно большое число.)

Но если вы слишком требовательны и «незначительны», это нехорошодостаточно, комбинация # 2 и # 3 гарантирует, что после того, как тест начнет проходить, он будет продолжать проходить.Даже этот незначительный риск сбоя возникает только при прикосновении к тестируемому коду;пока вы оставляете код в покое, вы гарантируете, что тест всегда будет работать правильно.

0 голосов
/ 21 мая 2011

Для тестирования заглушите Kernel.and со следующим простым, но совершенно разумным LCPRNG:

@@q = 0
def r
  @@q = 1_103_515_245 * @@q + 12_345 & 0xffff_ffff
  (@@q >> 2) / 0x3fff_ffff.to_f
end

Возможно, вы захотите пропустить деление и использовать целочисленный результат напрямую, если ваш код совместим, поскольку тогда все биты результата будут повторяемыми, а не просто «большинством из них». Это изолирует ваш тест от «улучшений» Kernel.rand и должно позволить вам протестировать кривую распределения.

...