it-roy-ru.com

Как написать тест, который перебирает диапазон значений, используя RSpec?

У меня есть очень простая Ruby реализация игры под названием "FizzBuzz" (т. Е. При вводе номера возвращается "Fizz", если число кратно 3, "Buzz", если кратно 5 "," FizzBuzz "). "если кратно и то, и другое, если оно не соответствует ни одному из предыдущих условий):

class FizzBuzz
    def answer(number)
        multiple3 = number%3 == 0
        multiple5 = number%5 == 0
        return case
        when (multiple3 and multiple5) then "FizzBuzz"
        when multiple3 then "Fizz"
        when multiple5 then "Buzz"
        else number
        end
    end
end

Я написал тест, используя RSpec, чтобы проверить каждое из условий:

require "rspec"
require "./fizzBuzz"

RSpec.describe "#answer" do
    it "returns Buzz when number is multiple of 3" do 
        result = FizzBuzz.new.answer(3)
        expect(result).to eq("Fizz")
    end

    it "returns Buzz when number is multiple of 5" do 
        result = FizzBuzz.new.answer(5)
        expect(result).to eq("Buzz")
    end

    it "returns a number when the input number is neither multiple of 3 nor 5" do 
        result = FizzBuzz.new.answer(11)
        expect(result).to eq(11)
    end

end

Тест работает отлично, однако я использую в нем конкретные значения (то есть 3, 5 и 11).

У меня вопрос: что если я захочу протестировать свой скрипт FizzBuzz Ruby, используя широкий диапазон значений (например, от 1 до 10000)?

Я знаю, что могу решить эту проблему, используя каждый цикл и регистр непосредственно в RSpec, однако меня беспокоит то, что если в моем тесте я приму те же условные операторы, что и в тестируемом скрипте Ruby (например, when number%3 == 0 then "Fizz", и т. д.) Я закончу тестирование своего кода с использованием сценария RSpec, который следует точно такой же логике, что и сценарий, подлежащий тестированию, поэтому тест, вероятно, пройдет успешно.

Какой будет альтернатива? Существуют ли передовые практики для написания тестов с использованием широкого пула значений (например, с использованием цикла), а не с жестко запрограммированными или конкретными значениями?

7
Albz

Возможная промежуточная точка здесь - циклически проходить возможные ответы в ваших тестах RSpec. Сохранение вашего кода DRY важно, но также важно поддерживать ваши тесты DRY, и это иногда недооценивается.

Как насчет чего-то вроде этого:

  RSpec.describe "#answer" do
      expected_values = {'3': 'Fizz', '5': 'Buzz', '6': 'Fizz', '11': '11', '15': 'FizzBuzz'}
      expected_values.each do |val, expected| 
        it "returns #{expected} when number is #{val}" do 
            result = FizzBuzz.new.answer(val.to_i)
            expect(result).to eq(expected)
        end
      end  
    end

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

16
Yule

Я сделаю вид, что вы спрашиваете, нужна ли вам эта помощь, и этот простой случай только для иллюстрации. 

Существует тестирование на основе свойств, которое может решить проблему такого рода элегантным способом, при таком подходе вы должны найти какое-либо свойство (например, при добавлении двух чисел результат превосходит два числа и a + b = b + а ...). И вы будете использовать фреймворк, который будет случайным образом генерировать записи, чтобы охватить большие спектры, чем если бы это были конкретные случаи.

Есть фреймворк, который может помочь и в Ruby. 

Отличное объяснение тестирования на основе свойств http://fsharpforfunandprofit.com/posts/property-based-testing/ (отказ от ответственности в коде Fsharp)

1
rad

Вы можете использовать циклы внутри своих спецификаций:

RSpec.describe "#answer" do

  RESULTS = { 3 => "Fizz",
              5 => "Buzz",
              11 => 11 }

  RESULTS.each do |value, answer|
    it "returns #{answer} when the input is #{value}" do 
      result = FizzBuzz.new.answer(value)
      expect(result).to eq(answer)
    end
  end

end
1
bukk530

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

RSpec.describe "#answer" do

  context 'when testing inputs and answers' do

    RESULTS = { 3 => "Fizz",
                5 => "Buzz",
                11 => 11 }

    RESULTS.each do |value, answer|
      it "returns #{answer} when the input is #{value}" do 
        result = FizzBuzz.new.answer(value)
        expect(result).to eq(answer)
      end
    end

  end

end
0
emc

RSpec теперь имеет встроенные интересные функции, такие как общие примеры. Больше информации здесь.

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

shared_examples "shared example" do |number, result|
  it "returns #{result} when accepts #{number}" do
    # implicit `subject` here is equal to FizzBuzz.new
    expect(subject.answer(number)).to eq(result)
  end
end

И тесты будут более читабельными и написаны более «rspec-like» способом:

DATA_SET = {15 => 'FizzBuzz', 3 => 'Fizz', 5 => 'Buzz', 11 => 11}

RSpec.describe FizzBuzz do
  context "#answer" do
    DATA_SET.each do |number, result|
      # pseudo-randomizing test data.
      number = [1,2,4,7,8,11,13,14,16].sample*number
      result = number if result.is_a?(Integer)
      include_examples "shared example", number, result
    end
  end
end
0
Sergii K