Solving the Fizz Buzz kata

Statement of the kata

Our objective will be to write a program that prints the numbers from 1 to 100 in such a way that:

  • if the number is divisible by 3 it returns Fizz.
  • if the number is divisible by 5 it returns Buzz.
  • if the number is divisible by 3 and 5 it returns FizzBuzz.

Language and focus

We’re going to solve this kata in Python with unittest as our testing environment. The task consists in creating a FizzBuzz class which will have a generate method to create the list, so it will be used more or less like this:

1 fizzbuzz = Fizzbuzz()
2 print(fizzbuzz.generate())

To do so, I create a folder called fizzbuzzkata and to it I add the fizzbuzz_test.py file.

Define the class

What the exercise asks for is a list with the numbers from 1 to 100 changing some of them by the words “Fizz”, “Buzz”, or both of them in case of fulfilling certain condictions.

Note that it doesn’t ask for a list of any amount of numbers, but rather specifically from 1 to 100. We’ll come back to this in a moment.

Now we’re going to focus in that first test. The less we can do is make it possible to instantiate a FizzBuzz type object. Here’s a possible first test:

 1 import unittest
 2 
 3 
 4 class FizzBuzzTestCase(unittest.TestCase):
 5     def test_something(self):
 6         FizzBuzz()
 7 
 8 
 9 if __name__ == '__main__':
10     unittest.main()

It may look weird. This test is just limited to trying to instantiate the class and nothing else.

This first test should be enough to fail, which is what the second law states, and force us to define the class so the test can pass, fulfilling the third law. In some environments it would be necessary to add an assertion, given that they consider that the test hasn’t passed if it hasn’t been explicitly verified, but it’s not the case in Python.

So, we launch it to see if it really fails. The result, as it was expected, is that the test doesn’t pass, displaying the following error:

1 NameError: name 'FizzBuzz' is not defined

To pass the test we’ll have to define the FizzBuzz class, something we’ll do in the test file itself.

 1 import unittest
 2 
 3 class FizzBuzz(object):
 4     pass
 5 
 6 class FizzBuzzTestCase(unittest.TestCase):
 7     def test_something(self):
 8         FizzBuzz()
 9 
10 
11 if __name__ == '__main__':
12     unittest.main()

And with this, the test will pass. Now that we’re green we can think about refactoring. The class doesn’t have any code, but we could change the name of the test for a more adequate one:

 1 import unittest
 2 
 3 class FizzBuzz:
 4     pass
 5 
 6 class FizzBuzzTestCase(unittest.TestCase):
 7     def test_can_instantiate(self):
 8         FizzBuzz()
 9 
10 
11 if __name__ == '__main__':
12     unittest.main()

Usually it’s better that the classes live in their own file (or Python module) because it makes it easier to manage the code and keep everything located. So, we create a fizzbuzz.py file and we move the class to it.

And in the test, we import it:

 1 import unittest
 2 
 3 from fizzbuzzkata.fizzbuzz import FizzBuzz
 4 
 5 
 6 class FizzBuzzTestCase(unittest.TestCase):
 7     def test_can_instantiate(self):
 8         FizzBuzz()
 9 
10 
11 if __name__ == '__main__':
12     unittest.main()

When we introduce this change and run the test, we can verify that it passes and that we’re in green.

We’ve fulfilled the three laws and closed our first test-code-refactor cycle. There’s not much else to do here, except for moving on to the next test.

Define the generate method

The FizzBuzz class not only doesn’t do anything, it doesn’t even have any methods! We’ve said that we want it to have a generate method, which is the one that will return the list of numbers from 1 to 100.

To force us to write the generate method, we have to write a test that calls it. The method will have to return something, right? No, not really. It’s not always necessary to return something. It’s enough if nothing breaks when we call it.

 1 import unittest
 2 
 3 from fizzbuzzkata.fizzbuzz import FizzBuzz
 4 
 5 
 6 class FizzBuzzTestCase(unittest.TestCase):
 7     def test_can_instantiate(self):
 8         FizzBuzz()
 9 
10     def test_responds_to_generate_message(self):
11         fizzbuzz = FizzBuzz()
12         fizzbuzz.generate()
13 
14 
15 if __name__ == '__main__':
16     unittest.main()

When we run the test, it tells us that the object doesn’t have any generate method:

1 AttributeError: 'FizzBuzz' object has no attribute 'generate'

Of course it doesn’t, we have to add it:

1 class FizzBuzz(object):
2     def generate(self):
3         pass

Now we already have a class capable of answering to the generate message. Can we do any refactoring here?

Well, yes, but not in the production code, but in the tests. It turns out that the test that we’ve just written overlaps the previous one. That is, the test_responds_to_generate_message test covers the test_can_instantiate test, making it redundant. Therefore, we can remove it:

 1 import unittest
 2 
 3 from fizzbuzzkata.fizzbuzz import FizzBuzz
 4 
 5 
 6 class FizzBuzzTestCase(unittest.TestCase):
 7     def test_responds_to_generate_message(self):
 8         fizzbuzz = FizzBuzz()
 9         fizzbuzz.generate()
10 
11 
12 if __name__ == '__main__':
13     unittest.main()

Perhaps this surprises you. This is what we talk about in the beginning of the book, some of the tests that we use to drive the development stop being useful for some reason or another. Generally, they end up becoming redundant and don’t provide any information that we’re not already getting from other tests.

Define a behavior for generate

Specifically, we want it to return a list of numbers. But it doesn’t need to have the multiples of 3 and 5 converted just yet.

The test should verify this, but it must keep passing when we have developed the complete algorithm. What we could verify would be that it returns a 100 element list, without paying any attention to what it contains exactly.

This test will force us to give it a behavior in response to the generate message:

 1 import unittest
 2 
 3 from fizzbuzzkata.fizzbuzz import FizzBuzz
 4 
 5 
 6 class FizzBuzzTestCase(unittest.TestCase):
 7 
 8     def test_respond_to_generate_message(self):
 9         fizzbuzz = FizzBuzz()
10         fizzbuzz.generate()
11 
12     def test_generates_list_of_100_elements(self):
13         fizzbuzz = FizzBuzz()
14         num_list = fizzbuzz.generate()
15         self.assertEqual(100, len(num_list))
16 
17 
18 if __name__ == '__main__':
19     unittest.main()

Of course, the test fails:

1 TypeError: object of type 'NoneType' has no len()

Right now, the method returns None. We want a list:

1 class FizzBuzz(object):
2     def generate(self):
3         return []

When we change generate so that it returns a list, the test fails because our condition isn’t met: that the list has a certain number of elements.

1 AssertionError: 100 != 0

This one is finally an error from the test. The previous one were basically equivalent to compiling errors (syntax errors, etc.). That’s why it’s so important to see the tests fail, to use the feedback that the error messages provide us.

Making the test pass is quite easy:

1 class FizzBuzz(object):
2     def generate(self):
3         return [None] * 100

With the test in green, let’s think a little.

In the first place, it could be argued that in this test we’ve asked generate to return a response that meets two conditions:

  • be of type list (or array, or collection)
  • have exactly 100 elements

We could have forced this same thing with two even smaller tests.

This tiny little steps are often called baby steps, and the truth is that they don’t have a fixed length, they depend on our practice and experience instead.

Thus, for example, the test that we’ve created is small enough to not generate a big leap in the production code, although it’s capable of verifying both conditions at once.

In the second place, note that we’ve just written the necessary code to fulfill the test. In fact, we return a list of 100 None elements, which may seem a little pointless, but it’s enough to achieve this test’s objective. Remember: don’t write more code than necessary to pass the test.

In the third place, we have written enough code, between test and production, to be able to examine it and see if there’s any opportunity for refactoring.

The clearest refactoring opportunity that we have right now is the magic number 100, which we could store in a class variable. Again, each language will have its own options:

1 class FizzBuzz(object):
2     _NUMBER_OF_ELEMENTS = 100
3 
4     def generate(self):
5         return [None] * self._NUMBER_OF_ELEMENTS

And we have some more in the test code. Once again, the new test that we’ve added overlaps and includes the old one, which we could remove.

 1 import unittest
 2 
 3 from fizzbuzzkata.fizzbuzz import FizzBuzz
 4 
 5 
 6 class FizzBuzzTestCase(unittest.TestCase):
 7 
 8     def test_generates_list_of_100_elements(self):
 9         fizzbuzz = FizzBuzz()
10         num_list = fizzbuzz.generate()
11         self.assertEqual(100, len(num_list))
12 
13 
14 if __name__ == '__main__':
15     unittest.main()

In the same way, the name of the test could improve. Instead of referencing the specific number, we could simply indicate something more general, that doesn’t tie the test to a specific implementation detail.

 1 import unittest
 2 
 3 from fizzbuzzkata.fizzbuzz import FizzBuzz
 4 
 5 
 6 class FizzBuzzTestCase(unittest.TestCase):
 7 
 8     def test_generates_list_of_required_number_of_elements(self):
 9         fizzbuzz = FizzBuzz()
10         num_list = fizzbuzz.generate()
11         self.assertEqual(100, len(num_list))
12 
13 
14 if __name__ == '__main__':
15     unittest.main()

Last but not least, we still have a magic number 100, which we will name:

 1 import unittest
 2 
 3 from fizzbuzzkata.fizzbuzz import FizzBuzz
 4 
 5 
 6 class FizzBuzzTestCase(unittest.TestCase):
 7     _NUMBER_OF_ELEMENTS = 100
 8 
 9     def test_generates_list_of_required_number_of_elements(self):
10         fizzbuzz = FizzBuzz()
11         num_list = fizzbuzz.generate()
12         self.assertEqual(self._NUMBER_OF_ELEMENTS, len(num_list))
13 
14 
15 if __name__ == '__main__':
16     unittest.main()

And with this, we’ll have finished a new cycle in which we have already introduced the refactoring phase.

Generate a list of numbers

Our FizzBuzz can already generate a list with 100 elements, but at the moment each of them is literally nothing. It’s time to write a test that forces us to put some elements inside that list.

To do this, we could expect the generated list to contain the numbers from 1 to 100. However, we have a problem: at the end of the development process, the list wil contain the numbers but some of them will be represented by the words Fizz, Buzz, or FizzBuzz. If I don’t take this into account, this third test will start failing as soon as I start implementing the algorithm that converts the numbers. It doesn’t seem like a good solution.

A more promising approach would be: what numbers won’t be affected by the algorithm? Well, those that aren’t multiples of 3 or 5. Thereby, we could choose some of them to verify that they’re included in the untransformed list.

The simplest of them all is 1, which should occupy the first position of the list. For symmetry reasons we’re going to generate the numbers as strings.

 1 import unittest
 2 
 3 from fizzbuzzkata.fizzbuzz import FizzBuzz
 4 
 5 
 6 class FizzBuzzTestCase(unittest.TestCase):
 7     _NUMBER_OF_ELEMENTS = 100
 8 
 9     def test_generates_list_of_required_number_of_elements(self):
10         fizzbuzz = FizzBuzz()
11         num_list = fizzbuzz.generate()
12         self.assertEqual(self._NUMBER_OF_ELEMENTS, len(num_list))
13 
14     def test_number_one_is_represented_as_itself(self):
15         fizzbuzz = FizzBuzz()
16         num_list = fizzbuzz.generate()
17         self.assertEqual('1', num_list[0])
18 
19 
20 if __name__ == '__main__':
21     unittest.main()

The test is very small and fails:

1 None != 1

At this point, what change could we introduce in the production code to make the test pass? The most obvious one could be the following:

1 class FizzBuzz(object):
2     _NUMBER_OF_ELEMENTS = 100
3 
4     def generate(self):
5         return ['1'] * self._NUMBER_OF_ELEMENTS

It’s enough to pass the test, so it suits us.

One problem that we have here is that the number ‘1’ doesn’t appear as such in the test. What it does appear is its representation, but we use its position in num_list, which is a 0-index array. We’re going to make explicit the fact that we’re testing against the representation of a number. First, we introduce the concept of position:

 1 import unittest
 2 
 3 from fizzbuzzkata.fizzbuzz import FizzBuzz
 4 
 5 
 6 class FizzBuzzTestCase(unittest.TestCase):
 7     _NUMBER_OF_ELEMENTS = 100
 8 
 9     def test_generates_list_of_required_number_of_elements(self):
10         fizzbuzz = FizzBuzz()
11         num_list = fizzbuzz.generate()
12         self.assertEqual(self._NUMBER_OF_ELEMENTS, len(num_list))
13 
14     def test_number_one_is_represented_as_itself(self):
15         fizzbuzz = FizzBuzz()
16         num_list = fizzbuzz.generate()
17         position = 0
18         self.assertEqual('1', num_list[position])
19 
20 
21 if __name__ == '__main__':
22     unittest.main()

And now the concept of number, as well as its relationship with position:

 1 import unittest
 2 
 3 from fizzbuzzkata.fizzbuzz import FizzBuzz
 4 
 5 
 6 class FizzBuzzTestCase(unittest.TestCase):
 7     _NUMBER_OF_ELEMENTS = 100
 8 
 9     def test_generates_list_of_required_number_of_elements(self):
10         fizzbuzz = FizzBuzz()
11         num_list = fizzbuzz.generate()
12         self.assertEqual(self._NUMBER_OF_ELEMENTS, len(num_list))
13 
14     def test_number_one_is_represented_as_itself(self):
15         fizzbuzz = FizzBuzz()
16         num_list = fizzbuzz.generate()
17         number = 1
18         position = number - 1
19         self.assertEqual('1', num_list[position])
20 
21 
22 if __name__ == '__main__':
23     unittest.main()

Now we don’t need to refer to the position at all, just to the number.

 1 import unittest
 2 
 3 from fizzbuzzkata.fizzbuzz import FizzBuzz
 4 
 5 
 6 class FizzBuzzTestCase(unittest.TestCase):
 7     _NUMBER_OF_ELEMENTS = 100
 8 
 9     def test_generates_list_of_required_number_of_elements(self):
10         fizzbuzz = FizzBuzz()
11         num_list = fizzbuzz.generate()
12         self.assertEqual(self._NUMBER_OF_ELEMENTS, len(num_list))
13 
14     def test_number_one_is_represented_as_itself(self):
15         fizzbuzz = FizzBuzz()
16         num_list = fizzbuzz.generate()
17         number = 1
18         self.assertEqual('1', num_list[(number - 1)])
19 
20 
21 if __name__ == '__main__':
22     unittest.main()

We could make the test easier to read. First, we separate the verification:

 1 import unittest
 2 
 3 from fizzbuzzkata.fizzbuzz import FizzBuzz
 4 
 5 
 6 class FizzBuzzTestCase(unittest.TestCase):
 7     _NUMBER_OF_ELEMENTS = 100
 8 
 9     def test_generates_list_of_required_number_of_elements(self):
10         fizzbuzz = FizzBuzz()
11         num_list = fizzbuzz.generate()
12         self.assertEqual(self._NUMBER_OF_ELEMENTS, len(num_list))
13 
14     def test_number_one_is_represented_as_itself(self):
15         number = 1
16 
17         self.__assert_number_is_represented_as(number)
18 
19     def __assert_number_is_represented_as(self, number):
20         fizzbuzz = FizzBuzz()
21         num_list = fizzbuzz.generate()
22         self.assertEqual('1', num_list[(number - 1)])
23 
24 
25 if __name__ == '__main__':
26     unittest.main()

We extract the representation as a parameter in the assertion, and we make an inline of number, to make the reading more fluent:

 1 import unittest
 2 
 3 from fizzbuzzkata.fizzbuzz import FizzBuzz
 4 
 5 
 6 class FizzBuzzTestCase(unittest.TestCase):
 7     _NUMBER_OF_ELEMENTS = 100
 8 
 9     def test_generates_list_of_required_number_of_elements(self):
10         fizzbuzz = FizzBuzz()
11         num_list = fizzbuzz.generate()
12         self.assertEqual(self._NUMBER_OF_ELEMENTS, len(num_list))
13 
14     def test_number_one_is_represented_as_itself(self):
15         self.__assert_number_is_represented_as(1, '1')
16 
17     def __assert_number_is_represented_as(self, number, representation):
18         fizzbuzz = FizzBuzz()
19         num_list = fizzbuzz.generate()
20         self.assertEqual(representation, num_list[(number - 1)])
21 
22 
23 if __name__ == '__main__':
24     unittest.main()

As you can see, we’ve work a lot in the test. Now introducing new examples will be very inexpensive, which will help us write more tests and make the process more pleasant and convenient.

We keep generating numbers

Actually, we haven’t yet verified whether the generate method is returning a list of numbers, so we need to keep writing new tests that force us to create that code.

Let’s make sure that the second position is occupied by the number two, which is the next simplest number that’s not a multiple of 3 or 5.

 1 import unittest
 2 
 3 from fizzbuzzkata.fizzbuzz import FizzBuzz
 4 
 5 
 6 class FizzBuzzTestCase(unittest.TestCase):
 7     _NUMBER_OF_ELEMENTS = 100
 8 
 9     def test_generates_list_of_required_number_of_elements(self):
10         fizzbuzz = FizzBuzz()
11         num_list = fizzbuzz.generate()
12         self.assertEqual(self._NUMBER_OF_ELEMENTS, len(num_list))
13 
14     def test_number_one_is_represented_as_itself(self):
15         self.__assert_number_is_represented_as(1, '1')
16         self.__assert_number_is_represented_as(2, '2')
17 
18     def __assert_number_is_represented_as(self, number, representation):
19         fizzbuzz = FizzBuzz()
20         num_list = fizzbuzz.generate()
21         self.assertEqual(representation, num_list[(number - 1)])
22 
23 
24 if __name__ == '__main__':
25     unittest.main()

We have a new test which fails, so we’re going to add some code to production so that the tets passes.
However, we have some problems with this implementation:

1 class FizzBuzz(object):
2     _NUMBER_OF_ELEMENTS = 100
3 
4     def generate(self):
5         return ['1'] * self._NUMBER_OF_ELEMENTS

To intervene in it, we’d need to refactor it a little first. At least, extract the response to a variable that we could manipulate before returning it.

But, since the test is failing right now, we can’t refactor. Before that we have to cancel or delete the test that we’ve just created. The easiest would be to comment it out to prevent its execution. Remember, to do any refactorings it’s compulsory that the tests are passing:

 1 import unittest
 2 
 3 from fizzbuzzkata.fizzbuzz import FizzBuzz
 4 
 5 
 6 class FizzBuzzTestCase(unittest.TestCase):
 7     _NUMBER_OF_ELEMENTS = 100
 8 
 9     def test_generates_list_of_required_number_of_elements(self):
10         fizzbuzz = FizzBuzz()
11         num_list = fizzbuzz.generate()
12         self.assertEqual(self._NUMBER_OF_ELEMENTS, len(num_list))
13 
14     def test_number_one_is_represented_as_itself(self):
15         self.__assert_number_is_represented_as(1, '1')
16         # self.__assert_number_is_represented_as(2, '2')
17 
18     def __assert_number_is_represented_as(self, number, representation):
19         fizzbuzz = FizzBuzz()
20         num_list = fizzbuzz.generate()
21         self.assertEqual(representation, num_list[(number - 1)])
22 
23 
24 if __name__ == '__main__':
25     unittest.main()

Now we can work:

1 class FizzBuzz(object):
2     _NUMBER_OF_ELEMENTS = 100
3 
4     def generate(self):
5         num_list = ['1'] * self._NUMBER_OF_ELEMENTS
6         return num_list

And we activate the test again, which now fails because the number 2 is represented by a ‘1’. The simplest change that I can come up with, right now, is this one. So silly:

1 class FizzBuzz(object):
2     _NUMBER_OF_ELEMENTS = 100
3 
4     def generate(self):
5         num_list = ['1'] * self._NUMBER_OF_ELEMENTS
6         num_list[1] = '2'
7         
8         return num_list

The truth is that the test is green. We know that this is not the implementation that will solve the full problem, but our production code is only obligated to satisfy the existing tests and nothing more. So, let’s not get ahead of ourselves. Let’s see what we can do.

To start, the name of the test is obsolete, let’s generalize it:

 1 import unittest
 2 
 3 from fizzbuzzkata.fizzbuzz import FizzBuzz
 4 
 5 
 6 class FizzBuzzTestCase(unittest.TestCase):
 7     _NUMBER_OF_ELEMENTS = 100
 8 
 9     def test_generates_list_of_required_number_of_elements(self):
10         fizzbuzz = FizzBuzz()
11         num_list = fizzbuzz.generate()
12         self.assertEqual(self._NUMBER_OF_ELEMENTS, len(num_list))
13 
14     def test_number_is_represented_as_itself(self):
15         self.__assert_number_is_represented_as(1, '1')
16         self.__assert_number_is_represented_as(2, '2')
17 
18     def __assert_number_is_represented_as(self, number, representation):
19         fizzbuzz = FizzBuzz()
20         num_list = fizzbuzz.generate()
21         self.assertEqual(representation, num_list[(number - 1)])
22 
23 
24 if __name__ == '__main__':
25     unittest.main()

Now that this has been solved, let’s remember that previously we saw that the concepts of “number” and “representation” were necessary to better define the expected behavior in the tests. We can now introduce them in our production code:

 1 class FizzBuzz(object):
 2     _NUMBER_OF_ELEMENTS = 100
 3 
 4     def generate(self):
 5         num_list = ['1'] * self._NUMBER_OF_ELEMENTS
 6         
 7         number = 2
 8         representation = '2'
 9         
10         num_list[number-1] = representation
11 
12         return num_list

It’s a first step. We can see the limitations of the current solution. For example, why does the 1 have a special treatment? And what will happen if we want to verify other number? There are several problems.

As for the number 1, the key lies in the list of numbers idea. Right now we’re generating a list of constants, but each of the elements of the list should be a correlative number, beginning with 1 until completing the desired number of elements.

And then we’d have to replace each number by its representation. Something like this:

 1 class FizzBuzz(object):
 2     _NUMBER_OF_ELEMENTS = 100
 3 
 4     def generate(self):
 5         num_list = list(range(1, self._NUMBER_OF_ELEMENTS + 1))
 6 
 7         number = 1
 8         representation = '1'
 9 
10         num_list[number-1] = representation
11 
12         number = 2
13         representation = '2'
14 
15         num_list[number-1] = representation
16 
17         return num_list

This structure keeps passing the test, but it doesn’t seem very practical. However, we can see a pattern. We need to iterate over the list to give solution:

 1 class FizzBuzz(object):
 2     _NUMBER_OF_ELEMENTS = 100
 3 
 4     def generate(self):
 5         num_list = list(range(1, self._NUMBER_OF_ELEMENTS + 1))
 6         for number in num_list:
 7             if number == 1:
 8                 representation = '1'
 9 
10             if number == 2:
11                 representation = '2'
12 
13             num_list[number-1] = representation
14 
15         return num_list

With the information that we have, we could simply assume that it’s enough to convert the number into a string and put it in its place:

 1 class FizzBuzz(object):
 2     _NUMBER_OF_ELEMENTS = 100
 3 
 4     def generate(self):
 5         num_list = list(range(1, self._NUMBER_OF_ELEMENTS + 1))
 6         for number in num_list:
 7             representation = str(number)
 8             num_list[number-1] = representation
 9 
10         return num_list

Of course, there are more compact and pythonic ways, such as this one:

1 class FizzBuzz:
2 
3     _NUMBERS_IN_LIST = 100
4     
5     def generate(self):
6         return list(map(lambda num: str(num + 1), range(self._NUMBERS_IN_LIST)))

But we should be careful, we’re probably getting too ahead of ourselves with this refactoring, and it’ll surely become a source of problems further down the line. For this reason, it’s preferable to keep a more direct and naive implementation, and leave the optimizations and more advanced structures for later, when the behavior of the method is completely defined. So, I would advise you to avoid this kind of approach.

All of this refactoring is done while the tests are green. This means that:

  1. With the test, we describe the behavior that we want to develop
  2. We make the test pass by writing the simplest possible code, as stupidly simple it looks, with the intent of implementing that behavior
  3. We use the green tests as a safety net to restructure the code until we find a better design: easy to understand, maintain, and extend.

Points 2 and 3 are build based on these principles:

  • KISS: Keep it simply stupid, which means keeping the system as mindless as possible, that is, not trying to add intelligence prematurely. The more mechanical and simple, the better, as long as it meets its needs. This KISS is our first approach.
  • Gall’s law: every working complex system has evolved from a simpler system that also worked. Therefore, we start with a very simple implementation that works (KISS), and we make it evolve towards a more complex one that works as well, something that we’re sure about because the test keeps passing.
  • YAGNI: You aren’t gonna need it, which prevents us from implementing more code than strictly necessary to pass the current tests.

But now we have to implement new behaviors.

The test that doesn’t fail

The next number which is not a multiple of 3, 5 o 15 is 4, so we add an example for this:

 1 import unittest
 2 
 3 from fizzbuzzkata.fizzbuzz import FizzBuzz
 4 
 5 
 6 class FizzBuzzTestCase(unittest.TestCase):
 7     _NUMBER_OF_ELEMENTS = 100
 8 
 9     def test_generates_list_of_required_number_of_elements(self):
10         fizzbuzz = FizzBuzz()
11         num_list = fizzbuzz.generate()
12         self.assertEqual(self._NUMBER_OF_ELEMENTS, len(num_list))
13 
14     def test_number_is_represented_as_itself(self):
15         self.__assert_number_is_represented_as(1, '1')
16         self.__assert_number_is_represented_as(2, '2')
17         self.__assert_number_is_represented_as(4, '4')
18 
19     def __assert_number_is_represented_as(self, number, representation):
20         fizzbuzz = FizzBuzz()
21         num_list = fizzbuzz.generate()
22         self.assertEqual(representation, num_list[(number - 1)])
23 
24 
25 if __name__ == '__main__':
26     unittest.main()

And the test passes. Good news? It depends. A test that passes just after its creation is always a reason for suspicion, at least from a TDD point of view. Remember: writing a failing test is always the first thing to do. If the test doesn’t fail, it means that:

  • The behavior is already implemented
  • It’s not the test we were looking for

In our case, the last refactoring has resulted in the general behavior of the numbers that don’t need transformation. In fact, we can categorize the numbers in these classes:

  • Numbers that are represented as themselves
  • Multiples of three, represented as ‘Fizz’
  • Multiples of five, represented as ‘Buzz’
  • Multiples of both three and five, represented as ‘FizzBuzz’

Numbers 1 and 2 belong to the first class, so they’re more than enough, since any of the numbers in that class would serve as an example. In TDD we need them both, because they’ve helped us to introduce the idea that we would have to iterate through the number list. However, just one of them would be sufficient for a QA test. For this reason, when we introduce the example of the number 4, we don’t have to add any additional code: the behavior is already implemented.

It’s time to move on to the other classes of numbers.

Learning to say “Fizz”

It’s time for our FizzBuzz to be able to convert the 3 into “Fizz”. A minimal test to specify this would be the following:

 1 import unittest
 2 
 3 from fizzbuzzkata.fizzbuzz import FizzBuzz
 4 
 5 
 6 class FizzBuzzTestCase(unittest.TestCase):
 7     _NUMBER_OF_ELEMENTS = 100
 8 
 9     def test_generates_list_of_required_number_of_elements(self):
10         fizzbuzz = FizzBuzz()
11         num_list = fizzbuzz.generate()
12         self.assertEqual(self._NUMBER_OF_ELEMENTS, len(num_list))
13 
14     def test_number_is_represented_as_itself(self):
15         self.__assert_number_is_represented_as(1, '1')
16         self.__assert_number_is_represented_as(2, '2')
17         self.__assert_number_is_represented_as(4, '4')
18 
19     def test_three_and_its_multiples_are_represented_as_fizz(self):
20         self.__assert_number_is_represented_as(3, 'Fizz')
21 
22 
23     def __assert_number_is_represented_as(self, number, representation):
24         fizzbuzz = FizzBuzz()
25         num_list = fizzbuzz.generate()
26         self.assertEqual(representation, num_list[(number - 1)])
27 
28 
29 if __name__ == '__main__':
30     unittest.main()

Having a failing test, let’s see what minimal production code we could add to pass it:

 1 class FizzBuzz(object):
 2     _NUMBER_OF_ELEMENTS = 100
 3 
 4     def generate(self):
 5         num_list = list(range(1, self._NUMBER_OF_ELEMENTS + 1))
 6         for number in num_list:
 7             representation = str(number)
 8 
 9             if number == 3:
10                 representation = 'Fizz'
11 
12             num_list[number - 1] = representation
13 
14         return num_list

We’ve added an if that makes this particular case pass. For the time being, with the information that we have, there isn’t any other better way. Remember KISS, Gall and YAGNI to avoid advancing faster than you should.

Regarding the code, there may be a better way to populate the list. Instead of generating a list of numbers and changing it later, perhaps we could initialize an empty list and append the representations of the numbers one by one.

 1 class FizzBuzz(object):
 2     _NUMBER_OF_ELEMENTS = 100
 3 
 4     def generate(self):
 5         representations = list()
 6         num_list = list(range(1, self._NUMBER_OF_ELEMENTS + 1))
 7         for number in num_list:
 8             representation = str(number)
 9 
10             if number == 3:
11                 representation = 'Fizz'
12 
13             representations.append(representation)
14 
15         return representations

This works. Now num_list becomes kind of pointless as a list. We can make a change:

 1 class FizzBuzz(object):
 2     _NUMBER_OF_ELEMENTS = 100
 3 
 4     def generate(self):
 5         representations = list()
 6         num_list = range(1, self._NUMBER_OF_ELEMENTS + 1)
 7         for number in num_list:
 8             representation = str(number)
 9 
10             if number == 3:
11                 representation = 'Fizz'
12 
13             representations.append(representation)
14 
15         return representations

And remove the temporary variable:

 1 class FizzBuzz(object):
 2     _NUMBER_OF_ELEMENTS = 100
 3 
 4     def generate(self):
 5         representations = list()
 6         for number in range(1, self._NUMBER_OF_ELEMENTS + 1):
 7             representation = str(number)
 8 
 9             if number == 3:
10                 representation = 'Fizz'
11 
12             representations.append(representation)
13 
14         return representations

Everything continues to work correctly, as the tests attest.

Saying “Fizz” at the right time

Now we want it to add a “Fizz” when the corresponding number is a multiple of 3, and not just when it’s exactly 3. Of course, we have to add a test to specify this. This time we use the number 6, which is the closest multiple of 3 (and not of 5) that we have.

 1 import unittest
 2 
 3 from fizzbuzzkata.fizzbuzz import FizzBuzz
 4 
 5 
 6 class FizzBuzzTestCase(unittest.TestCase):
 7     _NUMBER_OF_ELEMENTS = 100
 8 
 9     def test_generates_list_of_required_number_of_elements(self):
10         fizzbuzz = FizzBuzz()
11         num_list = fizzbuzz.generate()
12         self.assertEqual(self._NUMBER_OF_ELEMENTS, len(num_list))
13 
14     def test_number_is_represented_as_itself(self):
15         self.__assert_number_is_represented_as(1, '1')
16         self.__assert_number_is_represented_as(2, '2')
17         self.__assert_number_is_represented_as(4, '4')
18 
19     def test_three_and_its_multiples_are_represented_as_fizz(self):
20         self.__assert_number_is_represented_as(3, 'Fizz')
21         self.__assert_number_is_represented_as(6, 'Fizz')
22 
23 
24     def __assert_number_is_represented_as(self, number, representation):
25         fizzbuzz = FizzBuzz()
26         num_list = fizzbuzz.generate()
27         self.assertEqual(representation, num_list[(number - 1)])
28 
29 
30 if __name__ == '__main__':
31     unittest.main()

To pass the test we just have to make a pretty small change. We have to modify the condition to expand it to all of the multiples of three. But we’re going to do it incrementally.

First, we establish the behavior:

 1 class FizzBuzz(object):
 2     _NUMBER_OF_ELEMENTS = 100
 3 
 4     def generate(self):
 5         representations = list()
 6         for number in range(1, self._NUMBER_OF_ELEMENTS + 1):
 7             representation = str(number)
 8 
 9             if number == 3:
10                 representation = 'Fizz'
11 
12             if number == 6:
13                 representation = 'Fizz'
14 
15             representations.append(representation)
16 
17         return representations

With this, the test passes. Now let’s change the code so that it uses the concept of multiple of:

 1 class FizzBuzz(object):
 2     _NUMBER_OF_ELEMENTS = 100
 3 
 4     def generate(self):
 5         representations = list()
 6         for number in range(1, self._NUMBER_OF_ELEMENTS + 1):
 7             representation = str(number)
 8 
 9             if number == 3:
10                 representation = 'Fizz'
11 
12             if number == 6:
13                 representation = 'Fizz'
14 
15             if number % 3 == 0:
16                 representation = 'Fizz'
17 
18             representations.append(representation)
19 
20         return representations

The test keeps passing, which indicates that our hypothesis is correct. Now we can remove the redundant part of the code:

 1 class FizzBuzz(object):
 2     _NUMBER_OF_ELEMENTS = 100
 3 
 4     def generate(self):
 5         representations = list()
 6         for number in range(1, self._NUMBER_OF_ELEMENTS + 1):
 7             representation = str(number)
 8 
 9             if number % 3 == 0:
10                 representation = 'Fizz'
11 
12             representations.append(representation)
13 
14         return representations

At this point you may want to try other examples from the same class, although it’s not really necessary since any multiple of three is an adequate representative. For this reason, we’ll move on to the next behavior.

Learning to say “Buzz”

This test lets us specify the new behavior:

 1 import unittest
 2 
 3 from fizzbuzzkata.fizzbuzz import FizzBuzz
 4 
 5 
 6 class FizzBuzzTestCase(unittest.TestCase):
 7     _NUMBER_OF_ELEMENTS = 100
 8 
 9     def test_generates_list_of_required_number_of_elements(self):
10         fizzbuzz = FizzBuzz()
11         num_list = fizzbuzz.generate()
12         self.assertEqual(self._NUMBER_OF_ELEMENTS, len(num_list))
13 
14     def test_number_is_represented_as_itself(self):
15         self.__assert_number_is_represented_as(1, '1')
16         self.__assert_number_is_represented_as(2, '2')
17         self.__assert_number_is_represented_as(4, '4')
18 
19     def test_three_and_its_multiples_are_represented_as_fizz(self):
20         self.__assert_number_is_represented_as(3, 'Fizz')
21         self.__assert_number_is_represented_as(6, 'Fizz')
22 
23     def test_five_and_its_multiples_are_represented_as_buzz(self):
24         self.__assert_number_is_represented_as(5, 'Buzz')
25 
26     def __assert_number_is_represented_as(self, number, representation):
27         fizzbuzz = FizzBuzz()
28         num_list = fizzbuzz.generate()
29         self.assertEqual(representation, num_list[(number - 1)])
30 
31 
32 if __name__ == '__main__':
33     unittest.main()

So, we modify the production code to make the test pass. Same as we did before, we treat the particular case in a particular manner.

 1 class FizzBuzz(object):
 2     _NUMBER_OF_ELEMENTS = 100
 3 
 4     def generate(self):
 5         representations = list()
 6         for number in range(1, self._NUMBER_OF_ELEMENTS + 1):
 7             representation = str(number)
 8 
 9             if number % 3 == 0:
10                 representation = 'Fizz'
11 
12             if number == 5:
13                 representation = 'Buzz'
14 
15             representations.append(representation)
16 
17         return representations

Yes, we already know how we should handle the general case of the multiples of five, but it’s preferable to force ourselves to go slowly. Remember that the main objective of the exercise isn’t to solve the list generation, but rather do it guided by tests. Our main interest now is to internalize this slow step cycle.

There’s not much else that we can do now, except for continuing to the next test.

Saying “Buzz” at the right time

At this point, the test is quite obvious, the next multiple of 5 is 10:

 1 import unittest
 2 
 3 from fizzbuzzkata.fizzbuzz import FizzBuzz
 4 
 5 
 6 class FizzBuzzTestCase(unittest.TestCase):
 7     _NUMBER_OF_ELEMENTS = 100
 8 
 9     def test_generates_list_of_required_number_of_elements(self):
10         fizzbuzz = FizzBuzz()
11         num_list = fizzbuzz.generate()
12         self.assertEqual(self._NUMBER_OF_ELEMENTS, len(num_list))
13 
14     def test_number_is_represented_as_itself(self):
15         self.__assert_number_is_represented_as(1, '1')
16         self.__assert_number_is_represented_as(2, '2')
17         self.__assert_number_is_represented_as(4, '4')
18 
19     def test_three_and_its_multiples_are_represented_as_fizz(self):
20         self.__assert_number_is_represented_as(3, 'Fizz')
21         self.__assert_number_is_represented_as(6, 'Fizz')
22 
23     def test_five_and_its_multiples_are_represented_as_buzz(self):
24         self.__assert_number_is_represented_as(5, 'Buzz')
25         self.__assert_number_is_represented_as(10, 'Buzz')
26 
27     def __assert_number_is_represented_as(self, number, representation):
28         fizzbuzz = FizzBuzz()
29         num_list = fizzbuzz.generate()
30         self.assertEqual(representation, num_list[(number - 1)])
31 
32 
33 if __name__ == '__main__':
34     unittest.main()

And, again, the change in the production code is simple at first:

 1 class FizzBuzz(object):
 2     _NUMBER_OF_ELEMENTS = 100
 3 
 4     def generate(self):
 5         representations = list()
 6         for number in range(1, self._NUMBER_OF_ELEMENTS + 1):
 7             representation = str(number)
 8 
 9             if number % 3 == 0:
10                 representation = 'Fizz'
11 
12             if number == 5:
13                 representation = 'Buzz'
14 
15             if number == 10:
16                 representation = 'Buzz'
17 
18             representations.append(representation)
19 
20         return representations

Next, we perform the refactoring step by step, now that we’ve ensured the behavior:

 1 class FizzBuzz(object):
 2     _NUMBER_OF_ELEMENTS = 100
 3 
 4     def generate(self):
 5         representations = list()
 6         for number in range(1, self._NUMBER_OF_ELEMENTS + 1):
 7             representation = str(number)
 8 
 9             if number % 3 == 0:
10                 representation = 'Fizz'
11 
12             if number == 5:
13                 representation = 'Buzz'
14 
15             if number == 10:
16                 representation = 'Buzz'
17                 
18             if number % 5 == 0:
19                 representation = 'Buzz'
20 
21             representations.append(representation)
22 
23         return representations

And then:

 1 class FizzBuzz(object):
 2     _NUMBER_OF_ELEMENTS = 100
 3 
 4     def generate(self):
 5         representations = list()
 6         for number in range(1, self._NUMBER_OF_ELEMENTS + 1):
 7             representation = str(number)
 8 
 9             if number % 3 == 0:
10                 representation = 'Fizz'
11 
12             if number % 5 == 0:
13                 representation = 'Buzz'
14 
15             representations.append(representation)
16 
17         return representations

And with this refactoring, we can proceed to the next -and last- class of numbers.

Learning to say “FizzBuzz”

The structure is exactly the same. Let’s start with the simplest case: 15 should return FizzBuzz, since 15 is the first number that is a multiple of 3 and 5 at the same time.

 1 import unittest
 2 
 3 from fizzbuzzkata.fizzbuzz import FizzBuzz
 4 
 5 
 6 class FizzBuzzTestCase(unittest.TestCase):
 7     _NUMBER_OF_ELEMENTS = 100
 8 
 9     def test_generates_list_of_required_number_of_elements(self):
10         fizzbuzz = FizzBuzz()
11         num_list = fizzbuzz.generate()
12         self.assertEqual(self._NUMBER_OF_ELEMENTS, len(num_list))
13 
14     def test_number_is_represented_as_itself(self):
15         self.__assert_number_is_represented_as(1, '1')
16         self.__assert_number_is_represented_as(2, '2')
17         self.__assert_number_is_represented_as(4, '4')
18 
19     def test_three_and_its_multiples_are_represented_as_fizz(self):
20         self.__assert_number_is_represented_as(3, 'Fizz')
21         self.__assert_number_is_represented_as(6, 'Fizz')
22 
23     def test_five_and_its_multiples_are_represented_as_buzz(self):
24         self.__assert_number_is_represented_as(5, 'Buzz')
25         self.__assert_number_is_represented_as(10, 'Buzz')
26 
27     def test_fifteen_and_its_multiples_are_represented_as_fizzbuzz(self):
28         self.__assert_number_is_represented_as(15, 'FizzBuzz')
29 
30 
31     def __assert_number_is_represented_as(self, number, representation):
32         fizzbuzz = FizzBuzz()
33         num_list = fizzbuzz.generate()
34         self.assertEqual(representation, num_list[(number - 1)])
35 
36 
37 if __name__ == '__main__':
38     unittest.main()

The new test fails. Let’s make it pass:

 1 class FizzBuzz(object):
 2     _NUMBER_OF_ELEMENTS = 100
 3 
 4     def generate(self):
 5         representations = list()
 6         for number in range(1, self._NUMBER_OF_ELEMENTS + 1):
 7             representation = str(number)
 8 
 9             if number % 3 == 0:
10                 representation = 'Fizz'
11 
12             if number % 5 == 0:
13                 representation = 'Buzz'
14 
15             if number == 15:
16                 representation = 'FizzBuzz'
17 
18             representations.append(representation)
19 
20         return representations

Saying “FizzBuzz” at the right time

And, again, we introduce a test for another case of the “multiples of 3 and 5” class, which will be 30.

 1 import unittest
 2 
 3 from fizzbuzzkata.fizzbuzz import FizzBuzz
 4 
 5 
 6 class FizzBuzzTestCase(unittest.TestCase):
 7     _NUMBER_OF_ELEMENTS = 100
 8 
 9     def test_generates_list_of_required_number_of_elements(self):
10         fizzbuzz = FizzBuzz()
11         num_list = fizzbuzz.generate()
12         self.assertEqual(self._NUMBER_OF_ELEMENTS, len(num_list))
13 
14     def test_number_is_represented_as_itself(self):
15         self.__assert_number_is_represented_as(1, '1')
16         self.__assert_number_is_represented_as(2, '2')
17         self.__assert_number_is_represented_as(4, '4')
18 
19     def test_three_and_its_multiples_are_represented_as_fizz(self):
20         self.__assert_number_is_represented_as(3, 'Fizz')
21         self.__assert_number_is_represented_as(6, 'Fizz')
22 
23     def test_five_and_its_multiples_are_represented_as_buzz(self):
24         self.__assert_number_is_represented_as(5, 'Buzz')
25         self.__assert_number_is_represented_as(10, 'Buzz')
26 
27     def test_fifteen_and_its_multiples_are_represented_as_fizzbuzz(self):
28         self.__assert_number_is_represented_as(15, 'FizzBuzz')
29         self.__assert_number_is_represented_as(30, 'FizzBuzz')
30 
31 
32     def __assert_number_is_represented_as(self, number, representation):
33         fizzbuzz = FizzBuzz()
34         num_list = fizzbuzz.generate()
35         self.assertEqual(representation, num_list[(number - 1)])
36 
37 
38 if __name__ == '__main__':
39     unittest.main()

This time I’ll jump directly to the final implementation, but you get the idea:

 1 class FizzBuzz(object):
 2     _NUMBER_OF_ELEMENTS = 100
 3 
 4     def generate(self):
 5         representations = list()
 6         for number in range(1, self._NUMBER_OF_ELEMENTS + 1):
 7             representation = str(number)
 8 
 9             if number % 3 == 0:
10                 representation = 'Fizz'
11 
12             if number % 5 == 0:
13                 representation = 'Buzz'
14 
15             if number % 15 == 0:
16                 representation = 'FizzBuzz'
17 
18             representations.append(representation)
19 
20         return representations

And we have our “FizzBuzz”!

Wrapping up

We’ve completed the development of the specified behavior of the FizzBuzz class. In fact, any other test that we could add now would confirm that the algorithm is general enough to cover all cases. That is, there isn’t any conceivable test that could force us to add more production code: there’s nothing else we must do.

In a real work case, this code would de functional and deliverable. But we can certainly still improve it. The fact that all of the tests are passing indicates that the desired behavior is fully implemented, so we could fearlessly refactor and try to find a more flexible solution. For example, with the following solution it would be easier to add extra rules:

 1 class FizzBuzz(object):
 2     _NUMBER_OF_ELEMENTS = 100
 3 
 4     def generate(self):
 5         rules = {
 6             3: 'Fizz',
 7             5: 'Buzz',
 8             15: 'FizzBuzz',
 9         }
10 
11         representations = list()
12 
13         for number in range(1, self._NUMBER_OF_ELEMENTS + 1):
14             representation = str(number)
15             for divisor in rules.keys():
16                 if number % divisor == 0:
17                     representation = rules[divisor]
18 
19             representations.append(representation)
20 
21         return representations

And if you look closely, you can see that it would be relatively easy to modify the class so we could introduce the rules from the outside, as it would be enough to pass the rule dictionary at the moment of instantiating the class, fulfilling the Open for extension and Closed for modification principle. In this case, we’ve allowed for the original rules to be used unless others are not specifically indicated, so the tests continue to pass in exactly the same manner as before.

 1 class FizzBuzz(object):
 2     _NUMBER_OF_ELEMENTS = 100
 3 
 4     def __init__(self, rules=None):
 5         if rules is None:
 6             rules = {
 7                 3: 'Fizz',
 8                 5: 'Buzz',
 9                 15: 'FizzBuzz',
10             }
11         self.rules = rules
12 
13     def generate(self):
14 
15         representations = list()
16 
17         for number in range(1, self._NUMBER_OF_ELEMENTS + 1):
18             representation = str(number)
19             for divisor in self.rules.keys():
20                 if number % divisor == 0:
21                     representation = self.rules[divisor]
22 
23             representations.append(representation)
24 
25         return representations

What have we learned in this kata

  • The laws of TDD
  • The red->green->refactor cycle
  • To use minimal test to make the production code advance
  • To change the production code as minimally as possible to achieve the desired behavior
  • To use the refactor phase to improve the code design