Подтвердить что ты не робот

Как проверить `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, я тестирую свой метод. Что такое обычная практика для тестирования моего метода?

4b9b3361

Ответ 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