Как проверить `rand()` с RSpec?
У меня есть метод, который делает что-то вроде этого:
def some_method
chance = rand(4)
if chance == 1 do
# logic here
else
# another logic here
end
end
Когда я использую RSpec для тестирования этого метода, rand(4)
внутри него всегда генерирует 0. Я не тестирую метод t22 > Rails, я тестирую свой метод.
Что такое обычная практика для тестирования моего метода?
Ответы
Ответ 1
Есть два подхода, которые я хотел бы рассмотреть:
Подход 1:
Используйте известное значение семени в srand( seed )
в блоке before :each
:
before :each do
srand(67809)
end
Это работает во всех версиях Ruby и дает вам возможность контролировать, если вы хотите охватить определенные комбинации. Я очень много использую этот подход, думая об этом, потому что, поскольку тестируемый мной код использует rand()
прежде всего как источник данных и только вторично (если вообще) для ветвления. Кроме того, он получает много имен, поэтому принудительное управление по возвращенным значениям будет контрпродуктивным, я бы закончил сгребать множество тестовых данных, которые "выглядели случайными", вероятно, генерируя их в первую очередь, вызывая rand()
Вы можете запросить свой метод несколько раз, по крайней мере, в одном тестовом сценарии, чтобы обеспечить разумное покрытие комбинаций.
Подход 2:
Если у вас есть точки ветвления из-за значений, выводимых из rand()
, и ваши утверждения относятся к типу "если он выбирает X, то Y должен произойти", тогда в этом же наборе тестов также разумно издеваться rand( n )
с чем-то, возвращающим значения, которые вы хотите сделать утверждениями:
require 'mocha/setup'
Kernel.expects(:rand).with(4).returns(1)
# Now run your test of specific branch
В сущности, это оба теста "белого ящика", они оба требуют, чтобы вы знали, что ваша программа использует rand()
внутренне.
Тест "черного ящика" намного сложнее - вам нужно будет утверждать, что поведение статистически нормально, и вам также необходимо принять очень широкий диапазон возможностей, поскольку допустимое случайное поведение может привести к ошибкам тестирования phantom.
Ответ 2
Я бы извлек генерацию случайных чисел:
def chance
rand(4)
end
def some_method
if chance == 1 do
# logic here
else
# another logic here
end
end
И окуните его:
your_instance.stub(:chance) { 1 }
Это не привязывает ваш тест к деталям реализации rand
, и если вы когда-нибудь решите использовать другой генератор случайных чисел, ваш тест не сломается.
Ответ 3
Кажется, что лучше всего использовать заглушку вместо реального rand
. Таким образом, вы сможете протестировать все значения, которые вас интересуют. Поскольку rand
определяется в модуле Kernel
, вы должны его заглушить, используя:
Kernel.stub(:rand).with(anything) { randomized_value }
В конкретных контекстах вы можете определить randomized_value
с помощью метода let
.
Ответ 4
Я обнаружил, что просто обрезание rand ie. используя Kernel.stub(: rand), как ответил Самуил, изначально не работал. Мой код, который будет протестирован, называется rand, например,
random_number = rand
Однако, если я изменил код на
random_number = Kernel.rand
тогда работа была выполнена.
Ответ 5
Это работает в RSpec:
allow_any_instance_of(Object).to receive(:rand).and_return(1)
Ответ 6
Иногда бывает трудно заглушить объекты, находящиеся глубоко внутри другого. Поэтому я считаю, что этот подход помогает упростить вещи:
class Worker
def self.rand(*args)
# Calls the actual rand method
rand(*args)
end
def perform
# Calls private method rand -> Worker.rand
rand(1..5)
end
private
def rand(*args)
self.class.rand(*args)
end
end
Это позволяет нам с легкостью заглушить:
allow(Worker).to receive(:rand).and_return(2)
expect(Worker.new.perform).to eq 2