TDD: Getting to Green One Small Step at a Time

Jared Carroll ·

When getting a failing spec to pass, it’s important to do the simplest thing that can possibly work. In Kent Beck’s classic book on TDD, he outlines several approaches for quickly “getting to green”. I regularly use 2 of these to help maintain focus and constantly make progress towards an implementation: faking it and triangulation.

Faking it involves getting a spec to pass by returning exactly what it expects i.e. returning a hard-coded value. The goal is to get the spec passing as soon as possible; this will help you avoid over-thinking and doing more than the minimum amount of work necessary. At this point your first spec is passing but you’re not done yet. You now apply triangulation; writing additional specs to force the removal of the faked implementation and drive towards a real abstraction.

Here are a few examples of these 2 techniques at work during a pairing session (the pairing workflow used is a ping pong style with each pair alternating between spec and implementation).

Faking and Triangulating Copy

Our first spec is: “when a user is logged in, then the header should read ‘logged in'”

Pair One translates this spec into the following RSpec request spec:

describe 'the home page', 'when logged in' do
  before do
    user = Factory :user
    sign_in user

    visit '/'
  end

  it 'says that I am logged in' do
    page.should have_content('logged in')
  end
end

Pair Two fakes it in the view by hard-coding the expected copy in the header:

#header
  %p logged in

Pair One “You forgot to check if the current user is logged in.”

Pair Two “but the spec is already passing”

Pair One “ahhh, I need a spec for the negative context ‘when a user is NOT logged in'”

describe 'the home page', 'when NOT logged in' do
  before do
    visit '/'
  end

  it 'says that I am NOT logged in' do
    page.should_not have_content('logged in')
  end
end

This additional spec drives the pair towards the following “real” implementation:

#header
  - if logged_in?
    %p logged in

Faking and Triangulating Validation

Our next spec is: “all users require an email”.

This time Pair Two starts the spec by first implementing the negative context i.e. a user with NO email:

describe User do

  describe '#valid?' do
    context 'given a user with NO email' do
      before do
        @user = Factory.build(:user,
                              :email => '')
      end

      it 'fails' do
        @user.should_not be_valid
      end
    end
  end

end

Pair One gets it to pass by faking it; returning exactly what the spec is expecting:

class User < ActiveRecord::Base

  def valid?(context = nil)
    false
  end

end
&#91;/sourcecode&#93;

Pair Two "what is that?"

Pair One "well that's all I need to get it to pass"

Pair Two "hmmm...I just forgot the spec for the positive context: 'a user with an email'"

&#91;sourcecode language="ruby"&#93;
describe User do

  describe '#valid?' do
    ...

    context 'given a user with an email' do
      before do
        @user = Factory.build(:user,
                              :email => 'user@example.com')
      end

      it 'passes' do
        @user.should be_valid
      end
    end
  end

end

This second spec drives the pair towards a more flexible implementation:

class User < ActiveRecord::Base

  validates :email,
    :presence => true

end

Faking and Triangulating Finders

The next spec is for a simple finder: “recent posts are posts created in the past week”.

Pair One translates this into the following code:

describe Post do

  describe '.recent' do
    before do
      3.times do
        Factory :post
      end

      @posts = Post.recent
      @posts.should_not be_empty
    end

    it 'finds posts created in the past week' do
      @posts.each do |post|
        post.created_at.should be > 1.week.ago
      end
    end
  end

end

Pair Two fakes it by returning all posts:

class Post < ActiveRecord::Base

  def self.recent
    all
  end

end
&#91;/sourcecode&#93;

Pair One "hmmm...I think I need some more test data and another example for non-recent posts"

&#91;sourcecode language="ruby"&#93;
describe Post do

  describe '.recent' do
    before do
      3.times do |n|
        Factory(:post,
                :created_at => n.days.ago)
      end
      Factory(:post,
              :created_at => 2.weeks.ago)

      @posts = Post.recent
      @posts.should_not be_empty
    end

    it 'finds posts created in the past week' do
      @posts.each do |post|
        post.created_at.should be > 1.week.ago
      end
    end

    it 'does NOT find posts older than a week' do
      @posts.each do |post|
        post.created_at.should_not be > 1.week.ago
      end
    end
  end

end

This spec helps the pair move to the expected implementation:

class Post < ActiveRecord::Base

  def self.recent
    where 'created_at > ?', 1.week.ago
  end

end

Benefits

Getting a failing spec passing using a quick and dirty technique such as faking it is a great way to maintain momentum and to avoid over-engineering. Returning hard-coded values may seem ridiculous at first but I find that in the end, it results in better specified code implemented using the simplest possible thing that could work. This technique is especially fun, as demonstrated above, during a friendly back and forth challenge and response style of pair programming.