FizzBuzz Kata

1… 2… Fizz! 4… Buzz!

Rules of Testing

  • First Rule: You can write no production code except to pass a failing test case
  • Second Rule: You can only write enough of a test case to demonstrate a failure
  • Third Rule: You can only write enough production code to pass the currently failing test case

FizzBuzz Game

  • "Fizz" if a number is divisible by 3
  • "Buzz" if a number is divisible by 5
  • "FizzBuzz" if a number is divisible by 3 and 5
  • Otherwise, show the number (let's show the number as a String)

Project Setup

  • Open Git Bash
  • Run the following: mkdir fizzbuzz && cd fizzbuzz
  • Initialize a new node project: npm init -y
  • Install Parcel: npm i parcel-bundler
  • Install Jest and all dependencies: npm i -D jest babel-jest @babel/core @babel/preset-env
  • Make a src dir and a test dir: mkdir src test
  • Make a js dir in both: mkdir {src,test}/js
  • Open in VSCode: code .

Update package.json

update the scripts section of your package.json file

{
  ...
  "scripts": {
    "test": "jest test/js"
  },
  ...
}

Red

import FizzBuzz from "../../src/js/FizzBuzz"

describe("fizzbuzz", () => {
  test("Should return 1", () => {
    // Arrange
    const underTest = new FizzBuzz();
    // Act
    const actual = underTest.parse(1);
    // Assert
    expect(actual).toBe(1);
  });
});

First Failures

So all of our failures at this point are compilation failures. To fix these we need to make the FizzBuzz.js file with the parse method.


All fixed!

So now we have the following object in src/js/FizzBuzz.js:

class FizzBuzz {
  parse(inputNumber) {}
}

export default FizzBuzz

Now run your tests to see this one fail. In GitBash run npm test.


Red -> Green

Now, we do the SIMPLEST thing to make the test pass. In this case...

class FizzBuzz {
  parse(inputNumber) {
    return 1;
  }
}

export default FizzBuzz

Remember, never write more code than you need to make the test pass. Even if you know how the code may change in the future. Be creative with your tests and let them dictate how your code should work.

Now, run your test to see it passing!


Green -> Refactor

Now that we have gotten to green in the red -> green -> refactor cycle, it's time to refactor! Remember, refactoring can take the form of properly naming things, deleting unnecessary code, removing duplication, or anything else that generally makes our code cleaner.

At this point we don't have any clean up to do though, so it's time to move back to red.


Refactor -> Red

Now that we've got a passing test for 1, let's write a test for 2.

...
test("Should return 2", () => {
  // Arrange
  const underTest = new FizzBuzz();

  // Act
  const actual = underTest.parse(2);

  // Assert
  expect(actual).toBe(2);
});

Run this test to see it fail.


Red -> Green

So let's change our class under test to make this test pass...

class FizzBuzz {
  parse(inputNumber) {
    if (inputNumber === 1) {
      return 1;
    }
    return 2;
  }
}

export default FizzBuzz

Remember, since return is a break statement, we will never run the risk of returning two outcomes from the same method. Run your tests to see them pass.


Green -> Refactor

Time to make our code cleaner again. Now let's take a look at the logic of our method and think about how we can make it cleaner. At this point it's pretty clear that we are just returning the number we pass in. Let's make our method do that instead...

class FizzBuzz {
  parse(inputNumber) {
    return inputNumber;
  }
}

export default FizzBuzz

Refactor -> Red

Time for our third test!

...
test("Should return Fizz", () => {
  // Arrange
  const underTest = new FizzBuzz();

  // Act
  const actual = underTest.parse(3);

  // Assert
  expect(actual).toBe("Fizz");
});

Run this to see the fail.


Red -> Green

Remember to do the simplest thing to make the test pass.

class FizzBuzz {
  parse(inputNumber) {
    if (inputNumber === 3) {
      return "Fizz";
    }
    return inputNumber;
  }
}

export default FizzBuzz

Green -> Red

Since there really isn't anything to refactor, it's time to turn red again! So, for our next test, we are going to choose 5.

...
test("Should return Buzz", () => {
  // Arrange
  const underTest = new FizzBuzz();

  // Act
  const actual = underTest.parse(5);

  // Assert
  expect(actual).toBe("Buzz");
});

Why 5 and not 4?


Red -> Green

class FizzBuzz {
  parse(inputNumber) {
    if (inputNumber === 5) {
      return "Buzz";
    }
    if (inputNumber === 3) {
      return "Fizz";
    }
    return inputNumber;
  }
}

export default FizzBuzz

We now have a passing test for 5!


Green -> Red

Time to continue on our testing journey...

test("Should return Fizz", () => {
  // Arrange
  const underTest = new FizzBuzz();

  // Act
  const actual = underTest.parse(6);

  // Assert
  expect(actual).toBe("Fizz");
});

This gets us failing. Let continue on the simplest road to passing.


A Moment for Names

Wait...

Now we have two tests with the same description ('Should return Fizz'). We can use a nested describe block to help with that. This is what we should have now:

describe("fizzbuzz", () => {
  ...
  describe("Should return Fizz", () => {
    test("for 3", () => {
      ...
    });
    test("for 6", () => {
      ...
    });
  });
});

Now we can nest anything that should return us "Fizz" in here so we're organized.


Red -> Green

class FizzBuzz {
  parse(inputNumber) {
    if (inputNumber === 5) {
      return "Buzz";
    }
    if (inputNumber === 3 || inputNumber === 6) {
      return "Fizz";
    }
    return inputNumber;
  }
}

export default FizzBuzz

Green -> Refactor

Now let's examine our parse method and see how we can make this logic a little more clear

class FizzBuzz {
  parse(inputNumber) {
    if (inputNumber === 5) {
      return "Buzz";
    }
    if (inputNumber % 3 === 0) {
      return "Fizz";
    }
    return inputNumber;
  }
}

export default FizzBuzz

So now we're using the modulus operator to see if the number we have is evenly divisible by 3. We do this by checking to see if zero is the remainder when we divide by 3.


Complete remaining test cases

Time to continue using this same logic to figure out the remaining numbers to test! Which numbers make sense to test? Which do we have enough confidence to skip at this point?