在Ruby中测试STDIN

Mai*_*kon 12 ruby testing stdin rspec

我目前正在尝试测试一个基本方法,它接收来自用户的一些输入(获取)并输出它(puts).经过一番研究后,我找到了测试标准输出流的好方法,如下所示:

def capture_standard_output(&block)
  original_stream = $stdout
  $stdout = mock = StringIO.new
  yield
  mock.string.chomp
ensure
  $stdout = original_stream
end
Run Code Online (Sandbox Code Playgroud)

我正在测试的方法是下面的那个,输出和输入是指我在开头初始化并指向等效的$ stdout&$ stdin的ivars:

def ask_for_mark
  ouput.puts 'What shall I call you today?'
  answer = input.gets.chomp.capitalize
  answer
end
Run Code Online (Sandbox Code Playgroud)

现在我已经看到了STDIN的一些解决方案,但还没有真正了解它们中的任何一个,我绝对不想复制和粘贴.我唯一能够"工作"的是下面的那个,但它并没有真正起作用,因为当我运行rspec它暂停并等待输入时,只需按Enter键,它就会通过:

  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
Run Code Online (Sandbox Code Playgroud)

什么是测试STDIN的好方法?我一直大部分时间一直盯着屏幕(不好,我知道)所以一个新的视角和一些帮助将非常感激:-)

更新

对于任何面临类似问题的人,我都遵循不同的(更简单的)方法.首先,我将在他们自己的方法中分离IO操作.

在输入的情况下,在测试中可以预先填充所需的数据,因此当gets发送消息时,它将返回由换行符分隔的数据,\n如下所示:

input = StringIO.new("one\ntwo\n")
=> #<StringIO:0x007f88152f3510>
input.gets
=> "one\n"
input.gets
=> "two\n"
input.gets
=> nil
Run Code Online (Sandbox Code Playgroud)

这有助于保持被测方法的内部,私有而不将测试耦合到实现细节.

另一种方法是简单地使用多态并传入符合相同api的Fake或Spy对象,但不是调用stdinor 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
Run Code Online (Sandbox Code Playgroud)

每种方法都有权衡,但我认为我会把这些放在这里以防万一有人遇到类似问题或考虑选项.

Uri*_*ssi 18

你可以简单地存根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
Run Code Online (Sandbox Code Playgroud)

实际上,您也可以这样做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
Run Code Online (Sandbox Code Playgroud)