Posts Tagged ‘output’

stdout output tests

Wednesday, April 1st, 2009

I was working on simple_example a few days ago and needed to test the output’s formatting.

The first step is to capture the output so that we can compare it later. A simple way to do this is to swap $stdout for a StringIO object. ($stdout is an IO object. Kernel#puts and Kernel#print are wrappers around $stdout.puts and $stdout.write)

def keep_stdout(&block)
  begin
    orig_stream, $stdout = $stdout, StringIO.new
    block.call($stdout)
  ensure
    s, $stdout = $stdout.string, orig_stream
    s
  end
end

Clearly we want to restore the stdout stream after we are done with our call so that we can get out test restults printed.

Next, let’s create an assertion that wraps our stdout capture method.

def assert_output(expected, &block)
  keep_stdout do |stdout|
    block.call
    if expected.is_a?(Regexp)
      assert_match expected, stdout.string
    else
      stdout.string.should be(expected.to_s)
      assert_equal expected.to_s, stdout.string
    end
  end
end

The regexp matching is so we can assert that the output contains what we’re looking for, as opposed to being exactly equal to it.

Now we can do:

assert_output('abc') { puts "abc" }      #=> true
assert_output(/abc/) { puts "abc\ndef" } #=> true

If you have slightly more complex strings that you want to partially match, remember that you have to escape them first as they might contain special regexp chars. This can be achieved with a small string extention:

class String
  def to_regexp
    Regexp.new(Regexp.escape(self))
  end
end

#to_regexp is probably a bad name, but it’s only a test helper so it’ll work for now.

This allows:

assert_output("(1 + 1)".to_regexp) do
  puts "(2 + 2)\n(1 + 1)\nabc"
end
#=> true

To test for string formatting, though, I needed the expected string to be more complex than ‘abc’.

def test_formatting
  out = <<-STR.unindent
        (1 + 1)
    #=> 2
        (2 + 2)
    #=> 4
  STR
  # ...
end

This uses the small unindent gem to allow us to keep normal indentation; otherwise we’d have to write:

def test_formatting
  out = <<-STR
    (1 + 1)
#=> 2
    (2 + 2)
#=> 4
  STR
  # ...
end

Which works fine, of course. Just not as fun.

Putting it all together

require 'test/unit'
require 'rubygems'
require 'unindent'

class OutputTest < Test::Unit::TestCase
  def test_formatting
    out = <<-STR.unindent
          (1 + 1)
      #=> 2
          (2 + 2)
      #=> 4
      ----------
          (3 + 3)
      #=> 6
      ----------
    STR
    assert_output(out) do
      @obj.first_method
      @obj.other_method
    end
  end
end

You can see real life usage in simple_example’s test file.