My last post ended with this bold assertion:
If I am not mistaken, I have written enough code to pass the acceptance tests for this story.
My first attempt at running the tests -
java.lang.AssertionError: firstBallInFrame is not implemented yet- reminds me that I first need to hookup the adapter methods.
@Test
public void hookUpAdaptorMethods() {
deliverBall(0);
assertThat(gameOver(), is(false));
assertThat(numberOfFrames(), is(10));
assertThat(firstBallInFrame(1), is("0"));
assertThat(secondBallInFrame(1), is(""));
}
@Override
protected String firstBallInFrame(int frameIndex) {
return getFrame(frameIndex).getFirstBall();
}
@Override
protected String secondBallInFrame(int frameIndex) {
return getFrame(frameIndex).getSecondBall();
}
private Frame getFrame(int frameIndex) {
int counter = 1;
for (Frame frame : game) {
if(counter++ == frameIndex){
return frame;
}
}
throw new IllegalArgumentException("Bad frame : " + frameIndex);
}
My second attempt gives a different surprise:
java.lang.AssertionError: Expected: is "-" got : "0". D'oh! I am not done! Gutter balls need to be shown as "-". Back to the unit tests.
@Test
public void gutterBallShouldBeDash() {
frame.addBall(0);
assertThat(frame.getFirstBall(), is("-"));
}
The code to make that pass is...
private String formatBall(Integer ball) {
if (ball == null)
return "";
else if(ball == 0){
return "-";
}
else {
return ball.toString();
}
}
After I change the '0'
for a '-'
in hookUpAdapterMethods()
, I see a whole bunch more failures:
Oh yes, scoring...that's was kinda the whole point of this exercise. I should add up the scores...
@Test
public void shouldShowTheScoreForCompletedFrame() {
frame.addBall(4);
frame.addBall(5);
assertThat(frame.getScore(), is("9"));
}
public String getScore() {
return Integer.toString(firstBall + secondBall);
}
...and...
@Test
public void shouldNotShowTheScoreForIncompleteFrame() {
frame.addBall(4);
assertThat(frame.getScore(), is(""));
}
public String getScore() {
return needsMoreBalls()
? ""
: Integer.toString(firstBall + secondBall);
}
...and now we get to the tricky stuff. Frames only know their own scores but the spec wants me to show the cumulative score. What if I use a recursive definition of the frame score?
frame score = previous frame score + first ball + second ball
I can implement that by giving each frame a reference to the previous frame. Here's the test...
@Test
public void shouldAccumulateTheScoreFromPreviousFrame() {
Frame nextFrame = new Frame(frame);
frame.addBall(3);
frame.addBall(4);
nextFrame.addBall(3);
nextFrame.addBall(4);
assertThat(nextFrame.getScore(), is("14"));
}
...and to make it pass, I need a new constructor...
public Frame(Frame previousFrame) {
this.previousFrame = previousFrame;
}
...I'll extract a method to get the accumulated score...
public String getScore() {
return needsMoreBalls()
? ""
: Integer.toString(getCumulativeScore());
}
private int getCumulativeScore() {
return firstBall + secondBall;
}
...and add in the value from the previous frame.
private int getCumulativeScore() {
int cumulativeScore = firstBall + secondBall;
if(previousFrame != null){
cumulativeScore += previousFrame.getCumulativeScore();
}
return cumulativeScore;
}
To make it work for Game
, I can hookup the previousFrame
reference
as I construct each frame. Here's the test and the change to the initilization of frames
:
@Test
public void frameScoreShouldAccumulate() {
game.bowl(1);
game.bowl(2);
game.bowl(3);
game.bowl(4);
Iterator<Frame> frames = game.iterator();
Frame frameOne = frames.next();
assertThat(frameOne.getScore(), is("3"));
Frame frameTwo = frames.next();
assertThat(frameTwo.getScore(), is("10"));
}
public Game() {
frames = new ArrayList<Frame>();
Frame frame = null;
for(int i = 0; i < FRAMES_PER_GAME; i++) {
frame = new Frame(frame);
frames.add(frame);
}
}
There are still two failing acceptance tests:
For the last remaining failures, rather than bore you with every key stroke, I'll just show you the completed test and code.
@Test
public void gameShouldShowCurrentScoreAfterEachBall() {
game.bowl(9);
assertThat(game.getScore(), is(9));
}
and
public int getScore() {
return getLastFrame().getCumulativeScore();
}
public Frame getLastFrame() {
return frames.get(frames.size() - 1);
}
It turns out that everyFrameShouldShowTheFrameIndex
was not actually necessary - because the UI code takes care of the frame index and
we are not testing the UI - so I just got rid of it.
I run the acceptance tests again and now, finally, all the failures are about spares and strikes.
I am done with rule 2.1.2. Now, how do spares and strikes work?
Posted by Kevin Lawrence at March 16, 2007 03:05 PM
TrackBack URL for this entry: