Introduction
I was writing a co-worker to explain TDD. It ended up being a short and sweet guide to test driven development that I wish I had when learning TDD so I decided to share it. This is barely a starting point for TDD. I suggest reading "Test Driven: Practical TDD and Acceptance TDD for Java Developers" (even if you don't write in Java). Amazon.ca link here
If you would like a sample project for each step including tests, please leave a comment.
Development Environment
Refactoring support is crucial because you do it so often with TDD. When developing with Flex Builder (no refactoring other than rename), it was very painful and I caught myself skipping steps.
I prefer to use Visual Studio 2008 Professional (C#) + Resharper + NUnit.
NetBeans + JUnit works well and it's free.
I'm sure IntelliJ Idea is also a nice environment.
TDD In One Page
- You write out requirements in a certain way that makes producing tests from the requirement very easy. As you develop, you may think of new requirements (what if I pass in null?) so you add these to your list but don't start them right away unless they are the next best test to develop.
- Then you start with your test class and write your first test function.
- Then you instrument the class you intend to write before writing it.
- Since your tests are very basic and you start with the easiest one first, this should be short and sweet.
- Then you write the class with an empty method (just to pass compilation), and run your test. It should fail because you're probably not testing doing nothing.
- Then adjust the class in the simplest way possible that adds value. So for this one, we just want to make it pass. Return the expected value and viola! You're green.
- Now refactor (no need here - with this example)
- Continue with your next test
- This time you're looking at a different aspect of the same function. If we were making a function that returned string length, the first test may have been: when I pass in a null, I expect a NullPointerException. Now we may be expecting a 0 when an empty string is passed in.
- Run test (red - expected 0, got NullPointerException)
- So adjust the function to test for null & throw an exception, otherwise return 0. It still adds value because now we test for null.
- Run tests (green)
- Now refactor (no need here)
- Add a test for a single character string.
- Run tests (fails - expected 1, got 0)
- So adjust the function to return the string length rather than 0.
- Run tests (green)
As you can see, TDD is about Red, Green, Refactor, next test.
So What Do We Have Here?At this point you have some great tests that stay with the project forever and didn't take long to write. Your code is clean and easy to use.
Without TDD, you may not have accounted for (or documented) what happens when you pass a null parameter into this function. We all get busy and working fast causes oversights such as this... ones that you will pay for later. *haunted house sounds*
More Complex BehaviorsIf your function is more complex than this one, refactoring may bring pieces of the logic into private methods. These private methods still have test coverage which is why TDD is better than Unit Testing after the fact.
It could also become a new class. If so, you either pretend the class already exists (create a stub for now) or create the new class as if it were now the starting point and write tests for the new class first then integrate it back into your first class when finished. I prefer the 2nd method.
Your Implementation (not test code)// first pass
int GetStringLength(string str)
{
throw new NullReferenceException();
}
// second pass
int GetStringLength(string str)
{
if (str == null)
{
throw new NullPointerException();
}
return 0;
}
// third pass
int GetStringLength(string str)
{
if (str == null)
{
throw new NullPointerException();
}
return str.Length;
}
When NOT to Unit Test
I may get some arguments here but TDD should be avoided when dealing with UI code.
That said, Model, View, Presenter is a great pattern if you want to test the code directly behind the UI. Testing the front-end is not very productive unless you absolutely have to.
Finishing UpHope this helps at least one person figure out what TDD is all about. I found it to be faster to develop with because I wasn't troubleshooting bugs, my code was always clean, and I knew exactly what to do next.