如何使用 RSpec 编写循环遍历一系列值的测试?
How to write a test which loops through a range of values using RSpec?
我有一个非常简单的 Ruby 游戏实现,叫做 "FizzBuzz"(即给定一个输入数字,如果数字是 3 的倍数,它 returns "Fizz" , "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)。
我的问题是:如果我想使用广泛的值(例如从 1 到 10000)测试我的 FizzBuzz Ruby 脚本怎么办?
我知道我可以通过直接在 RSpec 中使用每个循环和案例来解决这个问题,但是,我担心的是如果在我的测试中我采用与 Ruby 脚本中相同的条件语句待测试(即 when number%3 == 0 then "Fizz"
等)我将使用 RSpec 脚本测试我的代码,该脚本遵循与要测试的脚本完全相同的逻辑,因此测试可能会成功通过。
还有什么选择?是否有使用大量值(例如使用循环)而不是硬编码或特定值来编写测试的最佳实践?
这里的一个可能 half-way 点是循环遍历 RSpec 测试中的可能答案。保持代码干燥很重要,但保持测试干燥也很重要,有时 under-estimated.
这样的事情怎么样:
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
散列来轻松添加测试,但是如果方法名称更改或类似的东西,您只需更改一个地方
我会假装你在问,以防你真的需要它,这个简单的例子只是为了说明。
有 属性 Based Testing 可以优雅地解决此类问题,使用这种方法,您必须找到一些 属性(例如,将两个数字相加结果优于两个数字和 a + b = b + a ...)。您将使用一个框架,该框架将随机生成条目,以便覆盖比特定情况下更大的范围。
在 Ruby 中也有可以提供帮助的框架。
属性 基于测试的出色解释http://fsharpforfunandprofit.com/posts/property-based-testing/(免责声明代码在 Fsharp 中)
您可以在规格中使用循环:
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
RSpec 现在有 built-in 很酷的功能,比如共享示例。更多信息 here.
结果我们会得到这样的共享组(注意,这个组可以用于其他测试):
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
为了支持已接受的答案,我建议将更广泛的测试包装在上下文块中,以便测试与其他代码保持隔离:
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
我有一个非常简单的 Ruby 游戏实现,叫做 "FizzBuzz"(即给定一个输入数字,如果数字是 3 的倍数,它 returns "Fizz" , "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)。
我的问题是:如果我想使用广泛的值(例如从 1 到 10000)测试我的 FizzBuzz Ruby 脚本怎么办?
我知道我可以通过直接在 RSpec 中使用每个循环和案例来解决这个问题,但是,我担心的是如果在我的测试中我采用与 Ruby 脚本中相同的条件语句待测试(即 when number%3 == 0 then "Fizz"
等)我将使用 RSpec 脚本测试我的代码,该脚本遵循与要测试的脚本完全相同的逻辑,因此测试可能会成功通过。
还有什么选择?是否有使用大量值(例如使用循环)而不是硬编码或特定值来编写测试的最佳实践?
这里的一个可能 half-way 点是循环遍历 RSpec 测试中的可能答案。保持代码干燥很重要,但保持测试干燥也很重要,有时 under-estimated.
这样的事情怎么样:
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
散列来轻松添加测试,但是如果方法名称更改或类似的东西,您只需更改一个地方
我会假装你在问,以防你真的需要它,这个简单的例子只是为了说明。
有 属性 Based Testing 可以优雅地解决此类问题,使用这种方法,您必须找到一些 属性(例如,将两个数字相加结果优于两个数字和 a + b = b + a ...)。您将使用一个框架,该框架将随机生成条目,以便覆盖比特定情况下更大的范围。
在 Ruby 中也有可以提供帮助的框架。
属性 基于测试的出色解释http://fsharpforfunandprofit.com/posts/property-based-testing/(免责声明代码在 Fsharp 中)
您可以在规格中使用循环:
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
RSpec 现在有 built-in 很酷的功能,比如共享示例。更多信息 here.
结果我们会得到这样的共享组(注意,这个组可以用于其他测试):
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
为了支持已接受的答案,我建议将更广泛的测试包装在上下文块中,以便测试与其他代码保持隔离:
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