我正在开发 Rails 应用程序并尝试练习 TDD(使用 RSpec)。我的 lib 目录中有一个文件,其中包含一个字符串列表,以及一个读取该文件并从列表中随机选择一个字符串的方法。我还没有实现这个方法,因为我正在为如何编写测试该功能而苦苦挣扎。
有很多方法可以从数组中随机选择一个对象,还有很多很好的回答问题,比如 this one在这里告诉我如何做到这一点(当涉及到实现时,我可能会使用 Array#sample
)。但我的期望应该是什么?我在想类似的东西:
期望(数组).to include(subject.random_select)
这肯定会断言我的方法返回了一些预期值——但是否足以断言该方法每次随机返回不同的字符串?有哪些替代方法,或者额外的测试可以确保我已经覆盖了这种方法?我真的不能期望 subject.random_select
等于伪造的输入,可以吗?
最佳答案
我会首先测试从单行文件中非随机选择单个字符串,然后测试从多行文件中选择字符串,最后测试选择是否是随机的。你不能在有限的时间内真正测试随机性,所以你能做的最好的就是
- 测试您的方法是否返回所需范围内的值,因为您的测试将在应用程序的生命周期内运行很多次,您可能会发现它是否会返回超出范围的值,并且<
- 证明您的代码使用了随机源。
假设该文件在您的测试环境中不存在,或者您不知道其内容,或者不希望它对一个测试正确而对其他测试不正确的不对称性,那么我们将需要提供一种方法让测试将类指向不同的文件。
我们可以编写以下代码,一次编写一个测试,使其通过并在编写下一个之前进行重构。下面是第三个测试写完后,实现前的测试和代码:
规范/模型/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
当我编写第二个测试时,我意识到它在没有任何额外代码更改的情况下通过了,所以我考虑删除它。但我推迟了那个决定,编写了第三个测试,并发现一旦第三个测试通过,第二个测试就会有值(value),因为第二个测试测试该值来自文件,但第三个测试没有。
be_in
是一种比 include
更好的测试返回值是否在已知集合中的方法,因为它将实际值放在 expect
中RSpec 期望的位置。
还有其他方法可以控制随机性,因此您可以测试它是否已被使用。例如,如果您使用 sample
,您可以 allow_any_instance_of(Array).to receive(:sample)
并返回您喜欢的任何内容。但我喜欢使用 srand
,因为它不需要实现来使用使用随机数生成器的特定方法。
如果文件丢失或为空,您也需要对其进行测试。
关于ruby - 如何测试一个元素是从列表中随机选择的?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/36580186/