29 March 2017

A Technique for Practicing TDD in Two Steps

So there are a ton of kata's out there in the world. There are practice exercises, and there are tutorials. I think a lot of them are really nifty and I've enjoyed working through many of them. However, I think rote memorization of katas is not really learning TDD so much as it is learning a technique, developing muscle memory, and practicing. For example, Uncle Bob's Bowling Kata isn't really about TDD so much as it is about Design Sense via method extraction.

I don't know when I came up with this technique for learning TDD, it just sorta happened. I have found it to be one of the most challenging things I've ever done, and once I mastered it I stopped struggling to do TDD. 

Here is an outline of this learning technique.

  1. Find a small problem to solve. I use Project Euler as source of inspiration.
  2. Attempt to solve the problem via TDD without thinking ahead.
Thats all there is to it. Of course there is a catch. You can't think ahead. That is a lot harder than it seems. Even for simple stuff, we often jump to the end and think about the final result. In so doing we exert a lot of influence on our implementation. The preconceived notions we take into our first test can limit our imagination for how to solve any given problem and they most assuredly cause our TDD to become clumsy and mechanical. 

So try this out. The first problem listed in the archives of Project Euler is called Multiples of 3 and 5. The objective is to sum all numbers that are multiples of 3 or 5 within a specified range. [Note: I read this question as implying a lower bound, you may choose to ignore that if you like] The example given is for numbers less than ten, the result is 23 (3 + 5 +6 + 9). So how would we write the code for that. 

This problem is so simple that most of us have figured it out in our heads already. Something like, we'll I'd write a loop from zero to 10 and check each number for divisibility by 3 and 5 using mod and then put those numbers in a list and add them up. 

OK, sure. Now test drive that.

But that wasn't the point of our game. Test drive that without thinking ahead. Don't think about loops, don't think about how you'd construct the list of numbers to be added at all. Think about how you'd test that your function did the right thing without thinking about the content of the function. 

So your first test might be something like this;

assert_equals( 23, f(0,10) )

And to make that true you'd write

def f(min, max):
  return 23

Now what do you do next? How about this;

assert_equals( 33, f(0,11) )

And to make that pass you might write;

def f(min, max):
  if max == 10:
    return 23
  if max == 11:
    return 33

But this isn't a general solution, so you'd add more tests and your code might evolve to look like this;

def f(min, max):
  numbers = []
  num = 0
  while num < max:
    if num % 3 == 0 or num % 5 == 0:
      numbers.append(num)
    num += 1
  result = 0
  for num in numbers:
    result += num
  return result

And eventually deal with the lower bound..

def f(min, max):
  numbers = []
  num = min
  while num < max:
    if num % 3 == 0 or num % 5 == 0:
      numbers.append(num)
    num += 1
  result = 0
  for num in numbers:
    result += num
  return result

...and hopefully after some refactoring you'd arrive at something like this;

def f(min, max):
   def f(min, max):

    return sum([number for number in range(min, max) if number % 3 == 0 or number % 5 == 0])

So the point of all this isn't the result, or the intermediate steps, its about developing the patience and self control to step through any problem in these tiny increments without anticipating the next step. A lot of practitioners might have started much closer to my first draft of the generalized solution than they would like to admit. If you practice this technique however, you will find yourself taking smaller steps forward, making fewer and more easily recovered mistakes, and developing simpler designs.