Какой лучший способ проверить это?
Я иду через EdgeCase Ruby Koans. В about_dice_project.rb существует тест под названием "test_dice_values_should_change_between_rolls", который прост:
def test_dice_values_should_change_between_rolls
dice = DiceSet.new
dice.roll(5)
first_time = dice.values
dice.roll(5)
second_time = dice.values
assert_not_equal first_time, second_time,
"Two rolls should not be equal"
end
За исключением этого комментария, который появляется там:
# THINK ABOUT IT:
#
# If the rolls are random, then it is possible (although not
# likely) that two consecutive rolls are equal. What would be a
# better way to test this.
Что (очевидно) заставило меня задуматься: какой лучший способ надежно проверить что-то случайное (в частности, и вообще)?
Ответы
Ответ 1
Я бы сказал, что лучший способ проверить все, что связано с случайностью, статистически. Запустите функцию кубика в цикле миллион раз, скопируйте результаты, а затем запустите некоторые тесты гипотез по результатам. Миллион образцов должны дать вам достаточную статистическую мощность, что будут замечены почти любые отклонения от правильного кода. Вы хотите продемонстрировать два статистических свойства:
- Вероятность каждого значения - это то, что вы намеревались сделать.
- Все ролики являются независимыми друг от друга событиями.
Вы можете проверить, являются ли частоты рулонов кости приблизительными, используя тест Пирсона Чи-квадрат. Если вы используете хороший случайный генератор nunber, такой как Mersenne Twister (который по умолчанию используется в стандартном lib для большинства современных языков, хотя и не для C и С++), и вы не используете какое-либо сохраненное состояние из предыдущих рулонов, кроме самого генератора Mersenne Twister, тогда ваши ролики практически не зависят друг от друга.
В качестве еще одного примера статистического тестирования случайных функций, когда я портировал генераторы случайных чисел NumPy на язык программирования D, мой тест на то, порт был правильным, должен был использовать тест Колмогорова-Смирнова, чтобы узнать, соответствуют ли числа, связанные с вероятностными распределениями, которые они должны были соответствовать.
Ответ 2
ИМХО большинство ответов до сих пор пропустили точку вопроса Koan, за исключением @Super_Dummy. Позвольте мне подробно остановиться на моем мышлении...
Скажите, что вместо кубиков мы переворачивали монеты. Добавьте еще одно ограничение только на использование одной монеты в нашем наборе, и у нас есть минимальное нетривиальное множество, которое может генерировать "случайные" результаты.
Если бы мы хотели проверить, что сбрасывание "набора монет" [в данном случае одной монеты] каждый раз порождало другой результат, мы ожидали бы, что значения каждого отдельного результата будут равны 50% времени, на статистическая основа. Выполнение этой unit test через n итераций для некоторого большого n будет просто осуществлять PRNG. Он ничего не говорит о сути о фактическом равенстве или различии между этими двумя результатами.
Другими словами, в этом Коане мы фактически не заботимся о значениях каждого броска кубиков. Мы действительно больше обеспокоены тем, что возвращенные ролики на самом деле представляют собой разные ролики. Проверка того, что возвращаемые значения различны, - это только проверка первого порядка.
В большинстве случаев этого будет достаточно - но очень редко случайность может привести к сбою вашего unit test. Это не хорошая вещь.
Если в случае, когда два последовательных рулона возвращают одинаковые результаты, мы должны проверить, что два результата фактически представлены разными объектами. Это позволило бы нам повторно преобразовать код в будущем [если это было необходимо], будучи уверенным, что тесты все равно всегда поймают любой код, который не ведет себя корректно.
TL; DR?
def test_dice_values_should_change_between_rolls
dice = DiceSet.new
dice.roll(5)
first_time = dice.values
dice.roll(5)
second_time = dice.values
assert_not_equal [first_time, first_time.object_id],
[second_time, second_time.object_id], "Two rolls should not be equal"
# THINK ABOUT IT:
#
# If the rolls are random, then it is possible (although not
# likely) that two consecutive rolls are equal. What would be a
# better way to test this.
end
Ответ 3
Невозможно написать тест на основе состояния для случайности. Они противоречивы, поскольку государственные тесты проходят путем предоставления известных входов и проверки выходных данных. Если ваш вход (случайное семя) неизвестен, нет возможности проверить.
К счастью, вы действительно не хотите протестировать реализацию rand для Ruby, поэтому вы можете просто отключить его с ожиданием, используя mocha.
def test_roll
Kernel.expects(:rand).with(5).returns(1)
Diceset.new.roll(5)
end
Ответ 4
Кажется, здесь есть 2 отдельных подразделения. Во-первых, генератор случайных чисел. Во-вторых, абстракция "кости", в которой используется (P) RNG.
Если вы хотите unit test абстракцию кубиков, затем извините вызовы PRNG и убедитесь, что они называет их, и возвращает соответствующее значение для ввода, который вы даете, и т.д.
PRNG, вероятно, является частью вашей библиотеки/рамки/ОС, поэтому я бы не стал ее тестировать. Возможно, вам понадобится тест интеграции, чтобы узнать, возвращает ли он разумные значения, но это целая проблема.
Ответ 5
Вместо сравнения значений сравните object_id
:
assert_not_equal first_time.object_id, second_time.object_id
Это предполагает, что другие тесты будут проверять массив целых чисел.
Ответ 6
Мне кажется, немного глупо. Вы должны тестировать, что генератор случайных чисел (psuedo) генерирует случайные числа? Это бесполезно и бессмысленно. Во всяком случае, вы можете проверить, что вызовы dice.roll на ваш PRNG.
Ответ 7
Мое решение состояло в том, чтобы разрешить отправку блока в функцию roll.
class DiceSet
def roll(n)
@values = (1..n).map { block_given? ? yield : rand(6) + 1 }
end
end
Затем я могу передать свой собственный RNG в такие тесты.
dice = DiceSet.net
dice.roll(5) { 1 }
first_result = dice.values
dice.roll(5) { 2 }
second_result = dice.values
assert_not_equal first_result, second_result
Я не знаю, действительно ли это лучше, но он абстрагирует призывы к RNG. И это не меняет стандартные функции.
Ответ 8
Просто создайте новый массив каждый раз, когда вы вызываете метод roll. Таким образом вы можете использовать
assert_not_same first_time, second_time,
"Two rolls should not be equal"
для проверки равенства object_id.
Да, этот тест зависит от реализации, но нет возможности проверить случайность.
Другой подход заключается в том, чтобы использовать mocks как предложено floyd.
Ответ 9
Я решил проблему с помощью рекурсии:
def roll times, prev_roll=[]
@values.clear
1.upto times do |n|
@values << rand(6) + 1
end
roll(times, prev_roll) if @values == prev_roll
end
И пришлось добавить к тестовой переменной метод dup, поэтому он не передает ссылку на мою переменную экземпляра @values .
def test_dice_values_should_change_between_rolls
dice = DiceSet.new
dice.roll(5)
first_time = dice.values.dup
dice.roll(5, first_time)
second_time = dice.values
assert_not_equal first_time, second_time,
"Two rolls should not be equal"
end
Ответ 10
Я только что создал новый экземпляр
def test_dice_values_should_change_between_rolls
dice1 = DiceSet.new
dice2 = DiceSet.new
dice1.roll(5)
first_time = dice1.values.dup
dice2.roll(5, first_time)
second_time = dice2.values
assert_not_equal first_time, second_time,
"Two rolls should not be equal"
end
Ответ 11
Я решил это, просто создав новый набор значений для каждой кости в любое время, когда вызывается метод "roll":
def roll(n)
@numbers = []
n.times do
@numbers << rand(6)+1
end
end
Ответ 12
rand детерминирован и зависит от его семени. Используйте srand с заданным номером перед первым броском и srand с другим номером перед вторым броском. Это предотвратит повторение серии.
srand(1)
dice.roll(5)
first_time = dice.values
srand(2)
dice.roll(5)
second_time = dice.values
assert_not_equal first_time, second_time,
"Two rolls should not be equal"