在 Ruby on Rails 3 Tutorial 的第6.2.4节中,Michael Hartl描述了一个关于检查电子邮件地址唯一性的警告:如果两个相同的请求及时关闭,请求A可以通过验证,然后B通过验证,然后A得到保存,然后B得到保存,你得到两个具有相同值的记录。每个都在检查时有效。
我的问题是不关于解决方案(对数据库设置了一个唯一约束,因此B的保存将不起作用)。这是关于编写测试以证明解决方案有效。我尝试自己编写,但无论我想出什么,只能模仿常规的,简单的唯一性测试。
作为对rspec的全新,我的天真方法是编写场景:
it 'should reject duplicate email addresses with caveat' do
A = User.new( @attr )
A.should be_valid # always valid
B = User.new( @attr )
B.should be_valid # always valid, as expected
A.save.should == true # save always works fine
B.save.should == false # this is the problem case
# B.should_not be_valid # ...same results as "save.should"
end
但是这个测试在与常规唯一性测试完全相同的情况下通过/失败;编写代码时B.save.should == false
会通过,以便常规测试失败时常规唯一性测试通过并失败。
所以我的问题是“如何编写一个可以验证我正在解决这个问题的rspec测试?”如果答案结果是“它很复杂”,那么我应该看一下不同的Rails测试框架吗?
答案 0 :(得分:4)
这很复杂。竞争条件非常糟糕,因为它们很难再现。在内部,save
就是这样的:
因此,要重现计时问题,您需要安排两个save
调用重叠,如下所示(伪Rails):
a.validate # first half of a.save
b.validate # first half of b.save
a.write_to_db # second half of a.save
b.write_to_db # second half of b.save
但是你不能打开save
方法并且很容易摆弄它的内部。
但是(这是一个很大但是),you can skip the validations entirely:
请注意,如果将
save
作为参数传递,:validate => false
也可以跳过验证。应谨慎使用此技术。
所以如果你使用
b.save(:validate => false)
你应该只获得b
save
一半的“写入数据库”并将数据发送到数据库而不进行验证。这应该会触发数据库中的约束违规,我很确定会引发ActiveRecord::StatementInvalid异常,所以我认为你需要查找异常,而不仅仅是save
的错误返回:
b.save(:validate => false).should raise_exception(ActiveRecord::StatementInvalid)
您可以加强它以查找特定的异常消息。我没有任何方便测试这个测试,所以在Rails控制台中试试并适当调整你的规范。