【问题标题】:Testing STDIN in Ruby在 Ruby 中测试 STDIN
【发布时间】:2014-06-14 12:25:09
【问题描述】:

我目前正在尝试测试一种基本方法,该方法接收来自用户的一些输入(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 的 ivars:

def ask_for_mark
  ouput.puts 'What shall I call you today?'
  answer = input.gets.chomp.capitalize
  answer
end

现在我已经看到了一些针对 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

什么是测试 STDIN 的好方法?我整天大部分时间都在盯着屏幕(我知道这不太好),因此非常感谢您提供新的视角和一些帮助:-)

更新

对于任何面临类似问题的人,我采用了不同(更简单)的方法。首先,我会在各自的方法中分离 IO 操作。

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

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

这有助于将被测方法的内部保持为私有,而无需将测试与实现细节耦合。

另一种方法是简单地使用多态性并传入符合相同 api 的 Fake 或 Spy 对象,但不是调用 stdinstdout,而是返回罐头数据,或者在间谍,它会注册呼叫。

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

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

【问题讨论】:

    标签: ruby testing rspec stdin


    【解决方案1】:

    您可以简单地存根 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
    

    【讨论】:

      【解决方案2】:

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

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

      【讨论】:

        猜你喜欢
        • 2013-04-04
        • 1970-01-01
        • 1970-01-01
        • 2011-05-15
        • 1970-01-01
        • 2010-09-06
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多