如何在 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 配置的详细信息。