Last week, I was onsite with a customer, and we were discussing Test-Driven Development. Actually, we were discussing the Unit Testing features of Team System, but since a lot of the books talk about TDD with the Unit Tests, the conversation wandered into it.
To talk about it, I whipped out the bowling example. I fired up Visual Studio, we created a project and a test project, and started writing tests. We got through the first two tests they came up with (scoring for all 0s, and scoring for all 2s) pretty easily. Then we came to the spare test.
Generally the spare test leads to some refactoring of a simple class to hold the rolls from a frame, and whether it is a spare or a strike. Then, in the score, we loop over the frame collection to generate the score. It always seemed like a big step to me, but I wasn’t sure how to make it smaller.
Turns out I just need to get my head out of OO. After we wrote the test, the people I was with came up with a simple solution to make the test pass. Let’s show it in Ruby (just to give me an excuse to jump over to Ruby). We’ll say we already have the following tests passing:
require 'test/unit'
require 'Game'
class GameTest < Test::Unit::TestCase
def test_works
assert(true)
end
def test_all_0s_scores_0
game = Game.new
10.times do
game.roll(0,0)
end
assert_equal(0, game.score)
end
def test_all_2s_scores_40
game = Game.new
10.times do
game.roll(2,2)
end
assert_equal(40, game.score)
end
end
with this class:
class Game
attr_reader :score
def initialize
@score = 0
end
def roll(roll1, roll2)
@score = @score + roll1 + roll2
end
end
We now add the test for spares:
def test_first_spare_rest_2s_scores_48
game = Game.new
game.roll(8,2)
9.times do
game.roll(2,2)
end
assert_equal(48, game.score)
end
which fails with Test::Unit::AssertionFailedError: <48> expected but was <46>.
As I mentioned before, often times here I would jump to a Frame class or something similar, but this team proposed a flag in Game to know if the last frame was a spare, and then add the roll appropriately. In this case it would look like:
class Game
attr_reader :score
def initialize
@score = 0
@last_frame_spare = false;
end
def roll(roll1, roll2)
if(@last_frame_spare)
@score = @score + roll1
end
@score = @score + roll1 + roll2
if (roll1 + roll2) == 10
@last_frame_spare = true
else
@last_frame_spare = false
end
end
end
Which looks a little klunky, but is TSTTCPW. More importantly, it passes our test.
This week I’m going to take a closer look and see how far I can push this. I can see interesting problems when we get to strikes with strikes, but I’m going to push that off for now.