gpt4 book ai didi

ruby - 如何在RSpec中使用验证 double 设置否定消息期望?

转载 作者:太空宇宙 更新时间:2023-11-03 16:20:11 26 4
gpt4 key购买 nike

我想在下面的代码中测试对obj.my_method的条件调用:

def method_to_test(obj)
# ...
obj.my_method if obj.respond_to?(:my_method)
# ...
end

obj可以是a Foo或a Bar。一个实现 my_method,另一个不:
class Foo
def my_method; end
end

class Bar
end

我的测试目前的结构如下:
describe '#method_to_test' do
context 'when obj is a Foo' do
let(:obj) { instance_double('Foo') }
it 'calls my_method' do
expect(obj).to receive(:my_method)
method_to_test(obj)
end
end

context 'when obj is a Bar' do
let(:obj) { instance_double('Bar') }
it 'does not call my_method' do
expect(obj).not_to receive(:my_method) # <- raises an error
method_to_test(obj)
end
end
end

第一个示例通过,但第二个示例在设置负面消息预期时引发错误,因为 obj是一个未实现 my_method的验证双精度数:
#method_to_test
when obj is a Foo
calls my_method
when obj is a Bar
does not call my_method (FAILED - 1)

Failures:

1) #method_to_test when obj is a Bar does not call my_method
Failure/Error: expect(obj).not_to receive(:my_method)
the Bar class does not implement the instance method: my_method

我完全理解rspec为什么会引发这个错误。如何测试线路,尽管 obj是一个验证双?
注:我已经启用了 verify_partial_doubles,因此将 instance_double('Bar')更改为 object_double(Bar.new)或只是 Bar.new没有帮助。

最佳答案

有趣又棘手的问题!从rspec文档中,我发现:
[编辑:刚刚看到您已经了解rspec抱怨的原因,所以您可以跳到可能脏/不太脏的解决方案部分我会保留我在这里找到的解释,以防其他人面临类似的问题(请纠正我/添加进一步的信息!)]
验证实例加倍不支持类报告不存在的方法
因为需要对类的实际实例进行验证。这通常是
使用方法缺少的情况。
-https://relishapp.com/rspec/rspec-mocks/docs/verifying-doubles/dynamic-classes
文件中提到了instance_double和method_missing的使用问题,但我相信这和你面临的问题是一样的。你的bar类没有报告响应:my_method。事实上,在失败的测试中,method_to_test(obj)永远不会被调用,因为当您试图定义实例的期望值时,测试失败了:

describe '#method_to_test' do
context 'when obj is a Bar' do
let(:obj) { instance_double('Bar') }

it 'does not call my_method' do
puts "A"
expect(obj).not_to receive(:my_method) # <- raises an error
puts "B" # <- never runs
method_to_test(obj)
end
end
end

生产:
#method_to_test
when obj is a Bar
A
does not call my_method (FAILED - 1)

当您尝试添加您的期望时,RSPEC查找BAR类报告已定义的方法列表,并且未找到 :my_method,并且无法预防地认为它实际上帮助您识别测试中的问题(毕竟,您试图在不存在的东西上添加方法调用期望),所以很可能是打字错误,对吧-哎哟!)是的。
可能的脏溶液
所以,恐怕没有一致的方法可以将这种期望添加到您正在使用的实例中,而不使用肮脏的诡计。例如,您可以在Bar上实际定义 :my_method,这样就可以添加消息期望,但随后您必须在Bar的上下文中重新定义 respond_to?,结果如下:
def my_method
raise # should never be executed in any case
end

def respond_to?(m_name)
m_name != :my_method && super(name)
end

但你为什么要这么做这是肮脏和违反直觉的这种策略可以在我们正在讨论的非常具体的情况下工作,但是这会将您的测试与您的特定实现耦合在一起,并使它们失去作为文档的价值。
可能的不太脏的解决方案
所以,我的建议是不要依赖于方法期望,而是依赖于期望的行为。您需要的是执行 method_to_test(obj)不会因为bar实例上缺少方法而引发异常。你可以这样做:
describe '#method_to_test' do
context 'when obj is a Bar' do
let(:obj) { instance_double('Bar') }

it 'does not call my_method' do
expect { method_to_test(obj) }.not_to raise_error
end
end
end

缺点是,如果在执行方法体时引发任何异常,则此测试将失败,而理想情况下,我们希望它仅在引发 NoMethodError时失败。你可以试试这个,但不行:
    it 'does not call my_method' do
expect { method_to_test(obj) }.not_to raise_error(NoMethodError)
end

原因是在obj上调用 :my_method时引发的错误不是 NoMethodError类型,而是 RSpec::Mocks::MockExpectationError类型:
#method_to_test
when obj is a Bar
does not call my_method (FAILED - 1)

Failures:

1) #method_to_test when obj is a Bar does not call my_method
Failure/Error: expect { method_to_test(obj) }.not_to raise_error
expected no Exception, got #<RSpec::Mocks::MockExpectationError: #<InstanceDouble(Bar) (anonymous)> received unexpected message :my_method with (no args)> with backtrace:

您可以使用 RSpec::Mocks::MockExpectationError而不是 NoMethodError来实现预期,但这也会创建脆弱的测试:如果您将测试条实例更改为一个真正的obj而不是一个instance-double,那么如果调用 NoMethodError会提高 :my_method?这项测试将继续通过,而根本不测试任何有价值的东西此外(如注释中指出的),如果您这样做,RSPEC将警告您:
“警告:使用expect{}.not_-to-raise_error(SpecificErrorClass)有误报的风险,因为任何其他错误都会导致期望通过,包括Ruby引发的那些错误(例如NoMethodError、NameError和ArgumentError),这意味着您打算测试的代码甚至可能无法到达相反,请考虑使用expect{}.not_来引发错误可以通过设置rspec::expectations.configuration.warn_about_potential_false_posities=false来抑制此消息。”
所以,在这些选项之间,我不会指定异常的类型。
可能(最佳)解决方案
最后,我建议重构。使用 respond_to?通常表示您缺少在这些多个类中定义和实现的适当接口。我无法帮助您了解重构的细节,因为这将高度依赖于您当前的代码上下文/实现细节但是,您应该评估它是否值得花费,如果不值得,我建议将 obj.my_method if obj.respond_to?(:my_method)提取到一个专用的方法调用中,这样断言它不会引发_error将是一个更可靠的断言,因为您可以在较少的上下文中引发奇怪的异常此外,如果这样做,您可以传递一个简单的 double对象,该对象将允许您按照最初预期的方式添加消息期望。
对不起--冗长的回答:D

关于ruby - 如何在RSpec中使用验证 double 设置否定消息期望?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/34920134/

26 4 0
Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
广告合作:1813099741@qq.com 6ren.com