Test Driving Your Tests by Writing Them Backwards

Jared Carroll ·

In many traditional software development methodologies you write tests for your code after you write your code. TDD reverses this process by requiring you to write your tests before you write your code. By writing your test first you immediately take the viewpoint of a client of the code you are about to write. This results in code that provides exactly what you need, nothing more and nothing less. But how can we get this same benefit in our test code?

We don’t write tests for our tests so how can we ensure our test code is as simple as it can be? One technique is to take the same approach to test writing as TDD did to traditional testing: write your tests backward.

Test Structure

A test is best described as having 4 distinct phases:

1) Setup (Given)
2) Exercise (When)
3) Verify (Then)
4) Teardown

The Verify phase is where you ask yourself what are you trying to achieve, by starting at the Verify phase you know exactly what you need from your test code. [1]

Start at the End

Let’s turn the following Rails spec into code:

an article identifier in a URI includes the article’s id and its title slugified.

We’ll use the Rails convention of overriding ActiveRecord::Base#to_param and implement this spec starting with the Verify phase i.e. our expectations.

Then

describe Article do

  describe '#to_param' do
    it 'returns its id and its title slugified' do
      @param.should == "#{@article.id}-#{@title.parameterize}"
    end
  end

end

By starting with the Verify phase we’re writing the test code we wish we had.

Running it results in the following failure:

Failures:
1) Article#to_param returns its id and its title slugified
Failure/Error: @param.should == "#{@article.id}-#{@title.parameterize}"
RuntimeError:
Called id for nil, which would mistakenly be 4 -- if you really wanted the id of nil, use object_id

This error guides us to the next phase, the Exercise phase.

When

describe Article do

  describe '#to_param' do
    before do
      @param = @article.to_param
    end

    it 'returns its id and its title slugified' do
      @param.should == "#{@article.id}-#{@title.parameterize}"
    end
  end

end

After implementing the Exercise phase we’re still failing because of the same error related to missing setup. Now we can implement the Setup phase.

Given

describe Article do

  describe '#to_param' do
    before do
      @title = 'title'
      @article = Factory(:article,
                         :title => @title)
      @param = @article.to_param
    end

    it 'returns its id and its title slugified' do
      @param.should == "#{@article.id}-#{@title.parameterize}"
    end
  end

end

Running it results in the following failure:

Failures:

1) Article#to_param returns its id and its title slugified
Failure/Error: @param.should == "#{@article.id}-#{@title.parameterize}"
expected: "1-title"
got: "1" (using ==)

We now have a legitimate failure and can proceed to the implementation. [2]

Nothing More and Nothing Less

The simple example above illustrates how we, in a way, test drove our test. The result was a test that contained exactly what it needed. Shortly after adopting TDD you probably realized how you no longer had “extra” code lying around because every line of code was justified by a failing test. By writing your tests backwards you’ll get that same feeling and confidence in your test code.


[1] Technically the Teardown phase is the last phase, but its usually not explicitly used in most tests; even if it is used you still want to start at the Verify phase and do the Teardown phase last.

[2] It’s important to use continuous testing tools such as guard and autotest in order to enjoy a workflow at this level of granularity; in my opinion I can’t see using this technique without such tools.