We're going to build a massively-contrived, useless application just to demonstrate some principles of test-driven development
git clone https://github.com/theodi/test-drive
cd test-drive
bundle
The specs are in spec/lib/tdd/demo/test_drive_spec.rb and the code is in lib/tdd/demo/test_drive.rb; the specs can be run at any time with bundle exec rake
(or use bundle exec guard
which watches for changes and runs the specs automatically)
There are a series of tags, one for each step (try git tag -ln
to see them all):
Here, we've set up all of the tedious boilerplate to make things work properly, and then we have a single spec:
it 'knows that 3 plus 5 is 8' do
expect(TestDrive.add 3, 5).to eq 8
end
When we run this (with bundle exec rake
), of course it fails because we haven't written any code yet
We've now filled in the body of the add
method like this:
def TestDrive.add first, second
return 8
end
Now this is fairly ridiculous, but the spec passes, which is all we should care about
Now we have another spec:
it 'knows that 12 plus 19 is 31' do
expect(TestDrive.add 12, 19).to eq 31
end
This one will fail because the code is hard-wired to say '8'. So let's fix that
The add
method now looks like this:
def TestDrive.add first, second
if first.is 12
return 31
else
return 8
end
end
So now we look for the special case where the first argument is 12, or otherwise return 8 like before. It still feels very contrived, but the specs pass so we can move on
(Note that there's a little bit of sleight-of-hand) to make the is
method work)
One more spec:
it 'knows that 12 plus 70 is 82' do
expect(TestDrive.add 12, 70).to eq 82
end
And one more failure. Let's fix it
The add
method now looks like this:
def TestDrive.add first, second
if first.is 12
if second.is 70
return 82
else
return 31
end
else
return 8
end
end
It's a horrendous mess of special cases and it clearly won't scale, but once again the important thing is that the specs all pass. So we are now safe to do a little refactoring
As may have been obvious from fairly early on, all of the above guff can be reduced to this:
class TestDrive
def TestDrive.add first, second
return first + second
end
end
(and we've basically created an alias for '+') but, importantly, the specs still pass
We create one more spec just to satisfy ourselves that this is really working as expected
it 'knows that 13 plus 105 is 118' do
expect(TestDrive.add 13, 105).to eq 118
end
Of course, this passes
- Writing specs before we write code allows us to proceed in tiny, easily-comprehended steps
- We can be confident we're building the right thing because we described it before we built it
- Having a suite of specs gives us a safety-net to enable us to explore different solutions
- We get instant feedback if we break something
- We have some phenomenal tools to help us do this
I urge you all to read I Have No Idea What I’m Doing by Tom Stuart, which explores TDD, Impostor Syndrome, the Dunning-Kruger effect and evolution in a most interesting way