自从我开始使用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
in before(:all)
或通过插入另一个fixture 来中断测试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
为每个模型编写一个方法太多了吗?或者这有效吗?
我很惊讶在这个主题中没有人或者杰森贝克与提到的 蒙特卡罗测试有关.这是我唯一一次广泛使用随机测试输入.然而,通过为每个测试用例提供随机数发生器的恒定种子,使测试可重复是非常重要的.
我们最近的一个项目对此进行了很多考虑.最后,我们确定了两点:
测试用例的可重复性至关重要.如果您必须编写随机测试,请准备好进行广泛的记录,因为如果/如果失败,您将需要确切知道原因.
使用随机性作为代码覆盖的拐杖意味着您要么没有良好的覆盖范围,要么您不了解域名足以知道代表性测试用例的构成.弄清楚哪个是真的并相应地修复它.
总之,随机性往往比它的价值更麻烦.在扣动扳机之前,请仔细考虑是否要正确使用它.我们最终决定随机测试案例一般都是个坏主意,如果有的话,可以谨慎使用.
这是你第二点的答案:
(2)我使用测试作为代码的文档形式.如果我有硬编码的夹具值,很难揭示特定测试试图演示的内容.
我同意.理想情况下,规范示例本身应该是可以理解的.使用灯具是有问题的,因为它将示例的前提条件与其预期结果分开.
因此,许多RSpec用户已完全停止使用灯具.相反,在spec示例本身中构造所需的对象.
describe Item, "#most_expensive" do it 'should return the most expensive item' do items = [ Item.create!(:price => 100), Item.create!(:price => 50) ] Item.most_expensive.price.should == 100 end end
如果结了大量的对象创建样板代码,你应该看看一些很多测试对象工厂库,如factory_girl,机械师,或FixtureReplacement.