间谍是否是查看 Resque 方法是否被触发的合适方法?

Are spies an appropriate approach to see if Resque methods are being fired?

虽然简单覆盖将此报告为 100% 覆盖,但我并不满意。标记为焦点的规范我想确认所有的 Resque 方法都被触发了。间谍或双重身份是正确的做法吗?

规格

describe 'resque tasks' do
  include_context 'rake'
  let(:task_paths) { ['tasks/resque'] }

  before do
    invoke_task.reenable
  end

  # rubocop:disable all
  describe 'resque' do
    context ':setup' do
      let(:task_name) { 'resque:setup' }

      it 'works' do
        invoke_task.invoke
        expect(Resque.logger.level).to eq(1)
      end
    end

    context ':scheduler_setup' do
      let(:task_name) { 'resque:scheduler_setup' }

      it 'works' do
        expect(invoke_task.invoke).to be
      end
    end

    context ':clear', focus: true do
      let(:task_name) { 'resque:clear' }

      it 'works' do
        expect(Resque).to receive(:remove_queue).with('queue:default').and_return(true)
        expect { invoke_task.invoke }.to output(
          "Clearing default...\n"\
          "Clearing delayed...\n"\
          "Clearing stats...\n"\
          "Clearing zombie workers...\n"\
          "Clearing failed jobs...\n"\
          "Clearing resque workers...\n"
        ).to_stdout
      end
    end
  end

  describe 'jobs:work' do
    let(:task_name) { 'jobs:work' }

    it 'works' do
      expect_any_instance_of(Object).to receive(:system).with("bundle exec env rake resque:workers QUEUE='*' COUNT='#{ENV['WEB_WORKERS']}'").and_return(true)
      expect(invoke_task.invoke).to be
    end
  end
  # rubocop:enable all
end

Resque Rake 任务

require 'resque'
require 'resque/tasks'
require 'resque/scheduler/tasks'

# http://jademind.com/blog/posts/enable-immediate-log-messages-of-resque-workers/
namespace :resque do
  desc 'Initialize Resque environment'
  task setup: :environment do
    ENV['QUEUE'] ||= '*'
    Resque.logger.level = Logger::INFO
  end

  task scheduler_setup: :environment

  # see  - old version
  # see https://github.com/defunkt/resque/issues/49
  # see http://redis.io/commands - new commands
  desc 'Clear pending tasks'
  task clear: :environment do
    queues = Resque.queues
    queues.each do |queue_name|
      puts "Clearing #{queue_name}..."
      Resque.remove_queue("queue:#{queue_name}")
    end

    puts 'Clearing delayed...'
    Resque.redis.keys('delayed:*').each do |key|
      Resque.redis.del key.to_s
    end
    Resque.redis.del 'delayed_queue_schedule'
    Resque.reset_delayed_queue

    puts 'Clearing stats...'
    Resque.redis.set 'stat:failed', 0
    Resque.redis.set 'stat:processed', 0

    puts 'Clearing zombie workers...'
    Resque.workers.each(&:prune_dead_workers)

    puts 'Clearing failed jobs...'
    cleaner = Resque::Plugins::ResqueCleaner.new
    cleaner.clear

    puts 'Clearing resque workers...'
    Resque.workers.each(&:unregister_worker)
  end
end

desc 'Alias for resque:work'
# 
task 'jobs:work' do
  system("bundle exec env rake resque:workers QUEUE='*' COUNT='#{ENV['WEB_WORKERS']}'")
end

共享上下文

shared_context 'rake' do
  let(:invoke_task) { Rake.application[task_name] }
  let(:highline) { instance_double(HighLine) }

  before do
    task_paths.each do |task_path|
      Rake.application.rake_require(task_path)
    end
    Rake::Task.define_task(:environment)
  end

  before do
    allow(HighLine).to receive(:new).and_return(highline)
  end
end

The spec marked as focus I would like to confirm that all of the Resque methods are being fired. Is a spy or a double the right approach for this?

是的。此测试中的 Spy 只会测试它是否收到了这些方法调用,因为它充当这些测试的 double 替身;这意味着您没有在此测试中测试任务的行为,您正在测试任务是否有一个对象,例如 Resque 接收这些方法调用。

Spies

Message expectations put an example's expectation at the start, before you've invoked the code-under-test. Many developers prefer using an act-arrange-assert (or given-when-then) pattern for structuring tests. Spies are an alternate type of test double that support this pattern by allowing you to expect that a message has been received after the fact, using have_received.

-- Spies - Basics - RSpec Mocks - RSpec - Relish

您的it 'works'测试

的示例
it 'works' do
  expect(Resque).to receive(:remove_queue).with('queue:default').and_return(true)
  expect { invoke_task.invoke }.to output(
    "Clearing default...\n"\
    "Clearing delayed...\n"\
    "Clearing stats...\n"\
    "Clearing zombie workers...\n"\
    "Clearing failed jobs...\n"\
    "Clearing resque workers...\n"
  ).to_stdout
end

如下

RSpec.describe "have_received" do
  it 'works' do
    Rake::Task.define_task(:environment)
    invoke_task = Rake.application['resque:clear']

    redis_double = double("redis")
    allow(redis_double).to receive(:keys).with('delayed:*').and_return([])
    allow(redis_double).to receive(:del).with('delayed_queue_schedule').and_return(true)
    allow(redis_double).to receive(:set).with('stat:failed', 0).and_return(true)
    allow(redis_double).to receive(:set).with('stat:processed', 0).and_return(true)

    allow(Resque).to receive(:queues).and_return([])
    allow(Resque).to receive(:redis).and_return(redis_double)
    # allow(Resque).to receive(:remove_queue).with('queue:default') #.and_return(true)
    allow(Resque).to receive(:reset_delayed_queue) #.and_return(true)
    allow(Resque).to receive(:workers).and_return([])

    cleaner_double = double("cleaner")
    allow(Resque::Plugins::ResqueCleaner).to receive(:new).and_return(cleaner_double)
    allow(cleaner_double).to receive(:clear).and_return(true)

    expect { invoke_task.invoke }.to output(
      # "Clearing default...\n"\
      "Clearing delayed...\n"\
      "Clearing stats...\n"\
      "Clearing zombie workers...\n"\
      "Clearing failed jobs...\n"\
      "Clearing resque workers...\n"
    ).to_stdout

    expect(redis_double).to have_received(:keys)
    expect(redis_double).to have_received(:del)
    expect(redis_double).to have_received(:set).with('stat:failed', 0)
    expect(redis_double).to have_received(:set).with('stat:processed', 0)

    expect(Resque).to have_received(:queues)
    expect(Resque).to have_received(:redis).at_least(4).times
    # expect(Resque).to have_received(:remove_queue).with('queue:default')
    expect(Resque).to have_received(:reset_delayed_queue)
    expect(Resque).to have_received(:workers).twice

    expect(Resque::Plugins::ResqueCleaner).to have_received(:new)
    expect(cleaner_double).to have_received(:clear)
  end
end

备注:

  • allow(Resque).to receive(:remove_queue).with('queue:default') 被注释掉了,因为 allow(redis_double).to receive(:keys).with('delayed:*').and_return([]) return 在我的示例代码中是一个空数组,这意味着 queues.each 永远不会迭代一次,所以 Resque.remove_queue("queue:#{queue_name}") 永远不会被调用并且 "Clearing default...\n"\ 不是 return 对于预期的输出

  • 另外,在这个任务中发生了很多事情,可能值得将它分解成更小的任务。

这有效地存根了 Resque 对象上的每个预期方法调用,然后在调用任务后访问双打接收那些预期的方法调用。它不测试这些任务的结果,只测试发生的方法调用并确认那些

methods are being fired.

参考文献: