views:

372

answers:

3

Hello,

I am writing some unit tests in ruby and have tests like the following:

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

The tests work fine, but the method which runs just before the assertion to execute the logic prints various messages to the console (mainly via puts methods).

Is there a quick, perhaps built-in way to suppres that output to the console? I am only interested in the final effect of the method on the model object, and for the sake of keeping the console clean basically, I was hoping to find a way to simply prevent all output to the console without re-writing commenting out those puts statements just for my tests.

It is definitely not a critical issue, but would very much like to hear any thoughts or ideas (or workaround) on it.

Many thanks.

+5  A: 

I use the following snippet in tests to capture and test STDOUT

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

With this method, the above would become:

  def executing_a_signal
    capture_stdout { a_method(a_signal.new, a_model, a_helper) }
    assert_equal(new_state, a_model.state)
  end
Thanks, this probably means that there is no standard built-in way?
denchr
This works very nicely by the way
denchr
Pretty neat solution, bravo!
khelll
A: 

There is also another variable in reference

$> The default output for print, printf. $stdout by default.

I can't find any difference between them at the moment. Perharps there are some methods out there, implemented to use $stdout without wrapping it with print?

kirushik
A: 

There's two solutions: redirecting where puts writes to (the solution given by @cldwalker above), or overwriting the puts method itself to be a no-op. (The implementation should be obvious: module Kernel; def puts(*args) end end).

However, in this case, what would really be the best solution is "listening to your tests". Because, oftentimes when something is awkward to test, your tests are really trying to tell you that something is wrong with your design. In this case, I smell a violation of the Single Responsibility Principle: why the heck does a Model object need to know how to write to the console? Its responsibility is representing a Domain Concept, not logging! That's what Logger objects are for!

So, an alternative solution would be to have the Model object delegate the responsibility for logging to a Logger object, and use dependency injection to inject the Model object with a suitable Logger object. That way, you can simply inject a fake logger for the test. Here's an example:

#!/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

At the very least, the Model object should take the IO object that it is logging to as an argument, so that you can simply inject StringIO.new into it for the test:

#!/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

If you still want to be able to just say puts whatever in your Model or you are afraid that someone might forget to call puts on the logger object, you can provide your own (private) puts method:

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
Jörg W Mittag