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).
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
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 [/sourcecode] 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'" [sourcecode language="ruby"] describe User do describe '#valid?' do ... context 'given a user with an email' do before do @user = Factory.build(:user, :email => 'firstname.lastname@example.org') 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
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 [/sourcecode] Pair One "hmmm...I think I need some more test data and another example for non-recent posts" [sourcecode language="ruby"] 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
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.