RSpec:我如何编写一个期望某些输出但不关心该方法的测试?

我试图了解测试驱动设计,特别是RSpec。 但是我遇到了RSpec Book的一些例子。

在本书中,我们测试$ STDOUT的输出如下:

output = double('output') game = Game.new output.should_receive(:puts).with('Welcome to Codebreaker!') game.start() 

嗯,这是一种时尚后的作品。 但是,为什么我应该关心Game对象是否使用puts()方法? 如果我把它改成print(),它真的会破坏测试吗? 而且,更重要的是,这不是针对TDD的主要原因之一 – 我应该测试该方法的作用(设计)而不是它是如何做的(实现)?

有没有什么方法可以编写一个测试,只测试最终在$ STDOUT上的内容,而不用看看它放在哪个方法?

看看这篇文章 。 尼克提出了关于同一个例子的问题,并在评论中进行了一次非常有趣的对话。 希望你觉得它有用。

创建一个能够写出状态的显示类。

生产代码将使用此显示对象,因此您可以自由更改写入STDOUT的方式。 当您的测试依赖于抽象时,这个逻辑只有一个地方。

例如:

 output = stub('output') game = Game.new(output) output.should_receive(:display).with('Welcome to Codebreaker!') game.start() 

虽然你的生产代码会有类似的东西

 class Output def display(message) # puts or whatever internally used here. You only need to change this here. end end 

我通过执行以下操作来完成此测试:

 def start @output.display('Welcome to Codebreaker!') end 

这里的生产代码并不关心输出的显示方式。 它可以是任何forms的显示,现在抽象已经到位。

所有上述理论都是语言不可知的,并且是一种享受。 你仍然嘲笑你不拥有的东西,比如第三方代码,但你仍在测试你是通过抽象来完成手头的工作。

捕获$stdout并对其进行测试,而不是试图模拟可能输出到stdout的各种方法。 毕竟,你想测试stdout而不是一些复杂的方法来模仿它。

 expect { some_code }.to match_stdout( 'some string' ) 

哪个使用自定义匹配器(rspec 2)

 RSpec::Matchers.define :match_stdout do |check| @capture = nil match do |block| begin stdout_saved = $stdout $stdout = StringIO.new block.call ensure @capture = $stdout $stdout = stdout_saved end @capture.string.match check end failure_message_for_should do "expected to #{description}" end failure_message_for_should_not do "expected not to #{description}" end description do "match [#{check}] on stdout [#{@capture.string}]" end end 

RSpec 3略微改变了Matcher API 。

failure_message_for_should现在是failure_message
failure_message_for_should_not现在是failure_message_when_negated
supports_block_expectations? 已被添加以使块的错误更清晰。

请参阅Charles的完整rspec3解决方案的答案。

我测试它的方式是使用StringIO对象。 它就像一个文件,但不会触及文件系统。 为Test :: Unit语法道歉 – 随意编辑RSpec语法。

 require "stringio" output_file = StringIO.new game = Game.new(output_file) game.start output_text = output_file.string expected_text = "Welcome to Codebreaker!" failure_message = "Doesn't include welcome message" assert output_text.include?(expected_text), failure_message 

我遇到了这篇博文,帮助我解决了这个问题:

在rspec中模拟标准输出 。

他在块之前/之后设置,我最终在实际的rspec内部进行了这些操作,由于某些原因,我无法按照建议从我的spec_helper.rb开始工作。

希望能帮助到你!

Matt对RSpec 3.0的回答的更新版本:

 RSpec::Matchers.define :match_stdout do |check| @capture = nil match do |block| begin stdout_saved = $stdout $stdout = StringIO.new block.call ensure @capture = $stdout $stdout = stdout_saved end @capture.string.match check end failure_message do "expected to #{description}" end failure_message_when_negated do "expected not to #{description}" end description do "match [#{check}] on stdout [#{@capture.string}]" end def supports_block_expectations? true end end