4

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 测试框架吗?

4

1 回答 1

4

情况很复杂。竞争条件之所以如此恶劣,正是因为它们难以重现。在内部,save是这样的:

  1. 证实。
  2. 写入数据库。

因此,要重现计时问题,您需要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这么容易地打开这个方法并摆弄它的内部结构。

但是(这是一个很大的问题),您可以完全跳过验证

请注意,如果作为参数save传递,它也可以跳过验证。:validate => false应谨慎使用此技术。

所以如果你使用

b.save(:validate => false)

您应该只获得“写入数据库”的一半,b并将save数据发送到数据库而无需验证。这应该会触发数据库中的约束违规,我很确定这会引发ActiveRecord::StatementInvalid异常,因此我认为您需要寻找异常,而不仅仅是从以下错误返回save

b.save(:validate => false).should raise_exception(ActiveRecord::StatementInvalid)

您也可以收紧它以查找特定的异常消息。我没有任何方便的东西来测试这个测试,所以在 Rails 控制台中尝试并适当地调整你的规范。

于 2012-01-04T06:21:38.033 回答