March 16, 2007 - Are We There Yet?

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.

tests-after-2.1.2.png

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 Pings

TrackBack URL for this entry:


Comments

Post a comment




Remember Me?