Monday, August 24, 2009

Record.Exception for Ruby TDD

I've been working on a Ruby chat server (well, it's actually more of a connection broker for peer-to-peer chat clients) for an upcoming app I'm going to release. TDD is how I roll, but I haven't done much of it in Ruby because the tooling and workflow is so very different than what I'm used to in the .NET world. Today, finally, I settled into a workflow with TextMate and Test::Unit that makes me happy.

I use xUnit.net in the .NET world, which was created and is maintained by my friend Brad and my skip-level boss Jim. There is one thing from xUnit.net that I was really missing when using Test::Unit: Record.Exception. Here is a C# example of how Record.Exception can be used:
using System;
using System.Collections.Generic;
using Xunit;

namespace ColorCode.Compilation
{
    public class LanguageRule_Facts
    {
        public class Constructor_Facts
        {
            [Fact]
            public void It_will_throw_when_the_regex_is_null()
            {
                Dictionary captures = new Dictionary { { 0, "fnord" } };

                Exception ex = Record.Exception(() => new LanguageRule(null, captures));

                Assert.IsType(ex);
                Assert.Equal("regex", ((ArgumentNullException)ex).ParamName);
            }
        }
    }
}
The important thing about Record.Exception is that it allows you to stay true to the 3A pattern (arrange, act, assert), even though you are dealing with an exception. This is pretty important to me. So I threw together a quick and dirty Ruby method to do the same thing:
def record_exception
  begin
    yield if block_given?
    return nil
  rescue Exception => ex
    return ex
  end
end
Yes, it's ridiculously simple. But now I can do this:
require "test/unit"
require "assert"
require "client"

class ClientTests < Test::Unit::TestCase
  def test_init_will_throw_when_io_is_nil
    ex = record_exception do
      client = Client.new(nil, Object.new) 
    end
    
    assert_not_nil(ex)
    assert_equal(ex.to_s, "io must not be nil.")
  end
end
This solves the second-most severe pain point I had with Ruby TDD. There are many more yet, and they'll likely be the subject of future 'blog posts.

http://twitter.com/anglicangeek