使用Ruby抑制控制台输出

22

我正在编写以下类似的单元测试:

def executing_a_signal
  a_method(a_signal.new, a_model, a_helper);
  assert_equal(new_state, a_model.state)
end
测试工作正常,但在执行逻辑前运行的方法会通过puts向控制台打印各种消息。是否有一种快速而简便的内置方法来抑制控制台的输出?我只对该方法对模型对象产生的最终效果感兴趣,为了保持控制台的清洁,我希望能找到一种方法,可以在不重新编写或注释那些puts语句的情况下,简单地防止所有控制台输出,仅用于我的测试。

这绝对不是一个关键问题,但我非常想听听任何想法或想法(或解决方法)。

5个回答

34

我在测试中使用以下代码片段来获取并测试STDOUT

def capture_stdout(&block)
  original_stdout = $stdout
  $stdout = fake = StringIO.new
  begin
    yield
  ensure
    $stdout = original_stdout
  end
  fake.string
end

使用这种方法,上述内容将变为:

def executing_a_signal
  capture_stdout { a_method(a_signal.new, a_model, a_helper) }
  assert_equal(new_state, a_model.state)
end

谢谢,这可能意味着没有标准的内置方式? - denchr
我维护Ruby/Rake的构建脚本,这个技巧甚至可以在使用Ruby sh函数调用devenv(Visual Studio)时禁用控制台输出。非常感谢! - anthonyvd
如果您在测试代码中使用了 STDOUT 常量,那么您将不得不单独重新定义它。 - Alexander Bird
这太棒了。由于我们正在使用的一个 gem 不断生成输出(可能有人在其中留下了 puts),这是一种轻松解决此问题而不需要猴子补丁的方法。谢谢! - Ten Bitcomb

11

这是对@cldwalker解决方案的略微改进:

def silenced
  $stdout = StringIO.new

  yield
ensure
  $stdout = STDOUT
end

silenced do
  something_that_prints
end

4
有两种解决方案:重定向puts输出的位置(@cldwalker提出的解决方案),或者覆盖puts方法本身,使其成为无操作。(具体实现很明显:module Kernel; def puts(*args) end end)。
然而,在这种情况下,“倾听你的测试”才是最好的解决方案。因为,通常当某些东西难以测试时,你的测试真正想告诉你的是你的设计有问题。在这种情况下,我嗅到了单一职责原则的违反:为什么一个模型对象需要知道如何写入控制台?它的职责是代表一个领域概念,而不是日志记录!这就是Logger对象的作用!
因此,另一个解决方案是让模型对象将日志记录的责任委托给Logger对象,并使用依赖注入为模型对象注入合适的Logger对象。这样,你可以简单地为测试注入一个虚拟的日志记录器。以下是一个示例:
#!/usr/bin/env ruby

class SomeModel
  def initialize(logger=Kernel) @logger = logger end
  def some_method_that_logs; @logger.puts 'bla' end
end

require 'test/unit'
require 'stringio'
class TestQuietLogging < Test::Unit::TestCase
  def setup; @old_stdout, $> = $>, (@fake_logdest = StringIO.new) end
  def teardown; $> = @old_stdout end

  def test_that_default_logging_is_still_noisy
    SomeModel.new.some_method_that_logs

    assert_equal "bla\n", @fake_logdest.string
  end

  def test_that_logging_can_be_made_quiet
    fake_logger = Object.new
    def fake_logger.puts *args; end

    SomeModel.new(fake_logger).some_method_that_logs

    assert_equal '', @fake_logdest.string
  end
end

至少,Model对象应该将其记录“到”的IO对象作为参数传入,这样您就可以在测试中简单地注入StringIO.new

#!/usr/bin/env ruby

class SomeModel
  def initialize(logdest=$>) @logdest = logdest end
  def some_method_that_logs; @logdest.puts 'bla' end
end

require 'test/unit'
require 'stringio'
class TestQuietLogging < Test::Unit::TestCase
  def setup; @old_stdout, $> = $>, (@fake_logdest = StringIO.new) end
  def teardown; $> = @old_stdout end

  def test_that_default_logging_is_still_noisy
    SomeModel.new.some_method_that_logs

    assert_equal "bla\n", @fake_logdest.string
  end

  def test_that_logging_can_be_made_quiet
    fake_logdest = (@fake_logdest = StringIO.new)

    SomeModel.new(fake_logdest).some_method_that_logs

    assert_equal '', @fake_logdest.string
    assert_equal "bla\n", fake_logdest.string
  end
end

如果你仍然希望在你的模型中只需说puts whatever或者你担心有人可能会忘记调用logger对象上的puts方法,那么你可以提供自己的(私有的)puts方法:

class SomeModel
  def initialize(logdest=$>) @logdest = logdest end
  def some_method_that_logs; puts 'bla' end
  private
  def puts(*args) @logdest.puts *args end
end

1

重新打开'/dev/null'

另一个选项是通过以下方式将其重定向到/dev/null

STDOUT.reopen('/dev/null', 'w')
STDERR.reopen('/dev/null', 'w')

这种技术被用于stdlib的WEBrick::Daemon(切换源代码)。
它比StringIO.new更高效,因为它不会将stdout存储在StringIO上,但可移植性较差。

0

我刚才在我的.rb文件的开头使用了下面的代码,这样它就可以禁用所有控制台打印语句。

 def puts(*args) end

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