Testando a simultaneidade com rspec, a maneira mais fácil

Recentemente, encontrei um bom artigo sobre como resolver problemas de simultaneidade , usando a gem fork_break . Exatamente o que eu precisava, mas ainda não era simples o suficiente para reutilizar para todos os possíveis problemas de simultaneidade no projeto. Isso deve tornar mais fácil:

em rspec / support / concurrency.rb

def make_concurrent_calls(object, method, options={})
options
.reverse_merge!(:count => 2)
processes
= options[:count].times.map do |i|
ForkBreak::Process.new do |breakpoints|
# Add a breakpoint after invoking the method
original_method
= object.method(method)
object
.stub(method) do |*args|
value
= original_method.call(*args)
breakpoints
<< method
value

end

object
.send(method)
end
end
processes
.each{ |process| process.run_until(method).wait }
processes
.each{ |process| process.finish.wait }
end

Um teste modelo rspec seria então:

context "unfrobbed thing" do
let
(:thing) { thing.make! }
context
"on concurrent calls to #frob" do
before
{ make_concurrent_calls(thing, :frob) }
it
"should only create one frob" do
# testing that the side effect is only executed once:
expect
(Frob.where(:thing_id => thing.id).count).to eq(1)
end
end
end

Alguns problemas podem exigir uma solução no nível do controlador, deve ser fácil adaptar o teste para esses casos.

Muitos dos problemas de simultaneidade aconteceram em métodos que eram realmente state_machinetransições. Aqui está uma solução reutilizável para esses casos:

em config / initializers / lock_transition.rb

module LockTransition
extend
ActiveSupport::Concern

def lock_transition(*args, &block)
before_transition
(*args) do |resource,transition|
resource
.lock!
if block_given?
yield
else
transition
.from == resource.state
end
end
end
end

class StateMachine::Machine
include
LockTransition
end

Então, no modelo:

class Thing < ActiveRecord::Base
state_machine
do
# (states and events)
lock_transition
:from => :initial, :to => :frobbed
after_transition
:from => :initial, :to => :frobbed, :do => :create_frob
end
# (methods)
end

Apenas tenha o cuidado de declarar o lock_transitionantes de qualquer before_transitiondeclaração contendo efeitos colaterais que devem ser protegidos.