如何在Rspec 3.5的模块中测试内核睡眠方法?

问题描述:

我在一个模块的私有部分下面的方法...如何在Rspec 3.5的模块中测试内核睡眠方法?

def add_api_delay 
    sleep(retry_delay * (retry_multiplier_adjustment - retries)) if retries.positive? 
    end 

我与这个样子的,到目前为止工作的规范...

let!(:klass) do 
    Class.new do 
     include AmazonMws::Shared::Utilities 
     attr_accessor :retries, :retry_multiplier_adjustment, :retry_delay 
     def initialize 
     @retries = 1 
     @retry_delay = 1 
     @retry_multiplier_adjustment = 2 
     end 

     def test_add_api_delay 
     add_api_delay 
     end 
    end 
    end 

    describe '.add_api_delay', focus: true do 
    # let(:kernel_spy) { class_spy(Kernel, sleep: true) } 

    before do 

    end 

    it 'sleeps retry api calls' do 
     # allow(klass).to receive(:sleep).with(1).and_return(kernel_spy) 
     # expect(kernel_spy).to have_received(:sleep) 
     # expect(Kernel).to receive(:sleep).with(1) 
     expect(Kernel).to receive(:sleep).and_return(true) 
     klass.new.test_add_api_delay 
    end 
    end 

我有几个目标以及我想测试这个私有方法的原因,但是如何验证睡眠被调用。我不想减慢套件,所以理想情况下我试图对Kernel使用类间谍。我正在测试的东西似乎工作。

UPDATE

describe '.add_api_delay' do 
    before do 
     allow_any_instance_of(klass).to receive(:sleep).and_return(1) 
    end 

    it 'sleeps retry api calls' do 
     expect(klass.new.test_add_api_delay).to eq(1) 
    end 
    end 

这个工作,但是,它是不理想的,因为它标志下面的警察......

C: RSpec/AnyInstance: Avoid stubbing using allow_any_instance_of 
     allow_any_instance_of(klass).to receive(:sleep).and_return(1) 

你有什么想法?

我相信这不起作用,因为你没有把期望放在正确的事情上。它看起来像你试图把它放在Kernel,并接受测试的类,但你必须把它的实例:

it 'sleeps retry api calls' do 
    thing = klass.new 
    allow(thing).to receive(:sleep) 

    thing.test_add_api_delay 

    expect(thing).to have_received(:sleep).with(1) 
end 

上述内容已经在测试嗅到它捻熄类下测试。但我认为这可能比在这里实施一些设计约束更好,并且在调用sleep时丢失了一些Ruby的优雅。

+0

谢谢蒂姆,这固定了一切。 –

您应该使用allow_any_instace_of

RSpec.describe "allow_any_instance_of" do 
    it "returns the specified value on any instance of the class" do 
    allow_any_instance_of(Object).to receive(:foo).and_return(:return_value) 

    o = Object.new 
    expect(o.foo).to eq(:return_value) 
    end 
end 
+0

我在想那个,但是那个警察从https://github.com/backus/rubocop-rspec –

下面是一个调用睡眠的私有方法的一个RSpec测试的小例子。

class Sleeper 
    def initialize(delay:) 
    @delay = delay 
    end 
    attr_reader :delay 

    private 

    def rest 
    sleep delay 
    end 
end 

require 'rspec' 

RSpec.describe Sleeper do 
    let(:sleeper) { Sleeper.new(delay: delay) } 
    let(:delay) { 10 } 

    # Comment here explaining why this test is necessary 
    describe '.send :rest' do 
    before { allow_any_instance_of(Sleeper).to receive(:sleep) } 

    it 'sleeps' do 
     expect(sleeper).to receive(:sleep) 
     sleeper.send :rest 
    end 
    end 
end 

UPDATE:

你必须在这里做一个决定。

Rubocop对“避免残留”的建议是很好的建议,但你说你有理由测试私有方法是否使用核心方法,而残留是最好的方法。如果那些测试原因比Rubocop纯度更重要,则应该忽略Rubocop警告。

如果Rubocop的建议更重要,那么我建议编写一个计算延迟的公开方法,并为此编写测试。也许是这样的:

def retry_delay_duration 
    return 0 if retries < 1 
    retry_delay * (retry_multiplier_adjustment - retries) 
end 

private 

def add_api_delay 
    sleep retry_delay_duration 
end 

在第二种情况下,你应该删除试验add_api_delay,并只测试公有方法retry_delay_duration(或者无论你怎么称呼它)返回正确的延迟。

+0

失败了,因为这个建议不起作用。有其他想法吗? –

+0

但是如果开发者只是评论或替换睡眠,它会被覆盖吗?另外,我的理解是使用“allow_any_instance_of”是不好的做法。 –

+0

你必须承认并拥有你的意图。以这种方式进行测试就是“实施”测试,如果有这样的业务案例,这不是一个不好的做法。的确,Ruby并不是一种用来制造这种限制的伟大语言,但我们都必须接受我们所拥有的东西。在这个例子中,这个对象有能力让运行时间停止任意长度的时间(我确信你的真实代码会设置某种最大持续时间)。如果你想这样做,围绕它进行某种实现测试似乎是明智的。它很脆弱,但是当你开始使用作业队列时,你会重构它。 – wobh