我已经为竞争条件实现了乐观锁定。如果lock_version
与数据库中更新的lock_version
不匹配,则会触发retry
三次。你能建议如何测试这个retry
事件吗
#Product: Model's new field:
# lock_version :integer(4) default(0), not null
def recalculate
method_1
self.save!
end
private
def method_1
begin
####
####
if self.lock_version == Product.find(self.id).lock_version
Product.where(:id => self.id).update_all(attributes)
else
raise ActiveRecord::StaleObjectError.new(self, "test")
end
rescue ActiveRecord::StaleObjectError => e
if tries < 3
tries += 1
sleep(1 + tries)
self.reload
retry
else
raise Exception.new(timeout.inspect)
end
end
end
Rspec 单元测试:
it 'if car is updated then ActiveRecord::StaleObjectError should be raised' do
prod_v1 =Product.find(@prod.id)
prod_v2 = Product.find(@prod.id)
prod_v1.recalculate
prod_v1.reload # will make lock_version of prod_v1 to 1
prod_v2.recalculate # howvever lock_version of prod_v2 is still 0.
expect{car_v2.send(:method1)}.to receive(:retry)
end
我将首先将你的方法1的复杂性分解为这样的东西。
private
def stale_object?
self.lock_version == Product.find(self.id).lock_version
end
def attempt_to_rescue_stale_object
if rescue_stale_object_retries =< 3
rescue_stale_object_retries += 1
sleep( 1 + rescue_stale_object_retries)
retry # and the sleep stuff, not sure how you
else
raise Exception.new(timeout.inspect)
end
end
def rescue_stale_object_retries
@rescue_stale_object_retries ||= 0
end
def method_1
begin
raise ActiveRecord::StaleObjectError.new(self, "test") if stale_object?
Product.where(:id => self.id).update_all(attributes)
rescue ActiveRecord::StaleObjectError => e
attempt_to_rescue_stale_object
reload # You can omit the self here
else
raise Exception.new(timeout.inspect)
end
end
end
现在你可以随心所欲地测试它,救援逻辑的复杂性被包装成一个方法。您不依赖于测试其中的实际行为(现在是一个睡眠调用,但它可能会变成运行后台作业(。
[https://relishapp.com/rspec/rspec-mocks/docs/设置约束/接收计数][1] 给了我们类似的东西
expect{car_v2}.to receive(:attempt_to_rescue_stale_object).exactly(3).times
car_v2.method1
将复杂性分解成小部分的另一个好处是,您的意图可以更好地传达给您的开发者(另一个人或您在 6 个月后忘记了您最初的测试目标(