web-dev-qa-db-ja.com

RSpecを使用して値の範囲をループするテストを作成するにはどうすればよいですか?

私は非常に単純なRuby「FizzBu​​zz」と呼ばれるゲームの実装を持っています(つまり、入力番号を指定すると、番号が3の倍数の場合は「Fizz」を返し、5の倍数の場合は「Buzz」を返します) 、両方の倍数の場合は「FizzBu​​zz」、前の条件のいずれにも当てはまらない場合は元の番号):

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)を使用しています。

私の質問は、FizzBu​​zz 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ハッシュに追加することで簡単にテストを追加できますが、メソッド名が変更された場合などは、1か所で変更するだけで済みます。

20
Yule

本当に必要な場合に備えて質問しているふりをします。この単純なケースは説明のためだけのものです。

この種の問題をエレガントな方法で解決できるプロパティベースのテストがあります。このアプローチでは、いくつかのプロパティを見つける必要があります(たとえば、2つの数値を追加すると、結果は2つの数値よりも優れ、a + b = b + a ...)。また、特定の場合よりも大きなスペクトルをカバーするために、エントリをランダムに生成するフレームワークを使用します。

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には、共有例のようなクールな機能が組み込まれています。詳細 こちら

その結果、次のような共有グループが作成されます(このグループは他のテストで使用できることに注意してください)。

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のような」方法で書かれるでしょう:

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
1
Sergii K

受け入れられた回答をサポートするために、テストを他のコードから分離したままにするために、より広いテストをコンテキストブロックでラップすることをお勧めします。

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