如何正确存根双打
How to properly stub doubles
正在测试的代码:
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
class 看到 double/instance 双倍并通过测试?
我认为,这不是存根问题,而是一般方法。在为某些 class 编写单元测试时,您应该坚持 class 的功能,并最终坚持 API 它看到的功能。如果您正在存根 "internal" out
of Interface
- 对于 Session
.
的规格来说已经太多了
Session
真正看到的是 Interface
s public hello
方法,因此 Session
规范,不应该知道它的内部实现(它是 @out.puts "hello"
)。您真正应该关注的唯一一件事是,调用了 hello
方法。另一方面,确保 put
被调用 hello
应该在 Interface
.
的规范中描述
Ufff...那么长introduction/explanation,但是接下来要怎么进行呢? (也称为 给我看代码! ;))。
话虽如此,Session.new
应该只知道 Interface
的 hello
方法,它应该相信它可以正常工作,并且 Session
的规范应该确保该方法被调用。为此,我们将使用 spy
。让我们亲自动手!
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
A test spy is a function that records arguments, return value, the value of this and exception thrown (if any) for all its calls.
这取自 SinonJS(这是谷歌搜索 "what is test spy" 时的第一个结果),但解释是准确的。
这是如何工作的?
Session.new
expect(fake_interface).to have_received(:hello)
首先,我们正在执行一些代码,然后我们断言预期事情发生了。从概念上讲,我们要确定 Session.new
、fake_interface
have_received(:hello)
。就这些!
好的,但我需要另一个测试确保 Interface
s 方法是用 specific 参数调用的 .
好的,让我们测试一下!
假设 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
s 方法?
让我们看一下示例实现:
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
s another_method
是否调用了 interface
s do_something_to_session
方法。
如果你这样测试,你可以让测试不那么脆弱以应对未来的变化。您可以更改 Interface
的实现,使其不再依赖于 put
。引入此类更改时 - 您只需更新 Interface
的测试。 Session
只知道调用了正确的方法,但是里面发生了什么?那是 Interface
的工作...
希望对您有所帮助!请看一下我的 .
中另一个 spy
的例子
祝你好运!
正在测试的代码:
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
class 看到 double/instance 双倍并通过测试?
我认为,这不是存根问题,而是一般方法。在为某些 class 编写单元测试时,您应该坚持 class 的功能,并最终坚持 API 它看到的功能。如果您正在存根 "internal" out
of Interface
- 对于 Session
.
Session
真正看到的是 Interface
s public hello
方法,因此 Session
规范,不应该知道它的内部实现(它是 @out.puts "hello"
)。您真正应该关注的唯一一件事是,调用了 hello
方法。另一方面,确保 put
被调用 hello
应该在 Interface
.
Ufff...那么长introduction/explanation,但是接下来要怎么进行呢? (也称为 给我看代码! ;))。
话虽如此,Session.new
应该只知道 Interface
的 hello
方法,它应该相信它可以正常工作,并且 Session
的规范应该确保该方法被调用。为此,我们将使用 spy
。让我们亲自动手!
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
A test spy is a function that records arguments, return value, the value of this and exception thrown (if any) for all its calls.
这取自 SinonJS(这是谷歌搜索 "what is test spy" 时的第一个结果),但解释是准确的。
这是如何工作的?
Session.new
expect(fake_interface).to have_received(:hello)
首先,我们正在执行一些代码,然后我们断言预期事情发生了。从概念上讲,我们要确定 Session.new
、fake_interface
have_received(:hello)
。就这些!
好的,但我需要另一个测试确保 Interface
s 方法是用 specific 参数调用的 .
好的,让我们测试一下!
假设 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
s 方法?
让我们看一下示例实现:
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
s another_method
是否调用了 interface
s do_something_to_session
方法。
如果你这样测试,你可以让测试不那么脆弱以应对未来的变化。您可以更改 Interface
的实现,使其不再依赖于 put
。引入此类更改时 - 您只需更新 Interface
的测试。 Session
只知道调用了正确的方法,但是里面发生了什么?那是 Interface
的工作...
希望对您有所帮助!请看一下我的
spy
的例子
祝你好运!