Test Driven Development: Red, Green, Refactor

Test Driven Development (TDD) is a software design technique where you use tests to drive your design. A generally used technique is the Red, Green, Refactor technique. You start with writing a failing test: Red. Then you write just enough production code to make the test pass: Green. And when the tests are green you have the opportunity to refactor both your tests and your production code.

The advantage of first writing a test, before writing your production code, is that you always saw your test fail before you make it pass. This gives you some confidence that the test works. When you write your test after your production code and you see it only pass, there might be a bug in your test that makes it always pass. As you get more confidence in your tests, you can start refactoring in the refactoring stage much more confidently.

FizzBuzz kata

The Japanese word Kata means Form. Kata's are often used in Martial Arts such as Judo and Karate. Kata is a choreographed pattern or movement designed to practice certain movements useful in a fight. Martial Artists repeat movements from a Kata to make the movements muscle memory so that they can apply them without thinking during a real fight.

In programming and TDD we can do the same thing. A programming kata is a programming problem specifically designed to practice certain programming techniques. Some kata's help you with the general red, green, refactor cycle. Others help you practice refactoring or designing of complex systems.

In this article we will use the FizzBuzz kata to learn TDD and the red, green, refactor cycle. FizzBuzz is an American children's game that teaches division. The goal of the game is to count to 100 in a group of people. Every next person in the group calls out the next number. The first person calls one, the second two, etc. But there is a twist to the game. Every time a number is dividable by three the person calls Fizz instead of the number. When a number is dividable by five the person calls Buzz. When a number is dividable by three and five you say FizzBuzz.

The goal of the FizzBuzz kata is to program the game of FizzBuzz using TDD.

Our first test

We don't have any code yet, so our first step is to write the first test to bring ourselves to the red stage. The goal is to make a test that tests a simple case, allowing us to write minimum code to bring us to green. In the case of FizzBuzz, the easiest test we can write is a test to see if our method returns 1 when we put in 1.

class FizzBuzzTest extends TestCase
{
    /** @test */
    public function it_returns_one_when_given_one() 
    {
        $game = new FizzBuzz();
        $this->assertEquals(1, $game->call(1));
    }
}

Let's run our test

Red: Write production code

Error : Class "Webdevils\Katas\FizzBuzz\FizzBuzz" not found

Our test fails! We are in the red stage. The minimum code to resolve the error is defining the FizzBuzz class:

class FizzBuzz
{

}

We still get an error, but the error is different:

Error : Call to undefined method Webdevils\Katas\FizzBuzz\FizzBuzz::call()

Let's add the call method. I return 0 to make sure our test still fails:

public function call($number) : string
{
    return 0;
}

We run the test again and we finally get the expected error:

Failed asserting that '0' matches expected 1.
Expected :1
Actual   :0

What is the simplest code to pass the test? Just return the number 1.

public function call($number) : string
{
    return 1;
}

And we are green!

Green! Refactoring?

Now we are green, but our code is stupid. We know it won't do, but that is also the point of TDD. We create the simplest code to make the test pass, which forces us to come up with the next test. With every test we come closer to our goal and the final solution. When we arrive at our solution we will have the simplest code that solves our problem and we will have a full test suite to proof it. That is the beauty of TDD.

Before we write our next test. Is there anything we can refactor right now? I don't think so. The production code is nice and easy and also our test code is nice and easily readable.

On to the next test.

Second test

We are green and we don't have anything to refactor. We can start with our second test. What would be a simple failing test we could write?

Well, we always return the number 1. Independent of the number that we need to call. The simplest failing test would be a test for the number two:

/** @test */
public function it_returns_two_when_given_two()
{
    $game = new FizzBuzz();
    $this->assertEquals(2, $game->call(2));
}

And as expected, the test fails:

Failed asserting that '1' matches expected 2.
Expected :2
Actual   :1

What is the simplest code to bring us back to green?

Well, when we pass in the number 1, we need to return 1. When we pass in the number 2, we return 2. The simplest code to make the test pass, is returning the input number:

public function call($number) : string
{
    return $number;
}

The code already starts to make more sense! Of course we still miss the Fizz and Buzz, but that will come with our next tests. But before we go to our next test. Anything to refactor?

Nothing in our production code. It is still very simple: just a return statement.

Our test code has some repeating code though. In both tests we construct the same FizzBuzz object. Luckily, we can get rid of the duplicate code by using the setUp method of PHPUnit. PHPUnit runs the setUp method before every test, allowing us to construct the object for every test.

class FizzBuzzTest extends TestCase
{
    private $game;

    protected function setUp() : void
    {
        $this->game = new FizzBuzz();
    }

    /** @test */
    public function it_returns_one_when_given_one()
    {
        $this->assertEquals(1, $this->game->call(1));
    }

    /** @test */
    public function it_returns_two_when_given_two()
    {
        $this->assertEquals(2, $this->game->call(2));
    }
}

That is much cleaner. On to our next test.

Three translates to Fizz

Now we come to our first special case: the number three. The number three is dividable by three and we should call out Fizz . Currently our code would just return 3. This means we have our next simplest test to go back to red:

    /** @test */
    public function it_returns_fizz_when_given_three()
    {
        $this->assertEquals('Fizz', $this->game->call(3));
    }
}

And this test fails as expected:

Failed asserting that two strings are equal.
Expected :'Fizz'
Actual   :'3'

We know that we want to test for dividable by three. However, the simplest way to write our code is by returning Fizz only when the number is three. So that's what we will do to go back to green:

public function call($number) : string
{
    if($number == 3) {
        return 'Fizz';
    }
    
    return $number;
}

We are green again and we can refactor. Our production code is still quite simple. Nothing to refactor yet. Our test code starts to feel a bit repetitive again. Every test we test the output of the call function with a different input. Nothing else changes in the test.

We can use a dataProvider in PHPunit to make our tests simpler. The idea of a dataProvider is that we would have a generic test method which performs the repeating test. We can define another method, a dataProvider, which supplies this generic test with the necessary input and output. This sounds complicated, but it really isn't. The following code shows how we could apply this in our test:

/**
 * @test
 * @dataProvider numbersAndOutput
 */
public function it_plays_FizzBuzz($number, $gameOutput)
{
    $this->assertEquals($gameOutput, $this->game->call($number));
}

public function numbersAndOutput()
{
    return [
        [1, 1],
        [2, 2],
        [3, 'Fizz'],
    ];
}

We can remove the three tests that we had and we are still testing the same thing. And we are still green!

Five translates to Buzz

The next number in our series is four, but our code already handles four properly. Which means it won't lead to a failing test and there no reason to write it. The next failing number would be five. Five should return Buzz, but currently returns 5. This will be our next test. We can add the test by adding another row to our dataProvider: numbersAndOutput:

public function numbersAndOutput()
{
    return [
        [1, 1],
        [2, 2],
        [3, 'Fizz'],
        [5, 'Buzz'],
    ];
}

This directly shows the elegance of our last refactor. It makes writing new tests much easier. And we are back to red.

Failed asserting that two strings are equal.
Expected :'Buzz'
Actual   :'5'

We can go back to green by adding another condition to our code:

public function call($number) : string
{
    if($number == 3) {
        return 'Fizz';
    } elseif($number == 5) {
        return 'Buzz';
    }

    return $number;
}

Again, the simplest code is to test specifically for the number 5. We know that we want dividable by 5, but our test doesn't force us to write that code yet.

Time to refactor again. Our production code still seems quite straight forward and also our test code is quite elegant. No need to refactor. Let's continue with the next test.

Six translates to Fizz

On to our next failing test. The number six. It should translate to Fizz, but it currently translates to 6.

public function numbersAndOutput()
{
    return [
        [1, 1],
        [2, 2],
        [3, 'Fizz'],
        [5, 'Buzz'],
        [6, 'Fizz'],
    ];
}

And the error we get:

Failed asserting that two strings are equal.
Expected :'Fizz'
Actual   :'6'

The simplest way to go back to green is by changing the condition for Fizz to a check if the number is dividable by three. Which is awesome, because that is exactly the business logic that we want. This shows the power of TDD. By writing a failing test at a time and writing the simplest code to make the tests pass, we come closer and closer to working code.

public function call($number) : string
{
    if($number % 3 == 0) {
        return 'Fizz';
    } elseif($number == 5) {
        return 'Buzz';
    }

    return $number;
}

Anything to refactor? Nope! On to the next failing test.

Ten is dividable by five

The numbers 7, 8 and 9 will all work with our current code, showing that we are making progress! Next failing number would be 10. It returns 10, but it is dividable by five and should return Buzz.

public function numbersAndOutput()
{
    return [
        [1, 1],
        [2, 2],
        [3, 'Fizz'],
        [5, 'Buzz'],
        [6, 'Fizz'],
        [10, 'Buzz'],
    ];
}

We get the following error:

Failed asserting that two strings are equal.
Expected :'Buzz'
Actual   :'10'

We can use the same trick as for the number six. The simplest production code change is by changing the check for the number 5 into a check if the number is dividable by five:

public function call($number) : string
{
    if($number % 3 == 0) {
        return 'Fizz';
    } elseif($number % 5 == 0) {
        return 'Buzz';
    }

    return $number;
}

We are back to green, but there is nothing to refactor. On to the next test.

FizzBuzz

We are making a lot of progress. All numbers until 15 are working well. 15 is a problem though. It should return FizzBuzz, but it is returning Fizz. We can fix that by adding a failing test:

public function numbersAndOutput()
{
    return [
        [1, 1],
        [2, 2],
        [3, 'Fizz'],
        [5, 'Buzz'],
        [6, 'Fizz'],
        [10, 'Buzz'],
        [15, 'FizzBuzz'],
    ];
}

And the error that 15 is giving us Fizz instead of FizzBuzz:

Failed asserting that two strings are equal.
Expected :'FizzBuzz'
Actual   :'Fizz'

Here we will do the same dance as we did with Fizz and Buzz. The simplest code is to add a condition to check for the number 15:

public function call($number) : string
{
    if($number == 15) {
        return 'FizzBuzz';
    } elseif($number % 3 == 0) {
        return 'Fizz';
    } elseif($number % 5 == 0) {
        return 'Buzz';
    }

    return $number;
}

Nothing to refactor, so on to the next number that fails:

public function numbersAndOutput()
{
    return [
        [1, 1],
        [2, 2],
        [3, 'Fizz'],
        [5, 'Buzz'],
        [6, 'Fizz'],
        [10, 'Buzz'],
        [15, 'FizzBuzz'],
        [30, 'FizzBuzz'],
    ];
}

The error showing that it is a failing test:

Failed asserting that two strings are equal.
Expected :'FizzBuzz'
Actual   :'Fizz'

And our final production code to solve FizzBuzz:

public function call($number) : string
{
    if($number % 15 == 0) {
        return 'FizzBuzz';
    } elseif($number % 3 == 0) {
        return 'Fizz';
    } elseif($number % 5 == 0) {
        return 'Buzz';
    }

    return $number;
}

Interestingly enough our code checks for dividable by 15 instead of 3 and 5, because that was the simplest code to bring our code back to green. Of course, it doesn't matter for the functionality, it is the same, but still I think it is an interesting outcome.

Conclusion

We followed the red, green, refactor cycle to implement the children's game of FizzBuzz. When we started, our code seemed quite stupid, but by adding one failing test at a time, we were forced to change our code to come closer to the outcome that we expected. Until we had the full game implemented.

I would recommend following the FizzBuzz kata again in a weeks time, but now on your own without checking final the code or the article. The goal isn't to perform exactly the same steps and come to the same code as me, but to practice the TDD cycle. Force yourself to first write a test, make it pass and then refactor. By practising in a playing ground you improve your skills, allowing you to use the same skills on more advanced topics in a working environment.

Source code

The full source code of the article is available at Github. The linked repository contains the source code of all katas that I walked through on this blog. As of the time of writing it is only the FizzBuzz kata, but stay tuned for more.

Author

Mark Kazemier's avatar
Mark Kazemier

Hi, my name is Mark. I'm the founder of webdevils.nl and love developing websites and other web applications. Through Webdevils.nl I want to spread my enthousiasm about the web and PHP. In my professional live I'm a security expert specialised in security monitoring.

View all posts