FIZZBUZZ WITH PHP TDD AND RECURSION

July 20th, 2019 by Kevin Pimentel

By now every Software Developer and their cat knows how to implement FizzBuzz. For me, FizzBuzz is a good and easy way to filter out web developers. When I’m involved in the interview process I like to give it as a written programming test. It’s simple enough to do with pen and paper and effective enough to filter out the bad.

 

Being that FizzBuzz is a lot of fun and I need to work on my PHP TDD, I couldn’t think of a better first post. As of late, I’ve been incorporating TDD more and more into my workflow. I’m at the point where I’m starting to question why I ever did anything outside of the TDD workflow at all.

Instructions

The instructions for our program are as follows:  

 

  • Write a recursive program that can accept n numbers.
  • Program should be recursive and return an array of numbers and strings.
  • Numbers divisible by 3 should be swapped out for the string Fizz.
  • Numbers divisible by 5 should be swapped out for the string Buzz.
  • Numbers divisible by both 3 and 5 should be swapped for the string FizzBuzz.


GitHub Repository

Project Setup

For this project we won’t need anything fancier than PHPUnit. To start off, we create the following directory structure:

/fizzbuzz
	/tests
	/src

Next, we want to run composer init to get PHPUnit pulled in. 

Initialize composer

$ composer init

This command will guide you through creating your composer.json config.

Welcome to the Composer config generator

Package name (<vendor>/<name>) [themi/fizzbuzz]:
Description []: FizzBuzz coding Kata using TDD and Recursion
Author [Kevin Pimentel <de56ep@gmail.com>, n to skip]: Kevin Pimentel <kevin@kev 
inpimentel.com>
Minimum Stability []:
Package Type (e.g. library, project, metapackage, composer-plugin) []: project
License []: MIT

Define your dependencies.

Would you like to define your dependencies (require) interactively [yes]? no
Would you like to define your dev dependencies (require-dev) interactively [yes] 
? no

{
    "name": "themi/fizzbuzz",
    "description": "FizzBuzz coding Kata using TDD and Recursion",
    "type": "project",
    "license": "MIT",
    "authors": [
        {
            "name": "Kevin Pimentel",
            "email": "kevin@kevinpimentel.com"
        }
    ],
    "require": {}
}

Do you confirm generation [yes]? [enter]

Install phpunit

composer require --dev phpunit/phpunit ^7

Don’t forget to autoload!

composer.json

"autoload": {
        "psr-4": {
            "App\\": "src/"
        }
}

Index.php

<?php

require __DIR__ . '/vendor/autoload.php';

Creating Our Test Class

To write our first test we need to create tests/FizzBuzzTest.php

<?php

use PHPUnit\Framework\TestCase;

final class FizzBuzzTest extends TestCase
{
        
}

Testing for Fizz

The first rule is: If the number is divisible by 3 we output fizz. So let's set up our first test.

<?php

use PHPUnit\Framework\TestCase;
use App\FizzBuzz;

final class FizzBuzzTest extends TestCase
{
        public function test_a_number_divisible_by_three_is_replaced_by_the_word_fizz()
        {
                $fizz = new FizzBuzz();

                $this->assertEquals('Fizz', $fizz->isFizz(3 * rand()));
        }

}

When we run it, it fails! Of course. 

We haven’t created src/FIzzBuzz yet. In our previously created src/ folder lets create our FizzBuzz.php class.

Once created, we can run the test again and see that the error shows progress. Instead of undefined class we now get undefined method. The next step is to define the method:

<?php

namespace App;

class FizzBuzz
{
        public function isFizz(int $number)
        {

        }
}

I like to run my tests often in order to show progress. When the test runs at this point, our error changes from undefined method to failed test. The reason for the failure is that our method is empty, and returning null. The next thing is to get our test to pass. 


Eventuall, we get to this point where we need to add the right logic to our method that makes it it pass.

return $number % 3 === 0
    ? 'Fizz'
    : false;

Notice how the test tells you exactly what you need to do, step by step. Write the class, write the method, and finally, add logic to make the method pass.

After adding the logic above, we run our test:

$ pu
PHPUnit 7.5.14 by Sebastian Bergmann and contributors.

.                                 1 / 1 (100%)

Time: 74 ms, Memory: 4.00 MB

OK (1 test, 1 assertion)

Success!

Testing For Buzz

Our second rule is: If the number is divisible by 5 then we need to output Buzz. 


Time to do the same exact thing for Buzz. 

public function test_a_number_divisible_by_five_is_replaced_by_the_word_buzz()
{
        $buzz = new FizzBuzz();

        $this->assertEquals('Buzz', $buzz->isBuzz(5 * rand()));
}

Immediately after writing this test I run it and get the undefined method error. 

$ pu
PHPUnit 7.5.14 by Sebastian Bergmann and contributors.

.E                                 2 / 2 (100%)

Time: 76 ms, Memory: 4.00 MB

There was 1 error:

1) FizzBuzzTest::test_a_number_divisible_by_five_is_replaced_by_the_word_buzz
Error: Call to undefined method App\FizzBuzz::isBuzz()

C:\xampp\htdocs\fizzbuzz\tests\FizzBuzzTest.php:19

ERRORS!
Tests: 2, Assertions: 1, Errors: 1.

The workflow naturally takes over the process of building our PHP program. Every time a test fails we receive directions on what the next step is. isBuzz method is undefined? I guess we better create it.

public function isBuzz($num)
{

}

Immediately after defining the method, I’ll run my test and get it to fail.

$ pu
PHPUnit 7.5.14 by Sebastian Bergmann and contributors.

.F                                 2 / 2 (100%)

Time: 75 ms, Memory: 4.00 MB

There was 1 failure:

1) FizzBuzzTest::test_a_number_divisible_by_five_is_replaced_by_the_word_buzz
Failed asserting that null matches expected 'Buzz'.

C:\xampp\htdocs\fizzbuzz\tests\FizzBuzzTest.php:19

FAILURES!
Tests: 2, Assertions: 2, Failures: 1.

Our test is returning null because it’s empty. We receive our next set of instructions and define the logic to make it pass.

public function isBuzz($num)
{
        return $num % 5 === 0
              ? 'Buzz'
              : false;
}

Now we run the test and make it pass.

Testing For FizzBuzz

The final rule is: If the number is divisible by both 3 and 5 we output FizzBuzz.


First, we write our test.


public function test_a_number_divisible_by_five_and_three_is_replaced_by_the_word_fizzbuzz()
{
        $fizzbuzz = new FizzBuzz();
        $num = 15 * rand();

                $this->assertEquals('FizzBuzz', $fizzbuzz->isFizz($num) . $fizzbuzz->isBuzz($num));

}

Next, we run it and it passes. It’s science magic.


$ pu
PHPUnit 7.5.14 by Sebastian Bergmann and contributors.

...                                3 / 3 (100%)

Time: 84 ms, Memory: 4.00 MB

OK (3 tests, 3 assertions)

Now that we’ve gotten to this point, it’s time to get really fancy with the recursion. 

Testing Our Input

We want to test against a controlled data-set to make sure we have the right output. 

public function test_array_of_fizz_buzz_fizzbuzz_is_matching()
{
        $fizzbuzz = new FizzBuzz();

        $this->assertEquals([
                1 => 1,
                2 => 2,
                3 => 'Fizz',
                4 => 4,
                5 => 'Buzz',
                6 => 'Fizz',
                7 => 7,
                8 => 8,
                9 => 'Fizz',
                10 => 'Buzz',
                11 => 11,
                12 => 'Fizz',
                13 => 13,
                14 => 14,
                15 => 'FizzBuzz'
        ], $fizzbuzz->calc(15));
}

I don’t have a calc method yet, and this test will fail, but I know that I want to call a calc method. As I am writing my tests, I am designing the FizzBuzz class and dictating how I wish to interact with it.


I’ll create a method calc that accepts a number and returns an array of fizz, buzz, fizzbuzz, or number. I came up with the following recursive class method:

public function calc(int $count, array $result = [], int $iteration = 1)
{
        if ($iteration > $count) return $result;

        $result[$iteration] = $this->isFizzOrBuzz($iteration)
                            ? $this->isFizz($iteration) . $this->isBuzz($iteration)
                            : $iteration;

        $iteration++

        return $this->calc($count, $result, $iteration);
}

The most important piece of recursion is to have an exit condition. Always, when defining a recursive method, define the exit condition first. We know that when our iteration is greater than the count, it’s time to return.

 

We know the method needs to call itself, because.. recursion. And that’s where the return call to $this->calc comes in. 

 

Finally, I do the heavy lifting and refactor to a isFizzOrBuzz method:

public function isFizzOrBuzz($number)
{
        return $this->isFizz($number) || $this->isBuzz($number);

}

This method, isFizzOrBuzz, will return true if either of the two are true. Then we know we need to output one or both Fizz and Buzz.


Here’s our final result:


tests/FizzBuzzTest.php

<?php

use PHPUnit\Framework\TestCase;
use App\FizzBuzz;

final class FizzBuzzTest extends TestCase
{
        public function test_a_number_divisible_by_three_is_replaced_by_the_word_fizz()
        {
                $fizz = new FizzBuzz();

                $this->assertEquals('Fizz', $fizz->isFizz(3 * rand()));
        }

        public function test_a_number_divisible_by_five_is_replaced_by_the_word_buzz()
        {
                $buzz = new FizzBuzz();

                $this->assertEquals('Buzz', $buzz->isBuzz(5 * rand()));
        }

        public function test_a_number_not_divisible_by_five_or_three_remains_a_number()	{		
                $fizzbuzz = new FizzBuzz();		

                $num = 7;		
                $this->assertFalse($fizzbuzz->isFizz($num));		
                $this->assertFalse($fizzbuzz->isBUzz($num));	
        }


        public function test_a_number_divisible_by_five_and_three_is_replaced_by_the_word_fizzbuzz()
        {
                $fizzbuzz = new FizzBuzz();

                $num = 15 * rand();

                $this->assertEquals('FizzBuzz', $fizzbuzz->isFizz($num) . $fizzbuzz->isBuzz($num));
        }

        public function test_array_of_fizz_buzz_fizzbuzz_is_matching()
        {
                $fizzbuzz = new FizzBuzz();

                $this->assertEquals([
                        1 => 1,
                        2 => 2,
                        3 => 'Fizz',
                        4 => 4,
                        5 => 'Buzz',
                        6 => 'Fizz',
                        7 => 7,
                        8 => 8,
                        9 => 'Fizz',
                        10 => 'Buzz',
                        11 => 11,
                        12 => 'Fizz',
                        13 => 13,
                        14 => 14,
                        15 => 'FizzBuzz'
                ], $fizzbuzz->calc(15));
        }
}


src/FizzBuzz.php

<?php

namespace App;

class FizzBuzz
{
        public function calc(int $count, array $result = [], int $iteration = 1)
        {
                if ($iteration > $count) return $result;

                $result[$iteration] = $this->isFizzOrBuzz($iteration)
                                    ? $this->isFizz($iteration) . $this->isBuzz($iteration)
                                    : $iteration;

                $iteration++;

                return $this->calc($count, $result, $iteration);
        }

        public function isFizz(int $number)
        {
                return $number % 3 === 0
                        ? 'Fizz'
                        : false;
        }

        public function isBuzz($number)
        {
                return $number % 5 === 0
                        ? 'Buzz'
                        : false;
        }

        public function isFizzOrBuzz($number)
        {
                return $this->isFizz($number) || $this->isBuzz($number);

        }
}


$ pu
PHPUnit 7.5.14 by Sebastian Bergmann and contributors.

.....                                5 / 5 (100%)

Time: 689 ms, Memory: 4.00 MB

OK (5 tests, 6 assertions)

FizzBuzz is always fun to implement, especially if you look for different and creative ways of doing it. You could of also added a test that returns a number if it isn’t divisible by 3 or 5.


It could be something like this:

public function test_a_number_not_divisible_by_five_or_three_remains_a_number()
{
	$fizzbuzz = new FizzBuzz();
 
	$num = 7;
 
	$this->assertFalse($fizzbuzz->isFizz($num));
	$this->assertFalse($fizzbuzz->isBUzz($num));
}

The best part about TDD and this workflow is that I can say it works with some level of confidence. If a few months later I have no idea what this program does, I can read my tests and get a pretty good description of what it can accomplish.


FIN.

GitHub Repository


Kevin Pimentel

There are two types of people in the world: those that code, and those that don’t. I said that! Quote me. My name is Kevin and I’m one of the ones that codes.