On TDD

May 24, 2014

Yesterday I tried to do the bownling kata by @unclebobmartin. After reading the intro, it must be clear to everyone that the scoring rules of bowling were not designed by a programmer.

Where I failed

As you can see from the presentation, Uncle Bob starts out with some design, which he does before doing anything. So apparently, he draws up this (big) design before he starts coding. I didn’t quite do that. I started TTD’ing sort of right away.

Because that’s how I solve problems, I start playing with ideas to see if they work.

Where I feel TDD fail in this scenario

In about slide 11 Uncle Bob starts to code. Note that from the preceiding slides, he has already identified that he needs

But still, instead of implementing these concepts, he starts out with the baby steps that TDD requires, starting out with verifying that we have a Game class.

Then he spends the next twenty something slides implementing baby steps while still not introducing the concept of Rolls.

And so he goes on.

And another gripe

During the course of this kata he introduces several private methods to his Game class, which, being private, are not tested explicitly, only implicitly through roll and score. I am aware of these just being implementation details, but when I did this implementation myself, there were quite a few of my private helper-methods I would have liked to unit-test to ensure that they were not the source of my failing implementation. They often were.

The proof of the pudding

This problem is obviously functional. Given a set of inputs (rolls) you get a known score. Same sequence of rolls, same score. Every time.

But yet I fail to see one test actually verifying a known game with a known score.

In this you’ll find examples of games with the correct scores.

So I guess, if I were to implement this kata, I’d start up with something like:

@Test
public void testRollSimpleGame() {
   //  9-0   3-5   6-1   3-6   8-1   5-3   2-5   8-0   7-1    8-1
   int [] rolls = {9, 0, 3, 5, 6, 1, 3, 6, 8, 1, 5, 3, 2, 5, 8, 0, 7,
   1, 8, 1};
   for (int i = 0; i< 0; i++) {
       g.roll(rolls[i]);
   }
   assertEquals(82, g.score());
}

And to stop me from mucking around creating an implementation which returns 82 for any input, I’d implement this one pretty quickly as well.

@Test
public void testRollSimpleGame() {
    // X    3/    6-1    X     X     X    2/    9-0    7/    XXX
   int [] rolls = {10, 3, 7, 6, 1, 10, 10, 10, 2, 8, 9, 0, 7, 3, 10, 10, 10};
   for (int i = 0; i< 0; i++) {
       g.roll(rolls[i]);
   }
   assertEquals(193, g.score());
}

This last test actually highlights a problem, when you roll a strike, you do not roll your second roll for the frame, which means that your game gets less than 20 rolls.

As a bonus, you see that you easily can refactor the tests into something like:

public void assertCorrectScore(int [] rolls, int score) {
   for (int i = 0; i< 0; i++) {
       g.roll(rolls[i]);
   }
   assertEquals(score, g.score());
}

As for Uncle Bobs implementation, I fail to see any such tests appart from the gutter-game and perfect-game, both of which I admit are important tests, though the former is more important with my bowling skills.

My main point

Is still that to me it seems just useless to implement a lot of small, useless tests, since they disturb my problem-solving process, and they seem to be of little value when the job is done.

I’d personally much rather have a couple of real-world tests which ensures that the function is correct, and use those as a guard when I refactor my code. In the end, that’s why I need tests, to ensure that I have implemented the requirements, not to help me on my way to solving the problem.

A sortof untested version in Clojure

Oh, and then the shameless plug for Clojure. I did end up implementing a scoringthingy in Clojure, which can be found here

The nice thing about doing this in Clojure, is that I could play with my data-model without having to resort to figuring out how to name classes and such.

Of course, I should have implemented the tests above, which I might just end up doing, but not today.

The test-driven java implementation? It was abandoned.