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
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’”
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
Pair One “hmmm…I think I need some more test data and another example for non-recent posts”
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.
Pingback: Tweets that mention TDD: Getting to Green One Small Step at a Time | Carbon Five Community -- Topsy.com
Pingback: TDD: Getting to Green One Small Step at a Time