Archive for November 2008

Test Sclerosis – The Trouble with Unit Tests

Your typical system, written in a nice object-oriented way performs its task using a number of collaborating objects that send methods to each other. Ignoring some of the complexities like objects actually creating new objects in response to message, we can draw a diagram like the one below:

Each arrow represents a method call. Now for mocking and unit testing, usually you want to test one particular part of the system. In a lot of cases I have seen so far this would be a single object. Somewhat like this:

As we are all aware of dependencies by now and know how we inject them, the constructor for this object probably takes its two collaborators as parameters. So we can easily pass in mock objects, that are used to mimic the actual collaborators behaviour and/ or verify that the object under test actually does trigger certain actions in its collaborators:

So far so good. What is the problem with this approach?
Another thing, that the well-adjusted programmer does quite often, is refactoring. While a lot of people think of rename class/ method/ variable, when refactoring is mentioned, these are obviously the trivial cases. Let’s consider a serious change to a subsystem of our hypothetical application:

We might actually introduce some new objects to get a better factored system (contrived example). So we end up with the following:

If we actually went down the hard-core mocking road with that system the place we start from actually looks like this:

The red lines indicate interactions that are now asserted and mocked out throughout the tests. To do a refactoring like the one proposed above you have to change a whole lot of test cases. Even worse I don’t have the confidence, that the behaviour is still the same, because I had to fiddle with the tests during the refactoring, even though the “outer” interface remained the same. I call this situation test sclerosis as the tissue of your application is hardened by all the tests and mocks and thereby loses a lot of flexibility.

How to avoid this situation?

One important question that helps avoiding getting into the above mentioned situation is asking about the value of writing a particular test. Generally we write tests for the following reasons (not a complete list):

  • Ensure the code actually provides the required functionality.
  • Avoid regression (safety net).
  • Help with design.
  • Make debugging easier.
  • (Make you feel confident.)

It seems to me that naive massively stubbed, or mocked unit tests usually don’t help much with these goals.

The general advice seems to be to choose your units carefully. Single objects are probably too fine grain. Look for natural subsystems. You might for example stub out the file system or an external system that has a very clearly defined interface, e.g a mail server. Keeping the number of mocks and stubs needed per test-case low (i.e. zero or one) is a good tactic. Try to test things that are interesting and non-trivial.

Intellij 8 is here!

After using Intellij 8 for one hour, I have to say JetBrains raised the bar. What I liked:

  • Extract Method Object Refactoring: Lets you extract an object, where you can’t extract a method, because of multiple return values. I vaguely remember having spent a lot of time doing this manually to castor’s evil 1200 line sax event handler method to do some debugging…
  • Extract Class: Pushes methods to a new class and creates a delegate + forwarding mehtod.
  • Inject Language: Lets you define a language for string literals, which gives you all the intellij goodness in there.
  • Growl integration: Long running operations such as builds and source control operations produce growl notifications on the mac.

Okay that’s enough of a love fest. Go get it! The personal license is only $249…

JUnit Shortcomings I

A few days back I wrote a test that used the filesystem. In order to keep my test independant from any particular file
layout I usually do something like this:

public class SomeFileSystemTest {
    @Test
    public void performTest() throws IOException {
         doStuff(tempDir);
         ...
    }
 
    private File tempDir;
 
 
    @After
    public void deleteTempDir() throws IOException {
        FileUtils.deleteDirectory(tempDir);
    }
 
    @Before
    public  void createTempDir() throws Exception {
        tempDir = File.createTempFile("temp", "SomeFileSystemTest");
        tempDir.delete();
        tempDir.mkdirs();
    }
}

This seems to be the JUnit 4 way of doing things. So today I got back to this test, because I wanted to reuse the temp
directory bit in a different test. So I am wondering if there is any way to reuse this bit other than pulling it into
a shared super class (how a testing framework is promoting bad design).

But how would I like to do this (Christmas-driven-development)?

The functionality for providing a resource and tearing it down again should be encapsulated! So how about this:

public class TempDirFixture implements Fixture {
    final private File tempDir;
 
    public TempDirFixture() throws Exception {
        tempDir = File.createTempFile("temp", "SomeFileSystemTest");
        tempDir.delete();
        tempDir.mkdirs();
    }
 
 
    @Override
    public void tearDown() throws IOException {
        FileUtils.deleteDirectory(tempDir);
    }
 
    public File getTempDir() {
        return tempDir;
    }
}

All I want to do in my test case is to specify, that I want to use a TempDirFixture.
The well adjusted java developers way of doing this is by using a constructor parameter. So the test case would become:

public class SomeFileSystemTest {
    final TempDirFixuture fixture;
 
    public SomeFileSystemTest(TempDirFixuture fixture){
        this.fixture = fixture;
    }
 
    @Test
    public void performTest() throws IOException {
         doStuff(fixture.getTempDir());
         ...
    }
}

Then the test runner would be responsible for newing a TempDirFixture up and tearing it down later.
So the test runner becomes a bit of a mini DI-framework. The dependencies might be injected on the method rather than
the object level. Like this:

public class SomeFileSystemTest {
 
    @Test
    public void performTest(TempDirFixuture fixture) throws IOException {
         doStuff(fixture.getTempDir());
         ...
    }
}

While I am calling for a new feature I am willing to give up the old @before and @after annotations,
because they are so imperative and not what I want to express. Obviously one might also think about the scope of these
Fixtures, but that’s the topic of another blogpost.