One of the canonical examples of Test Driven Development is Ron Jeffries‘ Bowling Game. It’s been done many times, and Ron has done several articles about various methods and outcomes from his experiences in doing the game for groups.
A couple of months ago, I found out I was going to be speaking at a Code Camp in Tampa, FL. One of my sessions was on TDD, and I decided what better example then the bowling game? Except – I’ve never actually gone through the bowling game example, or fully read through Ron’s articles on it. So I decided to give it a go and do the bowling game to see what path it led me down.
I fired up Visual Studio and created two projects – Bowling
and BowlingTests
. I then created a new class in BowlingTests
called GameTests
and created my first test method:
using System;
using NUnit.Framework;
namespace BowlingTests
{
[TestFixture]
public class GameTests
{
[Test]
public void ThisWorks()
{
Assert.IsTrue(true);
}
}
}
I built the project, fired up NUnit, and pointed it at my newly minted BowlingTests
dll. Running it got me:
Great! Everything is hooked up, and I’m ready to get down to business. Now, I know that a regular bowling game consists of 10 frames with up to 2 tries for each frame, except for the last frame when you can roll 3 times if the first roll was a strike or the second was a spare. Whew! That sounds like it could get complex, so let’s think of a case which wouldn’t require us to invoke that last frame rule – I know, all gutter balls!
[Test]
public void AllGutterBallsShouldScore0()
{
BowlingGame game = new BowlingGame();
for(int frame=1; frame<=10; frame++)
{
game.RollFrame(0,0);
}
Assert.AreEqual(0, game.Score, "All gutters should have a final score of 0");
}
Ok. Well, compiling this fails, since we don’t have a BowlingGame
class, much less all of those methods. Let’s create just enough to get this to compile. So over in the Bowling assembly, I create a BowlingGame
class:
using System;
namespace Bowling
{
public class BowlingGame
{
public BowlingGame(){}
public void RollFrame(int roll1, int roll2){}
public int Score
{
get{return 0;}
}
}
}
And…everything compiles! Let’s run our tests to see what we need to do next:
Wow! All our tests pass. How is that possible? Well, if we look at our test, we are expecting after all the rolls for the score to be 0. Since we stuck in a 0 to make the compiler happy in our Score
property, it made our test pass as well. The lesson here is that you should always run your tests – you never know when one might pass unexpectedly.
Ok, enough of that, let’s get a real test under our belts. The next thing I can think of is a game where we roll all 2s, or a total of 4 per frame, giving us a total of 40. Let’s create that test:
[Test]
public void All2sShouldScore40()
{
BowlingGame game = new BowlingGame();
for(int frame=1; frame<=10; frame++)
{
game.RollFrame(2,2);
}
Assert.AreEqual(40, game.Score, "All 2s should have a final score of 40");
}
And let’s run that:
Aha! A failing test. Let’s get it passing by doing some scoring in our BowlingGame. The class now looks like:
public class BowlingGame
{
private int score = 0;
public BowlingGame(){}
public void RollFrame(int roll1, int roll2)
{
score += (roll1 + roll2);
}
public int Score
{
get{return score;}
}
}
Pretty easy, we just keep adding the frames to the score, and return it. Let’s see if it passes our tests:
Now that we have a green test, let’s look at our code. TDD is a cycle which goes Red->Green->Refactor. In other words, write a failing test, write the code to make it pass, and refactor duplication. Looking in our BowlingGame class, we don’t see much duplication, but in our tests we do. Since our tests are code like anything else, we can using refactoring techniques like extract method to help clean it up. The main difference is that readability is key in tests, so be careful not to refactor so much that it becomes difficult to understand what is going on in the tests.
The first duplication is around the setup of the BowlingGame. We can make that an instance variable, and create it before our tests are run. NUnit helps us out by providing a [SetUp]
attribute we can tag a method with. So, we now have something like:
BowlingGame game = null;
[SetUp]
public void SetUp()
{
game = new BowlingGame();
}
SetUp
will be run before every test, reinitializing our game variable. In addition, you can have a [TearDown]
method to do any cleanup.
Now our test methods look like:
[Test]
public void AllGutterBallsShouldScore0()
{
for(int frame=1; frame<=10; frame++)
{
game.RollFrame(0,0);
}
Assert.AreEqual(0, game.Score, "All gutters should have a final score of 0");
}
I compile and run the tests to make sure nothing breaks, and all is green. But I still see some more duplication, in the rolls. So, let’s extract out a method called RollFrames
which takes in a pin count for roll 1 and 2, and does that for the specified number of frames:
private void RollFrames(int roll1, int roll2, int numberOfFrames)
{
for(int frame=0; frame < numberOfFrames; frame++)
{
game.RollFrame(roll1, roll2);
}
}
And now our test methods look like:
[Test]
public void AllGutterBallsShouldScore0()
{
RollFrames(0,0,10);
Assert.AreEqual(0, game.Score, "All gutters should have a final score of 0");
}
And again I run the tests, and they stay green. It is important to run your tests after every change, and to keep them running fast enough that you can do that.
Well enough about that, let’s get some spares going! In bowling, you score a spare by knocking down all remaining pins during your second roll of a frame. To score it, you get 10 points, plus whatever your next roll is. Rather than explain some scenarios, let’s write some!
In our first case, our first roll is a spare, and the remaining rolls are 2s:
[Test]
public void FirstFrameSpare
Rest2sShouldScore48()
{
game.RollSpare(2,8);
RollFrames(2,2,9);
Assert.AreEqual(48, game.Score, "Should have a final score of 48");
}
Why 48? Well, because our first frame is a spare, and the first roll of the next frame is a 2, the score for frame 1 is 12. We then add that to the remaining 9 frames which are each 4 points (2 for each roll).
We would run the tests, but our code isn’t compiling, because RollSpare isn’t valid. Let’s add it to our BowlingGame class:
public void RollSpare(int roll1, int roll2) {}
Ah, much better. Now, let’s run our tests:
Which makes sense, because we aren’t actually doing anything inside the RollSpare method, so Frame 1 isn’t being added to the total score. However, now we are at a design point. In order to score Frame 1, we have to know what Frame 2‘s first roll is, which also means we need to know what each roll for a frame was.
There are a couple of directions, but I’m going to create a small Frame class which holds two variables, Roll1 and Roll2, and a flag to say if that frame was a spare. So, in my Bowling assembly, I create Frame.cs:
using System;
namespace Bowling
{
public class Frame
{
private int roll1;
private int roll2;
public Frame(int roll1, int roll2)
{
this.roll1 = roll1;
this.roll2 = roll2;
}
public int Roll1 { get { return roll1; } }
public int Roll2 { get { return roll2; } }
}
}
and switch BowlingGame to use this:
using System;
using System.Collections;
namespace Bowling
{
public class BowlingGame
{
private int score = 0;
private ArrayList frames;
public BowlingGame()
{
frames = new ArrayList();
}
public void RollFrame(int roll1, int roll2)
{
frames.Add(new Frame(roll1, roll2));
}
public void RollSpare(int roll1, int roll2)
{
}
public int Score
{
get
{
foreach(Frame frame in frames)
{
score += (frame.Roll1 + frame.Roll2);
}
return score;
}
}
}
}
So we see that Score
is now responsible for calculating the score from the Frames. Notice also that we still haven’t done anything with RollSpare
mainly because I felt this was a bigger step and wanted to make sure we didn’t break anything. So, we should be able to rerun our tests and still see the Spare test failing:
Whew. I didn’t really like doing that on the fly, but it seems to be Ok. So, let’s get that spare test passing. I change RollSpare to look like:
public void RollSpare(int roll1, int roll2)
{
Frame frame = new Frame(roll1, roll2);
frame.IsSpare = true;
frames.Add(frame);
}
and also change the Frame class to be aware of the IsSpare property:
public bool IsSpare
{
get{return isSpare;}
set{isSpare = value;}
}
Running our tests shows that we are closer…
…but no cigar. That’s because our score method doesn’t know about Spares, so it is just blindly adding together scores. Let’s fix that:
public int Score
{
get
{
for(int currentFrame = 0; currentFrame < frames.Count; currentFrame++)
{
Frame frame = (Frame)frames[currentFrame];
score += (frame.Roll1 + frame.Roll2);
if(frame.IsSpare)
{
Frame nextFrame = (Frame)frames[currentFrame+1];
score += nextFrame.Roll1;
}
}
return score;
}
}
And let’s run our tests:
Yay, they’re green! So let’s look at our code. The first thing I notice is that Score
is returning the variable score, but isn’t declaring it. Ah, it was declared as a field instead of as a local variable. We fix that and run our tests. Still green! Secondly, I don’t like the casting, as I know we can use generics, but since I have control over the Array, I’m not going to worry about it for right now. I also see the following line:
score += (frame.Roll1 + frame.Roll2);
Surely we can just ask the frame for the total roll, so let’s modify Frame
:
public int TotalRoll { get { return roll1 + roll2; } }
And modify our Score
:
score += frame.TotalRoll;
A quick compile and run of the tests – yep, we’re still green!
Ok, now that we seem to have Spares working, let’s make sure. First, let’s do a game with the first 2 frames as spares, and the rest as 2s:
[Test]
public void FirstTwoFramesSpareRest2sShouldScore52()
{
game.RollSpare(2,8);
game.RollSpare(2,8);
RollFrames(2,2,9);
Assert.AreEqual(52, game.Score, "Should have a final score of 52");
}
A quick compile and run of the tests:
Egads! 60? Hmm, maybe my math was off, let me see. Aha! I see the problem. I copied the test from the above one, and added in the second Roll Spare. I forgot, however, to change the RollFrames from 9 to 8. Let me fix that and then we can clean it up…
Egads! Ok, this time my math was off. The first two frames equal 24, leaving 8 remaining frames, giving us a total of 56. Let’s fix that:
[Test]
public void FirstTwoFramesSpareRest2sShouldScore56()
{
game.RollSpare(2,8);
game.RollSpare(2,8);
RollFrames(2,2,9);
Assert.AreEqual(56, game.Score, "Should have a final score of 56");
}
And…
All green! Now I feel a little more confident in the scoring capabilities (and less confident in my addition skills).
Let’s go back to the problem we had above. A game should never have mor
e than 10 frames (unless you are Ishmael Boorg). So, let’s write a test that ensures that:
[Test]
[ExpectedException(typeof(OutOfFramesException))]
public void TryingToBowlMoreThan10FramesThrowsOutOfFramesException()
{
RollFrames(2,2,11);
}
We’re using another handy feature of NUnit – ExpectedException. This test will fail if the exception declared does not get thrown. Of course, this test isn’t compiling, so we’ll add the exception to our Bowling assembly and recompile. Now, let’s run our tests:
We’ll make this pass by tracking all frame adds through a method called AddFrame:
private void AddFrame(Frame frame)
{
if(frames.Count == 10)
{
throw new OutOfFramesException();
}
frames.Add(frame);
}
This is probably not the best solution, since we are relying on convention to enforce a business constraint. But since our public contract controls access to the frames array through RollFrame and RollSpare, it should be fine for now.
Actually, we don’t know if it is a solution, because I haven’t run the tests yet! Let’s see if it is:
And, it looks like it is! That should help keep us from making silly mistakes in our tests. And it should help guide us when we come back to finish up strikes and the infamous 10th frame in the next article. Until then, happy bowling!
Beautiful. Now, I wonder how this scales up, for one thing, and for another… How is one supposed to guard against writing silly or subtly broken tests, especially in situations where you can’t make that many assumptions about the data?
Thanks. Obviously this is a small example, but there are large projects out there which are developed using TDD. For example, NUnit itself, while not *that* large of a project, was written using TDD, and new features are required to have Unit tests around it.
As far as guarding against silly tests – it happens. If you are doing code reviews or pair programming, and you are keeping in contact with your customer, it should limit them. But I’ve certainly written tests which made bad assumptions. But when that happens, I’ve almost always found it easier to track down, since I can see the assumptions I was making in the test.