I’ve only been doing TDD for a few weeks, but I’m completely sold. I don’t want to go back! I’ll be honest though, it hasn’t been easy. I’ve made mistakes, I’ve wasted time, but I’m really starting to reap the benefits.
I’ve always thought I was a good developer. I write decent code and it works mostly as expected. It took me many years into my career before I wrote my first unit test. It always fell into the category of too time consuming or expensive. Oh the irony!
As I started learning how to write to unit tests, I always found myself rewriting things I already did just to get them to be unit tested; how frustrating! A unit test that should have only took a few minutes, ended up taking a really long time because the code had to be refactored just to be tested. No better way to turn you off from unit testing.
Enter test-driven development…
TDD is a discipline. It requires much smaller steps than the average person is used too. It also requires you to break rules just to get a “green bar”. Without persistence and the ability to see the bigger picture, these rules can easily make anyone give up.
When I first started I immediately realized my pace of “output” got slower in my mind. It felt slow because 20 – 30 lines of unit testing setup might equal less than 10 lines of “tangible” code. This is a hard pill to swallow as you start.
What would take me 15 minutes is now taking me 30 minutes. However, the end result is incredible. My code is so much cleaner and much more accurate. It’s important to not focus on the small increases in time at the beginning, trust me it all washes out in the end.
The best analogy I’ve heard is the tortoise and the hare. I used to be the hare, racing through the logic and lines of code. Only to hit – what I thought was the finish line – to find out that I had to spend time debugging issues that arouse along the way (that I didn’t catch). Unfortunately, this is where I (the hare) began to get tired and slow down. Issues became harder to detect. I found them, but the cost was high as my energy got lower.
Meanwhile, the tortoise (me while doing TDD) is moving along at a nice pace addressing much simpler, smaller pieces of logic to eventually surpass the hare and win the race (or maybe tie).
Enough of the analogies, I think it’s time to – you guessed it – test-drive this article with some examples. I’ll start with the first example that I ever TDD’ed – the Fizz Buzz example. This coding test was first discussed by Jeff Atwood to help determine who is a good developer.
Fizz Buzz works as follows. It receives a number as input and it outputs one of the following 4 things:
I am going to write this in C#. I’m going to write this in a new Unit Test Project. For simplicities sake, I will write both the tests and the resulting function in the same class.
Now before I begin, there are a few different options of how this could be TDD’ed:
To me both are valid and the one I choose depends on the complexity of the functionality. Because this is a blog article, it might work better with multiple small test methods instead of a constantly growing one.
Let’s begin. Here is the first test method:
As you will notice, the code currently doesn’t compile because the FizzBuzz function doesn’t exist. So the first rule of TDD is to get the code to compile and a green bar as fast as possible. Here is the simplest solution I can find:
There are a few interesting things to point out here. Because I wrote my tests using var and I used my IDE to automatically create the function for me (because it didn’t exist) it detected the correct input type as int but wasn’t sure what the output type should be, so it made it an object. Ironically this isn’t a bad choice as we will see shortly.
The next thing to notice is I simply returned a constant value of 1. If I were coding this normally I wouldn’t have done it, I would’ve returned p – the input parameter. I wanted to show the incremental steps.
There are two possible next steps that I see:
Given that I see the number 1 three times in less than 10 lines of code, I think removing the duplication makes most sense. Here is my refactoring to remove duplication of the number 1:
After refactoring, it’s a good time to run the tests again and make sure we still have a green bar. As expected we do. The nice part about this refactoring is we don’t need to write another test that might be called Given2Expect2, I think we can all be confident in our previous refactoring.
Here is the next obvious test I see:
As expected, this new test fails. Time to update our function to get a green bar. Once again I will go with the simplest solution:
Running the tests shows a green bar. Now we can refactor with confidence that we have our security blanket in case something goes wrong.
Because this example is pretty simple, we have the same opportunity as before. We can write a simple test to show how the constant won’t solve the long-term problem or we can refactor the solution to not use the constant conditional to 3.
After refactoring, it’s important to run the tests and ensure we still have green.
On to the next unit test:
Because of our previous refactoring, it’s safe to skip the use of a constant and instead apply Obvious Implementation. I often will use this rule as much as possible as I can better control the size of steps I take during my TDD efforts:
Once again we have green. At this point I don’t see any obvious refactoring, so let’s write another test:
As expected we have a red bar:
Once again the bar is green. But look at all that duplication! 3’s and 5’s splattered all over this function. Given that we have all green, it’s safe to refactor this function. There really are hundreds of ways to refactor this function. The simplest one to me is leveraging one more magic number.
I see one more unit test still. The minimum input value is 1. What will happen if 0 is entered? I think an exception should be thrown because it’s invalid input:
Once again we have a red bar that we need to solve. Knowing that anything less than 1 is invalid, let’s use obvious implementation to solve the red bar:
I sure feel pretty confident in this code now. More importantly, if the specifications ever changed, we have a solid foundation of tests to allow us to update with confidence.
As you can see from this very small TDD example, the steps from writing a test to writing the code feels slow and took a total of 13 steps! The final result of the FizzBuzz function could probably be written in one giant step; however, by doing TDD we saved future us two important things:
Since starting TDD, I’ve made a few mistakes. Here are the lessons I’ve learnt from them and hopefully it will help you getting started:
An example of this might be if you are writing a function that returns an integer offset that will be used to calculate a date offset. The function that returns the integer offset might be too small to test – unless there are important business rules happening inside this function. Instead, test the function that calculates the date offset. The date object that results from using the offset integer is probably more important. Published on Aug 6, 2013 Tags: Optimization
| ASP.NET MVC and Web API Tutorial
| Theory
| fizzbuzz
| tdd
| test-driven development
Did you enjoy this article? If you did here are some more articles that I thought you will enjoy as they are very similar to the article
that you just finished reading.
No matter the programming language you're looking to learn, I've hopefully compiled an incredible set of tutorials for you to learn; whether you are beginner
or an expert, there is something for everyone to learn. Each topic I go in-depth and provide many examples throughout. I can't wait for you to dig in
and improve your skillset with any of the tutorials below.
[TestMethod]
public void Given1Expect1()
{
var expected = 1;
var actual = FizzBuzz(1);
Assert.AreEqual(expected, actual);
}
private object FizzBuzz(int p)
{
return 1;
}
[TestMethod]
public void Given1Expect1()
{
var expected = 1;
var actual = FizzBuzz(expected);
Assert.AreEqual(expected, actual);
}
private object FizzBuzz(int p)
{
return p;
}
[TestMethod]
public void Given3ExpectFizz()
{
var expected = "Fizz";
var actual = FizzBuzz(3);
Assert.AreEqual(expected, actual);
}
private object FizzBuzz(int p)
{
if (p == 3)
return "Fizz";
return p;
}
private object FizzBuzz(int p)
{
if (p % 3 == 0)
return "Fizz";
return p;
}
[TestMethod]
public void Given5ExpectBuzz()
{
var expected = "Buzz";
var actual = FizzBuzz(5);
Assert.AreEqual(expected, actual);
}
private object FizzBuzz(int p)
{
if (p % 3 == 0)
return "Fizz";
if (p % 5 == 0)
return "Buzz";
return p;
}
[TestMethod]
public void Given15ExpectFizzBuzz()
{
var expected = "FizzBuzz";
var actual = FizzBuzz(15);
Assert.AreEqual(expected, actual);
}
private object FizzBuzz(int p)
{
if (p % 3 == 0 && p % 5 == 0)
return "FizzBuzz";
if (p % 3 == 0)
return "Fizz";
if (p % 5 == 0)
return "Buzz";
return p;
}
private object FizzBuzz(int p)
{
if (p % 15 == 0)
return "FizzBuzz";
if (p % 3 == 0)
return "Fizz";
if (p % 5 == 0)
return "Buzz";
return p;
}
[TestMethod]
[ExpectedException(typeof(IndexOutOfRangeException))]
public void Given0ExpectException()
{
FizzBuzz(0);
}
private object FizzBuzz(int p)
{
if (p < 1)
throw new IndexOutOfRangeException();
if (p % 15 == 0)
return "FizzBuzz";
if (p % 3 == 0)
return "Fizz";
if (p % 5 == 0)
return "Buzz";
return p;
}
Summary
Related Posts
Tutorials
Learn how to code in HTML, CSS, JavaScript, Python, Ruby, PHP, Java, C#, SQL, and more.