我目前正在尝试测试一个基本的方法,它从用户获取一些输入(gets)并输出它(puts)。经过一些研究,我找到了一种好的方法来测试标准输出流,如下所示:
def capture_standard_output(&block)
original_stream = $stdout
$stdout = mock = StringIO.new
yield
mock.string.chomp
ensure
$stdout = original_stream
end
我正在测试的方法如下,输出和输入是指我在开始时初始化并且指向等效的 $stdout & $stdin 的实例变量:
def ask_for_mark
ouput.puts 'What shall I call you today?'
answer = input.gets.chomp.capitalize
answer
end
我已经看到一些关于STDIN的解决方案,但是并没有真正理解它们,而且我绝对不想复制和粘贴。我唯一能够“运行”的是下面这个,但它并没有真正工作,因为当我运行rspec时,它会暂停并等待输入,只需简单地按下回车键,就通过了:
it "takes user's name and returns it" do
output = capture_standard_output { game.ask_for_name }
expect(output).to eq "What shall I call you today?"
game.input.stub(:gets) { 'joe' }
expect(game.ask_for_name).to eq 'Joe'
end
有什么好的方法可以测试标准输入(STDIN)?我已经盯着屏幕大部分时间了(我知道这不好),所以非常需要新的思路和帮助 :-)
更新
对于任何遇到类似问题的人,我采用了不同(更简单)的方法。首先,我将IO操作分离到它们自己的方法中。
在输入的情况下,在测试中它可以与所需数据预先填充,因此当发送gets
消息时,它将返回由换行符\n
隔开的数据,例如:
input = StringIO.new("one\ntwo\n")
=> #<StringIO:0x007f88152f3510>
input.gets
=> "one\n"
input.gets
=> "two\n"
input.gets
=> nil
这有助于保持测试中方法的内部机制私有,而不将测试与实现细节耦合。
另一种方法是仅使用多态性并传递一个伪造或监视对象,该对象符合相同的API,但不是调用stdin
或stdout
,而是返回预先设定的数据,或者在 Spy 的情况下注册调用。
class SpyIO
def initialize
was_called? = false
end
...
def ask_for_name
# call to stdout would normally take place
was_called? = true
end
...
end
class FakeIO
def initialize(data = [some, data])
@data = data
end
def get_user_input
# call to stdin would normally happen
@data.shift
end
...
end
每种方法都有权衡取舍,但我想把这些放在这里,以防有人遇到类似问题或正在考虑选项。