如何在 resque-retry 和 Rails 4 中测试重试和失败?
How to test retries and failures in resque-retry and Rails 4?
我正在尝试编写一个规范来测试 resque-retry 的重试功能,但我似乎无法让测试正确命中 binding.pry。有没有办法使用 rspec 3 测试此功能,以便我可以验证它们是否按预期运行?
这是一个请求规范,我正在尝试通过固定装置模拟一个实时请求,但无论我尝试什么,我似乎都无法让作业重试。
gem 'resque', require: 'resque/server'
gem 'resque-web', require: 'resque_web'
gem 'resque-scheduler'
gem 'resque-retry'
gem 'resque-lock-timeout'
我正在使用 resque_rspec, and trying this testing strategy。
部分规范
it 'retries it' do
stub_request(:any, /.*api.bigcartel.*/).to_return(body: '{}', status: 200)
@order_shipped_json['order']['originator_id'] = @provider_order
post "/hook/shops/#{@shop.id}", @order_shipped_json.to_json, format: :json
ResqueSpec.perform_all(queue_name)
???
end
队列作业
class QueueHook
extend Resque::Plugins::LockTimeout
extend Resque::Plugins::Retry
extend QueueLock
extend QueueLogger
@queue = AppSettings.queues[:hook_queue_name].to_sym
@lock_timeout = 600
@retry_exceptions = [QueueError::LockFailed]
@retry_limit = 600
@retry_delay = 1
class << self
def perform(web_hook_payload_id, _whiplash_customer_id)
ActiveRecord::Base.clear_active_connections!
@web_hook_payload = WebHookPayload.find(web_hook_payload_id)
klass_constructor
@hook.process_event
end
def identifier(_web_hook_payload_id, whiplash_customer_id)
"lock:integration_hook:#{whiplash_customer_id}"
end
def after_perform_delete_webhook(_web_hook_payload_id, _whiplash_customer_id)
@web_hook_payload.destroy
end
private
...
end
end
队列作业模块
module QueueLogger
def before_perform_log_job(*args)
Rails.logger.info "[Resque][#{self}] running with #{args.inspect}..."
end
def on_failure_log_job(*args)
message = "[Resque][#{self}] failed with #{args.inspect}..."
run_counters
Rails.logger.info message_builder(message)
end
private
def run_counters
@num_attempts += retry_attempt
@all_attempts += retry_limit
end
def message_builder(message)
return message unless @num_attempts
return message += " Retrying (attempt ##{@num_attempts + 1})" if @num_attempts < @all_attempts
message += ' Giving up.'
message
end
end
module QueueLock
def loner_enqueue_failed(*args)
Rails.logger.info "[Resque][#{self}] is already enqueued: #{args.inspect}..."
end
def lock_failed(*)
raise QueueError::LockFailed
end
end
一些注意事项-
1) 正如其他人所提到的,您可能希望将 resque
回调与其功能分开。也就是说,测试 retries
是否触发,但也单独测试它们是否按预期运行。您可能希望将它们分成两个单独的测试。
2) 为了检查他们是否开火,我想你正在 RSpec 中寻找 class doubles 3.
您需要实例化 double 然后引发异常 (docs). This will allow you to see if your retries
are being called, and how many times they have been called (docs)。
所以,例如,
it "retries on exception n number of times" do
queue_hook = class_double("QueueHook")
expect(queue_hook).to have_received(:on_failure_log_job).exactly(n).times
allow(queue_hook).to receive(:perform).and_raise(ExceptionClass, "Exception message")
queue_hook.perform(payload_id, customer_id)
end
发生了一些事情,所以我无法在本地实施,但希望这可以帮助您朝着正确的方向前进。
所以您要测试重试的特定失败来自您实现的这个挂钩。
def lock_failed(*)
raise QueueError::LockFailed
end
我们需要触发它。 Here 是它在插件中使用的地方。由于您使用的是锁定超时,因此看起来我们想要存根 .acquire_lock_algorithm!
。这很危险,因为此方法是插件内部 api 的一部分。升级插件时请记住。
it 'retries it' do
stub_request(:any, /.*api.bigcartel.*/).to_return(body: '{}', status: 200)
allow(QueueHook).to receive(:acquire_lock_algorithm!).and_return(false, true)
@order_shipped_json['order']['originator_id'] = @provider_order
post "/hook/shops/#{@shop.id}", @order_shipped_json.to_json, format: :json
ResqueSpec.perform_all(queue_name)
end
此规范现在应该因 Failure/Error: raise QueueError::LockFailed
而失败。既然这是预期的,我们可以设定一个预期。
it 'retries it' do
stub_request(:any, /.*api.bigcartel.*/).to_return(body: '{}', status: 200)
allow(QueueHook).to receive(:acquire_lock_algorithm!).and_return(false, true)
@order_shipped_json['order']['originator_id'] = @provider_order
post "/hook/shops/#{@shop.id}", @order_shipped_json.to_json, format: :json
expect {
ResqueSpec.perform_all(queue_name)
}.to raise_error(QueueError::LockFailed)
end
除非您设置了 ResqueSpec.inline = true
,否则规范现在应该通过了。如果您已针对此规范将其设置为 false。这样会更容易理解。
如果 resque-retry 正在运行,那么作业的失败应该会导致作业重新排队到 ResqueSpec。我们可以为此增加一个期望。 expect(ResqueSpec.queues[queue_name]).to be_present
。不是我们可以 运行 再次工作。我们将 acquire_lock_algorithm!
的第二个 return 值模拟为真,因此这次作业应该会成功。
因为我们要测试计数器,所以让我们为它们添加读者
module QueueLogger
attr_reader :all_attempts, :num_attempts
end
然后完成规范...
it 'retries it' do
stub_request(:any, /.*api.bigcartel.*/).to_return(body: '{}', status: 200)
allow(QueueHook).to receive(:acquire_lock_algorithm!).and_return(false, true)
@order_shipped_json['order']['originator_id'] = @provider_order
post "/hook/shops/#{@shop.id}", @order_shipped_json.to_json, format: :json
# Failing
expect {
ResqueSpec.perform_all(queue_name)
}.to raise_error(QueueError::LockFailed)
expect(ResqueSpec.queues[queue_name]).to be_present
# Retrying
ResqueSpec.perform_all(queue_name)
expect(QueueHook.num_attempts).to eq(2)
... # Whatever else you want to test.
end
如果您想专门测试日志记录,您可以存根它们并设置关于它们被调用的预期。应该可以,我在自己的机器上有一个简化版本 运行ning。如果不是,我们可能必须了解您的测试和 Resque 配置的详细信息。
我正在尝试编写一个规范来测试 resque-retry 的重试功能,但我似乎无法让测试正确命中 binding.pry。有没有办法使用 rspec 3 测试此功能,以便我可以验证它们是否按预期运行?
这是一个请求规范,我正在尝试通过固定装置模拟一个实时请求,但无论我尝试什么,我似乎都无法让作业重试。
gem 'resque', require: 'resque/server'
gem 'resque-web', require: 'resque_web'
gem 'resque-scheduler'
gem 'resque-retry'
gem 'resque-lock-timeout'
我正在使用 resque_rspec, and trying this testing strategy。
部分规范
it 'retries it' do
stub_request(:any, /.*api.bigcartel.*/).to_return(body: '{}', status: 200)
@order_shipped_json['order']['originator_id'] = @provider_order
post "/hook/shops/#{@shop.id}", @order_shipped_json.to_json, format: :json
ResqueSpec.perform_all(queue_name)
???
end
队列作业
class QueueHook
extend Resque::Plugins::LockTimeout
extend Resque::Plugins::Retry
extend QueueLock
extend QueueLogger
@queue = AppSettings.queues[:hook_queue_name].to_sym
@lock_timeout = 600
@retry_exceptions = [QueueError::LockFailed]
@retry_limit = 600
@retry_delay = 1
class << self
def perform(web_hook_payload_id, _whiplash_customer_id)
ActiveRecord::Base.clear_active_connections!
@web_hook_payload = WebHookPayload.find(web_hook_payload_id)
klass_constructor
@hook.process_event
end
def identifier(_web_hook_payload_id, whiplash_customer_id)
"lock:integration_hook:#{whiplash_customer_id}"
end
def after_perform_delete_webhook(_web_hook_payload_id, _whiplash_customer_id)
@web_hook_payload.destroy
end
private
...
end
end
队列作业模块
module QueueLogger
def before_perform_log_job(*args)
Rails.logger.info "[Resque][#{self}] running with #{args.inspect}..."
end
def on_failure_log_job(*args)
message = "[Resque][#{self}] failed with #{args.inspect}..."
run_counters
Rails.logger.info message_builder(message)
end
private
def run_counters
@num_attempts += retry_attempt
@all_attempts += retry_limit
end
def message_builder(message)
return message unless @num_attempts
return message += " Retrying (attempt ##{@num_attempts + 1})" if @num_attempts < @all_attempts
message += ' Giving up.'
message
end
end
module QueueLock
def loner_enqueue_failed(*args)
Rails.logger.info "[Resque][#{self}] is already enqueued: #{args.inspect}..."
end
def lock_failed(*)
raise QueueError::LockFailed
end
end
一些注意事项-
1) 正如其他人所提到的,您可能希望将 resque
回调与其功能分开。也就是说,测试 retries
是否触发,但也单独测试它们是否按预期运行。您可能希望将它们分成两个单独的测试。
2) 为了检查他们是否开火,我想你正在 RSpec 中寻找 class doubles 3.
您需要实例化 double 然后引发异常 (docs). This will allow you to see if your retries
are being called, and how many times they have been called (docs)。
所以,例如,
it "retries on exception n number of times" do
queue_hook = class_double("QueueHook")
expect(queue_hook).to have_received(:on_failure_log_job).exactly(n).times
allow(queue_hook).to receive(:perform).and_raise(ExceptionClass, "Exception message")
queue_hook.perform(payload_id, customer_id)
end
发生了一些事情,所以我无法在本地实施,但希望这可以帮助您朝着正确的方向前进。
所以您要测试重试的特定失败来自您实现的这个挂钩。
def lock_failed(*)
raise QueueError::LockFailed
end
我们需要触发它。 Here 是它在插件中使用的地方。由于您使用的是锁定超时,因此看起来我们想要存根 .acquire_lock_algorithm!
。这很危险,因为此方法是插件内部 api 的一部分。升级插件时请记住。
it 'retries it' do
stub_request(:any, /.*api.bigcartel.*/).to_return(body: '{}', status: 200)
allow(QueueHook).to receive(:acquire_lock_algorithm!).and_return(false, true)
@order_shipped_json['order']['originator_id'] = @provider_order
post "/hook/shops/#{@shop.id}", @order_shipped_json.to_json, format: :json
ResqueSpec.perform_all(queue_name)
end
此规范现在应该因 Failure/Error: raise QueueError::LockFailed
而失败。既然这是预期的,我们可以设定一个预期。
it 'retries it' do
stub_request(:any, /.*api.bigcartel.*/).to_return(body: '{}', status: 200)
allow(QueueHook).to receive(:acquire_lock_algorithm!).and_return(false, true)
@order_shipped_json['order']['originator_id'] = @provider_order
post "/hook/shops/#{@shop.id}", @order_shipped_json.to_json, format: :json
expect {
ResqueSpec.perform_all(queue_name)
}.to raise_error(QueueError::LockFailed)
end
除非您设置了 ResqueSpec.inline = true
,否则规范现在应该通过了。如果您已针对此规范将其设置为 false。这样会更容易理解。
如果 resque-retry 正在运行,那么作业的失败应该会导致作业重新排队到 ResqueSpec。我们可以为此增加一个期望。 expect(ResqueSpec.queues[queue_name]).to be_present
。不是我们可以 运行 再次工作。我们将 acquire_lock_algorithm!
的第二个 return 值模拟为真,因此这次作业应该会成功。
因为我们要测试计数器,所以让我们为它们添加读者
module QueueLogger
attr_reader :all_attempts, :num_attempts
end
然后完成规范...
it 'retries it' do
stub_request(:any, /.*api.bigcartel.*/).to_return(body: '{}', status: 200)
allow(QueueHook).to receive(:acquire_lock_algorithm!).and_return(false, true)
@order_shipped_json['order']['originator_id'] = @provider_order
post "/hook/shops/#{@shop.id}", @order_shipped_json.to_json, format: :json
# Failing
expect {
ResqueSpec.perform_all(queue_name)
}.to raise_error(QueueError::LockFailed)
expect(ResqueSpec.queues[queue_name]).to be_present
# Retrying
ResqueSpec.perform_all(queue_name)
expect(QueueHook.num_attempts).to eq(2)
... # Whatever else you want to test.
end
如果您想专门测试日志记录,您可以存根它们并设置关于它们被调用的预期。应该可以,我在自己的机器上有一个简化版本 运行ning。如果不是,我们可能必须了解您的测试和 Resque 配置的详细信息。