正在测试的代码:
class Session
def initialize
@interface = Interface.new(self)
@interface.hello
end
end
class Interface
def initialize(session, out = $STDOUT)
@session = session
@out = out
end
def hello
@out.puts "hello"
end
end
测试:
describe Session do
let (:fake_stdout) {double("$STDOUT", :puts => true)}
let (:interface) {instance_double("Interface", :out => "fake_stdout")}
let (:session) { Session.new }
describe "#new" do
it "creates an instance of Session" do
expect(session).to be_an_instance_of(Session)
end
end
end
这会引发private method 'puts' called for nil:NilClass
。它似乎没有看到fake_stdout
指定的:puts
为out
。我尝试用allow(Interface).to receive(:new).with(session).and_return(interface)
绑定它,但这没有改变。如何让经过测试的Session
类看到double / instance double并通过测试?
答案 0 :(得分:0)
我认为,这不是真正的存根问题,而是一般方法。在为某些类编写单元测试时,您应该坚持使用该类的功能,并最终使用它所见到的API。如果您正在对out
的“内部”Interface
进行存根 - 那么Session
的规格已经很多了。
Session
真正看到的是Interface
公共hello
方法,因此Session
规范,不应该知道它的内部实现(它是{{ 1}})。你唯一应该关注的是,已经调用了@out.puts "hello"
方法。另一方面,确保为hello
调用put
时,应在hello
的规范中进行说明。
Ufff ......那是很长的介绍/解释,但是如何继续呢? (称为告诉我代码!;)。
说过,Interface
应该只知道Session.new
Interface
方法,它应该相信它能够正常运行,并且hello
规范应该确保方法叫做。为此,我们将使用Session
。让我们弄脏手!
spy
测试间谍是一个记录参数,返回值,此值以及所有调用抛出异常(如果有)的函数。
这取自SinonJS(这是搜索“什么是测试间谍”时的第一个结果),但解释是准确的。
这是如何工作的?
RSpec.describe Session do
let(:fake_interface) { spy("interface") }
let(:session) { Session.new }
before do
allow(Interface).to receive(:new).and_return(fake_interface)
end
describe "#new" do
it "creates an instance of Session" do
expect(session).to be_an_instance_of(Session) # this works now!
end
it "calls Interface's hello method when initialized" do
Session.new
expect(fake_interface).to have_received(:hello)
end
end
end
首先,我们正在执行一些代码,之后我们断言期望发生事情。从概念上讲,我们希望确保在Session.new
expect(fake_interface).to have_received(:hello)
Session.new
fake_interface
期间。这就是全部!
好的,但我需要另一个测试,确保使用特定的参数调用have_received(:hello)
方法。
好的,让我们测试一下!
假设Interface
看起来像:
Session
我们要测试class Session
def initialize
@interface = Interface.new(self)
@interface.hello
@interface.say "Something More!"
end
end
:
say
这个很简单。
还有一件事 - 我的RSpec.describe Session do
describe "#new" do
# rest of the code
it "calls interface's say_something_more with specific string" do
Session.new
expect(fake_interface).to have_received(:say).with("Something More!")
end
end
end
以Interface
为参数。如何测试Session
调用interface
方法?
让我们看一下示例实现:
session
如果我说......那就不会太令人惊讶了。
class Interface
# rest of the code
def do_something_to_session
@session.a_session_method
end
end
class Session
# ...
def another_method
@interface.do_something_to_session
end
def a_session_method
# some fancy code here
end
end
如果RSpec.describe Session do
# rest of the code
describe "#do_something_to_session" do
it "calls the a_session_method" do
Session.new.another_method
expect(fake_interface).to have_received(:do_something_to_session)
end
end
end
Session
调用了another_method
interface
方法,则应该检查。
如果你这样测试,你会使测试对未来的变化不那么脆弱。您可以更改do_something_to_session
的实施,它不再依赖Interface
。引入此类更改时 - 您必须仅更新put
的测试。 Interface
只知道调用了正确的方法,但内部会发生什么?这是Session
的工作......
希望有所帮助!请查看我other answer中Interface
的另一个例子。