Friday, January 15, 2010

Test Driven Development in One Page or Less

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 Behaviors

If 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 Up


Hope 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.

3 comments:

Anonymous said...

Great post, you should write more often.

What I always get caught up on is that every layer of Java EE applications often involves some framework or managed container service. I think it is very different than testing pure logic or library code you write yourself. I think that's where design patterns help. For example: service facade, service, and data access object. I may create service facades to expose a service as REST or SOAP, but the core service code is kept separate. If the service needs to talk to a database, the data access code has been separated and can be mocked in tests. In my projects I thoroughly test my service code, but have little to no tests for my DAO and service facades. Recently I've been looking at a tool called SoapUI for creating test suites for SOAP and REST web services. It can even do load testing.

Unit testing is the area I need most improvement on. I want to switch myself from writing tests just before committing to subversion, to TDD. I think the only way I can be efficient at that is by first learning how to unit test every type of scenario I need. Transactional services, JPA data access objects, SOAP and REST web service endpoints and clients, etc. Maybe some of those are integration tests, not unit tests.

Graeme Wicksted said...

Good notes. You also always want to test down not up with TDD. If your framework does not allow tests to be accomplished easily, test down the code that is.

Anything strongly tied to a framework should be disposable code (if possible). Consider this: if I wrote an application strongly tied to EJB2, I'd have to completely toss everything I wrote to maintain such things as PCI compliance (EJB2 is no longer supported).

This is another reason why the UI is often skipped. It's strongly tied and disposable.

I also think of SOAP calls and Database IO as something that should be in Integration Testing. There are all sorts of idiosyncrasies that cannot be accounted for by stubs. Load testing these components together is also very important.

michaelvk said...
This comment has been removed by the author.