Tip One – Debugging tests

I use RDebug in my apps a lot, but frequently I found myself wanting to debug my tests. Here’s how you can do it:

If you haven’t done so already, install RDebug

  
$ gem install ruby-debug

Insert debugger in your code where you’d like to breakpoint

  
def like_bananas?  
  # Want to breakpoint here and see what the self.fruit attribute looks like
  debugger
  return self.fruit.likes.include?('bananas')
end  

And then run the test from the command line using rdebug like this:

  
rdebug test/unit/bananas_test.rb  

You could even put the debugger reference in your test code as well.

Tip Two – Gently ease yourself into BDD with shoulda

Everybody’s talking about Behaviour Driven Development these days – and the popular BDD tool for Rails is the mighty RSpec.

But, it can seem like quite a bit of a jump to get into RSpec. Maybe you’ve got a well established application with a plethora of Test::Unit tests that you can’t bear to look at. Or maybe you just wanna try out the whole BDD approach for a while and see how it goes without really committing.

Well never fear – there’s a really easy way of easing yourself into BDD and it’s called shoulda. This is a great BDD tool made by the crazy freakin’ geniuses (genii?) over at thoughtbot.

Basically, shoulda is a BDD framework built on top of Test::Unit. It uses all the Test::Unit assertions that we know and love – adds some exotic delicous syntactic BDD sugar – and throws some wonderful helper macros in as freebies..

The thoughtbot folks rabbit on about it over here: http://thoughtbot.com/projects/shoulda.

An example test unit class might look like this:

  
class FruitBowlTests < Test::Unit::TestCase  
  def setup
    @fruit_bowl = get_fruit_bowl
  end

  # Check out this BDD coolness
  should "be best before date" do
    assert @fruit_bowl.fresh?
  end

  # But you can still include old skool tests like this
  def test_fruit_bowl_has_three_fruit
    assert_equal 3, @fruit_bowl.fruit.size
  end
end  

So you can try out BDD without messing up your current tests – hooray! Shoulda actually provides way more than this though – but don’t take my word for it, gem install shoulda today!

PS: Not only that, but the shoulda guys include a script that modifies your Test::Unit tests and changes them into shoulda style tests. Run it like this:

  
$ ./vendor/plugins/shoulda/bin/convert_to_should_syntax [path to test file]

Tip Three – DRY up your tests with define method

Ever find yourself writing the same tests over and over again but with slightly different modifications – like this:

  
def test_banana_should_be_nil_if_fruit_bowl_rotten  
  @fruit_bowl = FruitBowl.new(:rotten => true)
  assert_nil @fruit_bowl.banana
end

def test_apple_should_be_nil_if_fruit_bowl_rotten  
  @fruit_bowl = FruitBowl.new(:rotten => true)
  assert_nil @fruit_bowl.apple
end

def test_pear_should_be_nil_if_fruit_bowl_rotten  
  @fruit_bowl = FruitBowl.new(:rotten => true)
  assert_nil @fruit_bowl.pear
end  

Using the wonderful Ruby methods define_method and send you can dry this up really easily like thus:

  
# Put the different bits into an array
[:banana, :apple, :pear].each do |fruit|
  # Iterate over the array and create a test method for each object.
  # Remember to prefix the method with 'test' when using Test::Unit
  define_method "test_#{fruit}_should_be_nil_if_fruit_bowl_rotten" do
    @fruit_bowl = FruitBowl.new(:rotten => true)
    # And use the send method to check the correct attribute
    assert_nil @fruit_bowl.send(fruit)
  end
end  

Tip Four – Test the objects behaviour

Here’s a tip that should revolutionise your testing methodology forever…

Make sure you test the correct behaviour for an object

What this means is that when testing an object, don’t create a test that looks outside of its scope.

For example:

Up until very recently I used to be a big fixtures man. It wasn’t uncommon for me to write functional tests like this:

  
def test_transfers_heading_should_be_on_page  
  get :show, :id => festivals(:festival_with_transfers_heading)
  assert_select 'h2#heading', "Transfers"
end  

Which was to test that the h2 with the id of ‘heading’ displayed the correct text, in this case “transfers”.

But deep down inside I was relying on this test to do more than what it should be doing. Whenever this test was run it had to load the festival from the database, and show the correct template. However, the scope of the test though was just to test that the correct information was shown in the view, anything else was irrelevant to this test.

A better test could look like this:

  
def test_transfers_heading_should_be_on_page  
  festival = Festival.new(:heading => 'Transfers')
  Festival.stubs(:find).returns(festival)
  get :show, :id => 1
  assert_select 'h2#heading', "Transfers"
end  

The database doesn’t get hit because it doesn’t need to get hit. All we’re testing here is that the correct heading is displayed on the page – that the controller fulfilled its obligation by marshalling the data from the model to the view.

Tip Five – Expectations are tests too you know!

Mocks Rock – there I said it!

But, you can get so obsessed with stubbing out all manner of stuff for your assertions that you can mess out on all the nice inbuilt assertions built into mock objects – expectations.

For example, you might have an ActiveRecord observer that does some stuff after a model is saved like this:

  
class FruitObserver < Observer  
  def after_create(fruit)
  # Add to all the inventory lists
    InventoryList.find(:all).each do |i|
      i.stock.push fruit
    end
  end
end  

You could use a mocha expectation to test this without using a single assert_blah like this:

  
def test_when_creating_banana_observer_is_called  
  banana = Fruit.new(:name => "banana")
  InventoryList.any_instance.expects(:push).with(banana)
  banana.save
end  

You don’t need to have assert_blah in all your tests – this is a perfectly acceptable test because it includes the expectation InventoryList.any_instance.expects(:push).with(banana).

Need to find a lawyer for your business? Get in touch with my company Lexoo and we'll find you a great one for free.