DEV Community

Cover image for How To Never Fail At The Fizzbuzz Interview Challenge

How To Never Fail At The Fizzbuzz Interview Challenge

Alex Fedorov on October 11, 2017

Ah, the classic FizzBuzz coding challenge. I’ve decided to include this picture to get your attention. Oh, and also because it associates with the...
Collapse
 
tbodt profile image
tbodt

This is scarily close to being overengineered, IMHO

Collapse
 
waterlink profile image
Alex Fedorov

I guess that is why I add at the end:

Did you need to do all these refactorings?

Not necessarily.

Do you need to know how to do them so that you are ready for any requirement change?

Absolutely!

I would probably not do the Strings extraction here. At least, not until there is a feature on the top of backlog that requires it.

With the method extraction, I would probably leave it at the first level methods extraction (isFizz and isBuzz, without others). Again, until there is a feature that will make these other private methods handy.

As with any practice code, you want to take things to the max and extreme. You do that to see how things could be. And think when you would and would not do certain things (like I did in previous 2 paragraphs here).

Essentially, doing that expands your arsenal, when practicing and learning.

Collapse
 
jmfayard profile image
Jean-Michel 🕵🏻‍♂️ Fayard

I can very easily write a fizzbuzz implementation that pass all your tests but is completly wrong. For example:

fun evilFizzBuzzValidAccordingToTests(number: Int): String = when {
    number > 100 -> "FOOBAR"
    number % 3 == 0 && number % 5 == 0 -> "FizzBuzz"
    number % 3 == 0 -> "Fizz"
    number % 5 == 0 -> "Buzz"
    else -> number.toString()
}

So I would throw in properties based testing, here with kotlin test

import io.kotlintest.specs.FreeSpec
import io.kotlintest.specs.StringSpec



fun evilFizzBuzz(number: Int): String = when {
    number > 100 -> "FOOBAR"
    number % 3 == 0 && number % 5 == 0 -> "FizzBuzz"
    number % 3 == 0 -> "Fizz"
    number % 5 == 0 -> "Buzz"
    else -> number.toString()
}

fun realFizzBuzz(number: Int): String = when {
    number % 3 == 0 && number % 5 == 0 -> "FizzBuzz"
    number % 3 == 0 -> "Fizz"
    number % 5 == 0 -> "Buzz"
    else -> number.toString()
}


class FizzBuzzTesting : FreeSpec() { init {

    "it's always possible to trumps example-based testing" {

        val expected = listOf("FizzBuzz",
                "1", "2", "Fizz", "4", "Buzz",
                "Fizz", "7", "8", "Fizz", "Buzz",
                "11", "Fizz", "13", "14", "FizzBuzz",
                "16", "17", "Fizz", "19", "Buzz",
                "Fizz", "22", "23", "Fizz", "Buzz",
                "26", "Fizz", "28", "29", "FizzBuzz",
                "31")
        for (i in 1..expected.lastIndex) {
            evilFizzBuzz(i) shouldBe expected[i]
        }
    }

    "but no implementation can break properties-based testing" - {

        val fizzbuzz = ::realFizzBuzz
//        val fizzbuzz = ::evilFizzBuzz

        "Multiples of 15" {
            forAll<Int> { i ->
                if (Math.abs(i)  >= Integer.MAX_VALUE / 15 -1) return@forAll true
                val nb = 15 * Math.abs(i)
                fizzbuzz(nb) == "FizzBuzz"
            }
        }

        "Multiples of 3" {
            forAll<Int> { i ->
                if (Math.abs(i)  >= Integer.MAX_VALUE / 3 -1) return@forAll true
                val nb = 3 * Math.abs(i)
                nb % 5 == 0 || fizzbuzz(nb) == "Fizz"
            }
        }

        "Multiples of 5" {
            forAll<Int> { i ->
                if (Math.abs(i) >= Integer.MAX_VALUE / 5 -1) return@forAll true

                val nb = 5 * Math.abs(i)
                nb % 5 == 0 || fizzbuzz(nb) == "Fizz"
            }
        }

        "Others" {
            forAll<Int> { i ->
                val nb = Math.abs(i)
                nb % 5 == 0 || nb % 3 == 0 || fizzbuzz(nb) == nb.toString()
            }
        }

    }


} }
Collapse
 
waterlink profile image
Alex Fedorov

Yes. You can.

But will you, really?

IMHO alert.

In Test-Driven Development, you pass the failing test with the simplest possible solution. And your solution has a higher complexity than the one proposed in the article.

You have one edge case handler more (additional “if” statement).

Therefore, if TDD practiced properly, such “evil” solution will not be written.

Anyways.

I agree that PBT has a higher degree of correctness when it comes to testing. Don’t forget, though, that EBT is easier to read and reason about.

I mean, look at that:

         "Multiples of 15" {
            forAll<Int> { i ->
                if (Math.abs(i)  >= Integer.MAX_VALUE / 15 -1) return@forAll true
                val nb = 15 * Math.abs(i)
                fizzbuzz(nb) == "FizzBuzz"
            }
        }

There is just so much stuff involved here. Show that to an apprentice-level developer and they will have a lot of questions about this test. It is not simple.

Collapse
 
jmfayard profile image
Jean-Michel 🕵🏻‍♂️ Fayard

For this "Multiples of 15" test, my refactored version would be something like

         "Multiples of 15" {
            forAll(BigInteger.positive()) { nb ->
                fizzbuzz(15 * nb) == "FizzBuzz"
            }
        }

As you can see one small pb was that I was too lazy to lookup how to test for only positive numbers. The harder and much more interesting one is I'm not used to think about integer overflows. This style of testing forced me to think about it because my test kept failing! That was actually a teachable lesson.

Anyway I highly recommend this, most fun presentation about testing I have ever read!

slideshare.net/ScottWlaschin/an-in...

Thread Thread
 
waterlink profile image
Alex Fedorov

Great refactoring!

Still the "15 * nb" part requires a mental leap.

Example tests are nice because, when written well, they can be read very quickly (like prose).

Another thing. So what if I don't need my code to handle overflows? Because all the numbers that business wants to work with are under 500.

YAGNI in that case.

Thread Thread
 
waterlink profile image
Alex Fedorov

I've taken a look at the presentation. And I'm still convinced that 2 tests is enough to drive out correct "add" code if you follow 3 rules of TDD.

And if you doubt that I thought about this enough, here is my own presentation about PBT: github.com/waterlink/property-base...

Collapse
 
waterlink profile image
Alex Fedorov

Another way to look at this “simplest possible way to make the failing test pass” is to imagine you have a developer on the team, who is removing any code that is not tested.

So that additional if statement is not tested by the test. That means it can be safely removed.

You can apply such radical technique to teams that do TDD rigorously. And if you knew there is a person on your team (and that person – is every member of the team), you are probably going to make sure to have all the production code covered.

You just don’t add any code that is not tested, as simple as that.

Collapse
 
kenvy profile image
zw • Edited

Simple but a very good teachable tdd article.

Look forward to your IOS version, Kotlin series of learning articles.