自从我开始使用 rspec 以来,我一直对固定装置的概念有疑问。我主要担心的是:
我使用测试来揭示令人惊讶的行为。对于我正在测试的示例,我并不总是足够聪明地列举所有可能的边缘情况。使用硬编码的固定装置似乎是有限的,因为它只用我想象的非常具体的情况来测试我的代码。(诚然,我的想象力也限制了我测试的案例。)
我使用测试作为代码文档的一种形式。如果我有硬编码的夹具值,则很难揭示特定测试试图演示的内容。例如:
describe Item do describe '#most_expensive' do it 'should return the most expensive item' do Item.most_expensive.price.should == 100 # OR #Item.most_expensive.price.should == Item.find(:expensive).price # OR #Item.most_expensive.id.should == Item.find(:expensive).id end end end
使用第一种方法不会让读者知道最贵的物品是什么,只是它的价格是 100。这三种方法都要求读者相信灯具
:expensive
是列出的最贵的灯具fixtures/items.yml
。粗心的程序员可能会通过创建一个Item
inbefore(:all)
或将另一个夹具插入到fixtures/items.yml
. 如果这是一个大文件,可能需要很长时间才能找出问题所在。
我开始做的一件事是#generate_random
为我的所有模型添加一个方法。此方法仅在我运行规范时可用。例如:
class Item
def self.generate_random(params={})
Item.create(
:name => params[:name] || String.generate_random,
:price => params[:price] || rand(100)
)
end
end
(我如何做到这一点的具体细节实际上更简洁一些。我有一个处理所有模型的生成和清理的类,但这段代码对于我的示例来说已经足够清晰了。)所以在上面的示例中,我可以测试为跟随。对虚心的警告:我的代码严重依赖于使用before(:all)
:
describe Item do
describe '#most_expensive' do
before(:all) do
@items = []
3.times { @items << Item.generate_random }
@items << Item.generate_random({:price => 50})
end
it 'should return the most expensive item' do
sorted = @items.sort { |a, b| b.price <=> a.price }
expensive = Item.most_expensive
expensive.should be(sorted[0])
expensive.price.should >= 50
end
end
end
这样,我的测试可以更好地揭示令人惊讶的行为。当我以这种方式生成数据时,我偶尔会偶然发现我的代码没有按预期运行的边缘情况,但如果我只使用固定装置,我就不会发现这种情况。例如,在 的情况下#most_expensive
,如果我忘记处理多个项目共享最贵价格的特殊情况,我的测试偶尔会在第一次失败should
。看到 AutoSpec 中的非确定性故障会提示我出了点问题。如果我只使用固定装置,发现这样的错误可能需要更长的时间。
我的测试在代码中演示预期的行为方面也做得更好。我的测试清楚地表明 sorted 是按价格降序排序的项目数组。由于我希望#most_expensive
等于该数组的第一个元素,因此更明显的是预期的行为most_expensive
是什么。
那么,这是一种不好的做法吗?我对固定装置的恐惧是非理性的吗?为每个模型写一个generate_random
方法是不是工作量太大了?或者这行得通吗?