22

我正在编写一些单元测试,如下所示:

def executing_a_signal
  a_method(a_signal.new, a_model, a_helper);
  assert_equal(new_state, a_model.state)
end

测试工作正常,但是在断言之前运行以执行逻辑的方法将各种消息打印到控制台,主要是通过puts.

是否有一种快速的(可能是内置的)方法来抑制向控制台的输出?我只对该方法对模型对象的最终效果感兴趣,并且为了基本保持控制台清洁,我希望找到一种方法来简单地阻止所有输出到控制台而不重写或注释掉那些puts仅用于我的测试的陈述。

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

4

5 回答 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
于 2009-09-30T03:52:03.953 回答
10

@cldwalker 的解决方案稍微干净一点:

def silenced
  $stdout = StringIO.new

  yield
ensure
  $stdout = STDOUT
end

silenced do
  something_that_prints
end
于 2016-09-14T08:33:59.403 回答
4

有两种解决方案:重定向puts写入位置(上面@cldwalker 给出的解决方案),或者将puts方法本身覆盖为无操作。(实现应该很明显:)module Kernel; def puts(*args) end end

但是,在这种情况下,真正最好的解决方案是“听你的测试”。因为,通常当某些东西难以测试时,您的测试实际上是在试图告诉您您的设计有问题。在这种情况下,我闻到了对单一责任原则的违反:为什么模型对象需要知道如何写入控制台?它的职责是代表一个领域概念,而不是记录!这就是 Logger 对象的用途!

因此,另一种解决方案是让 Model 对象将日志记录的责任委托给 Logger 对象,并使用依赖注入向 Model 对象注入合适的 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在您的模型中说,或者您担心有人可能会忘记调用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
于 2009-09-30T16:10:47.950 回答
1

reopen '/dev/null'

另一种选择是重定向到/dev/null

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

此技术用于WEBrick::Daemonstdlib(切换源)。

它的优点是效率更高,StringIO.new因为它不将 stdout 存储在 a 上StringIO,但它的可移植性较差。

于 2014-10-10T18:42:49.053 回答
0

我只是在 .rb 文件的开头使用了下面的代码..所以它禁用了所有控制台打印语句..

 def puts(*args) end
于 2017-08-02T10:13:55.737 回答