在Ruby中测试标准输入

16

我目前正在尝试测试一个基本的方法,它从用户获取一些输入(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,但不是调用stdinstdout,而是返回预先设定的数据,或者在 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

每种方法都有权衡取舍,但我想把这些放在这里,以防有人遇到类似问题或正在考虑选项。

2个回答

21

您可以简单地存根STDIN

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?"
  allow(STDIN).to receive(:gets) { 'joe' }
  expect(game.ask_for_name).to eq 'Joe'
end

实际上,您可以使用STDOUT完成相同的操作,而无需更改$stdout

it "takes user's name and returns it" do
  expect(STDOUT).to receive(:puts).with("What shall I call you today?")
  allow(STDIN).to receive(:gets) { 'joe' }
  expect(game.ask_for_name).to eq 'Joe'
end

4

我不能确定,但我在使用$stdin/$stdout/StringIO时遇到的一个问题(仍然存在)是确保对它们运行#rewind。你有这样做吗?

input = StringIO.new
input.puts "joe"
input.rewind
input.gets #=> "joe\n"

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接