I was thinking about Reactive extensions (and using it) and then it hit me – why not try and use Reactive Extensions (Rx) in order to perform the FizzBuzz kata – it seems like a perfect fit.
And so I set myself to try and use only Rx when performing this TDD kata.
What is this FizzBuzz – a quick introduction
Fizz buzz is a group word game for children to teach them about division.[1] Players take turns to count incrementally, replacing any number divisible by three with the word "fizz", and any number divisible by five with the word "buzz".
[Wikipedia]
The idea behind this exercise is to solve the simple problem of writing the numbers from 1 to N according to the following three rules:
- Number is divisible by 3 –> write Fizz
- Number is divisible by 5 –> write Buzz
- Number is divisible by 3 and 5 –> write FizzBuzz
- Otherwise write number
It’s a simple problem to solve – in fact I’m sure you’re already solving it in your head. But don’t start just yet, one of the important aspect of TDD is to only concentrate on a specific problem each time and don’t design the whole solution up front.
My First test
I generally start with the simplest wrong input I can think about. I do this as a form of “warm up” to begin my test-code cycle.
In this case any input which is lower then 1 should return an empty string.
[Test]
public void GivenNumberBelowOne_ReturnEmptyString()
{
var result = FizzBuzz.Generate(-1);
Assert.That(result, Is.Empty);
}
I wrote the simplest test, although some would claim that I should have used a negative number instead of ‘0’ – which would work for me as well. If you feel the test is inadequate – feel free to add another test as soon as you write the code to make this simple case pass
I run the test – and it failed (no surprises here).
Now to write the minimal amount of code to make the test pass:
public static string Generate(int max)
{
return string.Empty;
}
As simple as that.
To some my implementation would look like cheating (especially if you do not have any previous TDD experience). But when you think about it you realize that the code above elegantly fulfills all requirements (in this point and time).
Let’s make it more interesting
Now that we got that out of the way it’s time to start adding some value to our code.
The next requirement to tackle is “unless the number is divided by 3 or 5 we should just write it”.
So the simplest test here should be using 1:
[Test]
public void Given1_Return1()
{
var result = FizzBuzz.Generate(1);
Assert.That(result, Is.EqualTo("1,"));
}
To make the test pass I can write the following – trivial code:
public static string Generate(int max)
{
if (max < 1)
{
return string.Empty;
}
return "1,";
}
Now let’s see what happen if we pass ‘2’ (code + test):
[Test]
public void Given2_Return12()
{
var result = FizzBuzz.Generate(2);
Assert.That(result, Is.EqualTo("1,2,"));
}
// Code
public static string Generate(int max)
{
if (max < 1)
{
return string.Empty;
}
if (max == 1)
{
return "1,";
}
return "1,2,";
}
Now we get to the 3rd stage of the TDD cycle – refactoring and since I wanted to use Rx I refactored the code accordingly:
public static string Generate(int max)
{
var result = string.Empty;
if (max > 0)
{
Observable.Range(1, max)
.Subscribe(i => result += i + ",");
}
return result;
}
the code above is basically a simple foreach using Rx:
- Create an observable that would return a range from 1 to max
- Subscribe (iterate) the observable and add each item to the result
Refactoring is not only for my “production code” and since both tests are use similar code I can refactor my tests using NUnit’s TestCase:
[Test]
public void GivenNumberBelowOne_ReturnEmptyString()
{
var result = FizzBuzz.Generate(0);
Assert.That(result, Is.Empty);
}
[TestCase(1, Result = "1,")]
[TestCase(2, Result = "1,2,")]
public string GivenNumberUpTo2_ReturnNumbers(int input)
{
return FizzBuzz.Generate(input);
}
Although I could also test the 1st requirement (less then 1) using the same code I prefer to separate them since they are logically belong to different aspects of my solution. In other words I prefer to “pay” the (low) maintainability price in order to separate the requirements (readability).
Onward to Fizz
Now we’re finally getting somewhere – let’s write a failing test for “3”:
[Test]
public void GivenNumberDividedByThree_ReturnFizzInstead()
{
var result = FizzBuzz.Generate(3);
Assert.AreEqual("1,2,Fizz,", result);
}
Since we’re already familiar with TDD I’ll allow myself to jump a few stages (in this post, not in the actual Kata) and show you the result after refactoring – the code starts to look better – Rx style
public static string Generate(int max)
{
var result = string.Empty;
if (max > 0)
{
IObservable observable = Observable.Range(1, max);
observable
.Where(i => i % 3 != 0)
.Subscribe(i => result += i + ",");
observable
.Where(i => i % 3 == 0)
.Subscribe(i => result += "Fizz,");
}
return result;
}
The end result
In a similar matter I’ve TDD’ed my way to Buzz & FizzBuzz and got the following code:
public static string Generate(int max)
{
var result = string.Empty;
if (max <= 0)
{
return result;
}
var observable = Observable.Range(1, max);
var dividedByThree = observable
.Where(i => i % 3 == 0)
.Select(_ => "Fizz");
var dividedByFive = observable
.Where(i => i % 5 == 0)
.Select(_ => "Buzz");
var simpleNumbers = observable
.Where(i => i % 3 != 0 && i % 5 != 0)
.Select(i => i.ToString());
var commaDelimiter = observable.Select(_ => ",");
IObservable specialCases = (dividedByThree).Merge(dividedByFive);
simpleNumbers
.Merge(specialCases)
.Merge(commaDelimiter)
.Subscribe(s => result += s);
return result;
}
I found a cool thing – as soon as I handled numbers that divide by 3 and 5 separately I “automatically” got the case of FizzBuzz.
That’s it – simple and elegant and with Reactive extensions. Unfortunatly I failed creating an Rx only solution (I still check for i < 0)
And the all exercise took a few minutes (more time than it took me to write this blog post).
Happy coding…Labels: C#, TDD