04 May 2005

Test before vs. test driven

I've been seeing a lot of articles lately about "test driving" code when developing software. This has appeared most lately in an article on ADC, but also - as I've been looking for work recently - during interviews. Companies I've worked for have always written tests before they write code (or at worst, just after). This is often passed off as test driven code. However, it isn't. Test driving means writing a very simple test first, that forces you to write the simplest code that will pass the test.

So for example, suppose you want to create a class called Xxx. To do this, first create a class called XxxUnitTest. Add a method called testCreate():

public void testCreate() {
  assertNotNull(new Xxx());
}

What this does, is force you to write the implementation class. You can follow the same simple pattern for driving out member methods, the test doesn't need to stay the same, it can change dynamically.

public void testCreate() {
  Xxx x = new Xxx();
  assertEquals(“bar”, x.foo());
}

We've now driven out the method foo(). Repeating this process, we can drive out all the implementation, safe in the knowledge that we have pretty good test coverage.

If you always write the simplest code to pass the test, sometimes you end up needing to triangulate to drive out the desired behaviour. So for example consider the following:

public void testFoo() {
  assertEquals(“bar”, new Xxx(“bar”).foo());
}

This code implies that the constructor will store the string passed in a member field, then return it when getFoo() is called. However the simplest code that will pass this test is something like:

class Xxx {
  Xxx(String bar) {}
  getFoo() {
   return “bar”;
  }
}

This code passes the test, however, it doesn't express the intent implied in the unit test. So now, you need to include a new test that checks a different argument.

public void testFoo() {
  assertEquals(“bar”, new Xxx(“bar”).foo());
  assertEquals(“baz”, new Xxx(“baz”).foo());
}

So the simplest code that will pass the test will store the parameter passed into the constructor, and return it when getFoo() is called. Of course, this test lends itself nicely to a little refactoring to reduce the duplication in the test...

If we were to test before we wrote this class, we would potentially build up a large chunk of test code, that will contain a lot of compile errors, meaning the code is a long way from being complete at any point in time. We may also decide, once we write the code, that the test is not correct, as we learn something by writing the code that we didn't know beforehand. By keeping the tests and the code in tight sync, you minimise the amount of time spent in the wilderness, and ensure good test coverage.

This approach lends itself nicely to pairing. The pair can work off each other, one writes the failing test, the other does the simplest thing to make it pass, and the cycles continues. This shares the keyboard around, and introduces some good natured competition, usually producing better code than the traditional pairing approach of driver and navigator.

0 Comments:

Post a Comment

<< Home