2016-04-12 3 views
3

Я работаю над приложением Rails и пытаюсь применить TDD (используя RSpec). У меня есть файл в моем каталоге lib, который содержит список строк, и метод, который будет читать этот файл и случайным образом выбирать одну из строк из списка. Я еще не реализовал этот метод, потому что я изо всех сил пытаюсь написать тест на эту функциональность.Как проверить, что элемент случайно выбран из списка?

Существует множество способов случайного выбора объекта из массива и множество замечательных ответов, например this one, которые расскажут мне, как это сделать (когда дело доходит до реализации, я, вероятно, буду использовать Array#sample). Но каково должно быть мое ожидание? Я имею в виду что-то вроде:

expect(array).to include(subject.random_select)

Это, конечно, утверждать, что некоторые ожидаемое значение возвращается из моего метода - но достаточно, чтобы утверждать, что метод случайным образом возвращает другую строку каждый раз? Какие были бы альтернативы или, возможно, дополнительные тесты, которые гарантировали бы, что у меня есть покрытие для этого метода? Я не могу ожидать subject.random_select, чтобы подделать поддельный вход, не так ли?

+0

Является ли файловая часть (например, отправленной) системой, которую вы тестируете, и имеет ли она известный контент? Или он предоставляется пользователем и может содержать что угодно? –

ответ

4

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

  • теста, что ваш метод возвращает значение в желаемом диапазоне, зная, что, так как ваш тест будет работать много, много раз в течение в течение жизни вашего приложения вы, вероятно, узнаете, когда-нибудь выйдет что-то вне диапазона, и
  • докажите, что ваш код использует источник случайности.

Предположим, что файл не существует в тестовой среде, или вы не знаете его содержимого, или не хотите, чтобы его асимметрия была правильной для одного теста и некорректна для других, поэтому нам нужно предоставить способ тестирования для класса в разных файлах.

Мы могли бы написать следующее, написание одного теста за раз, заставляя его проходить и рефакторинг, прежде чем писать следующее. Вот тесты и код после третьего тест написан, но прежде, чем это было реализовано:

спецификации/модель/thing_spec.rb

describe Thing do 
    describe '.random_select' do 
    it "returns a single line from a file with only one line" do 
     allow(Thing).to receive(:file) { "spec/models/thing/1" } 
     expect(Thing.random_select).to eq("Thing 1") 
    end 

    it "returns a single line from a file with multiple lines" do 
     allow(Thing).to receive(:file) { "spec/models/thing/2" } 
     expect(Thing.random_select).to be_in(['Thing 1', 'Thing 2']) 
    end 

    it "returns different lines at different times" do 
     allow(Thing).to receive(:file) { "spec/models/thing/2" } 
     srand 0 
     thing1 = Thing.random_select 
     srand 1 
     thing2 = Thing.random_select 
     expect(thing1).not_to eq(thing2) 
    end 

    end 
end 

приложения/модель/thing.rb

class Thing 
    def self.random_select 
    "Thing 1" # this made the first two tests pass, but it'll need to change for all three to pass 
    end 

    def self.file 
    "lib/things" 
    end 

end 

Когда я написал второй тест, я понял, что он прошел без каких-либо дополнительных изменений кода, поэтому я решил удалить его. Но я отложил это решение, написал третий тест и обнаружил, что, как только третий тест пройдет второй, будет иметь значение, так как второй тест проверяет, что значение получено из файла, но третий тест этого не делает.

be_in это красивый способ, чтобы проверить, что возвращаемое значение в известном наборе чем include, поскольку он ставит фактическое значение внутри expect где RSpec ожидает.

Существуют и другие способы контроля случайности, поэтому вы можете проверить, что она используется. Например, если вы использовали sample, вы могли бы allow_any_instance_of(Array).to receive(:sample) и вернуть все, что захотите. Но мне нравится использовать srand, поскольку для реализации не требуется использование определенного метода, использующего генератор случайных чисел.

Если файл может отсутствовать или пуст, вам также нужно будет его проверить.

0

Похоже, вы на правильном пути. Я бы дополнительно включил фальшивый «образец» из массива и ожидал, что массив до не включает этот образец.

See these docs, например: expect(array).not_to include("fake sample")

+0

Хм, я пытаюсь проверить метод 'subject.random_select'. Я не очень обеспокоен тем, что включает массив, поэтому я не уверен, что массив, не содержащий поддельный образец, добавит значение в этот набор тестов. – casto101

2

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

  1. выделит работу чтения записей в массив, и задание выбора случайный элемент из массива.

  2. Если вы не хотите, чтобы полагаться на файловой системе в ваших тестах, вы можете написать метод для работы с общим IO объектом (или потоком на других языках), а затем использовать в своих тестах на StringIO , У вас будет простая функция пересылки, которая открывает файл и передает открытый файл методу, который работает с IO. Например:

    def read_entries(file) 
        File.open(file) { |io| read_entries_from_io(io) } 
    end 
    
    def read_entries_from_io(io) 
        # ... do the work ... 
    end 
    
    # In your spec: 
    io = StringIO.new("Entry1\nEntry2\nEntry3\n") 
    expect(read_entries_from_io(io)).to eq %w[Entry1 Entry2 Entry3] 
    
  3. Довольно много каждый метод Ruby, что делает что-то случайное (как sample и shuffle) принимает необязательный аргумент random: ключевое слово, которое позволяет поставить свой собственный генератор случайных чисел. Если ваш метод следует той же конвенции, вы можете придать фальшивый генератор случайных чисел от ваших тестов, возвращающий жестко закодирована последовательностью поддельных случайных чисел:

    def random_select(entries, random: Random.new) 
        entries.sample(random: random) 
    end 
    
    # In the spec: 
    
    entries = %w[Entry0 Entry1 Entry2 Entry3] 
    random = instance_double(Random) 
    allow(random).to receive(:rand).and_return(2, 0) 
    
    expect(random_select(entries, random: random)).to eq 'Entry2' 
    expect(random_select(entries, random: random)).to eq 'Entry0' 
    

Примечания: я бы не стал на самом деле имеют два ожидания в этом последнем тесте; Я просто включил его, чтобы показать, как можно вернуть последовательность случайных значений.

Кроме того, в этом случае тест делает предположение о том, как sample использует генератор случайных чисел. Это может быть нормально с sample, но, вероятно, не с shuffle.

Смежные вопросы