Test Data Buildering

This blog post accompanies one of our weekly lightning talks, embedded below. Read the text, watch the video, or heck, do both!

[video:youtube:hUgp4svllcA]

Over the course of our learning how to specify and write tests for our code on the HIP we have gone through many different styles of dealing with setup. There was a stage when No setup was done and therefore very little real testing of behavior. Then there was the time of mocking absolutely everything (including numbers). That time still haunts us, but we are slowly putting it behind us. After that we got mock shy and just started using the real objects whenever possible.

This stage in our evolution caused tests to be written. The tests checked meaningful behavior, were fast, and somewhat maintainable. The problem was that they were not always the easiest to read. The problem came down to how we built the objects that we wanted to use.

   FundOfFund fohf = new FundOfFund( [8 parameters, only two of which we care about] );

We tried to solve this problem by having a whole load of pre-built test objects stored as statics.

    public class TestFundOfFund {
        public static final FundOfFund USD_FOHF = new FundOfFund(...);
    }

This unfortunately lead to us have a lot of hidden knowledge in our tests. “Oh that USD_FOHF, it also happens to have an initial price of $5.” This caused us to start backing away from that approach pretty quickly.

Our next step was to try out mocking again. If we needed a FundOfFund, then we would mock out the parts of a FundOfFund that should be used in the test.

    final FundOfFund fohf = context.mock(FundOfFund.class);
    context.checking(new Expectations() {{
        allowing(fohf).getName(); will(returnValue("blah"));
        allowing(fohf).getCurrency(); will(returnValue(usd));
    }});

This worked. It expressed what was needed for the test. But it is noisy and annoying to type. If a large amount of data is needed (which we sometimes need), then it gets hard to see what has been setup. It also caused noise in tests when some data needed to be available for the code under test to work, but the value had no bearing on what we were trying to specify (name in all of my code snippets here is a prime example). There was no way for making sure that the object had sensible defaults.

The next thing we are trying out is Test Data Builders. The standard way of doing it is fine and gets it done. What I don’t like about it is the mass of boilerplate code that is needed (all of those with*() methods written out individually? ick!). The make-it-easy framework is a bit better, but has problems as well.

    FundOfFund fohf = make(a(FundOfFund, with(name, "bar"), with(currency, usd)));

To write it this way you need to import “make”, “a”, “name”, and “currency” into the namespace. Pollution! Collision! Confusion ensues. But the basic idea is good. So what I am trying out now is using the framework for its basic elements and wrapping it up a little.

    FundOfFund fohf = new FundOfFundMaker() {{
        with(name, "bar");
        with(currency, usd);
    }}.build();

So far this has worked out pretty well (if you can get over the instance initializer syntax). It doesn’t pollute the namespace and also it seems to provide a nice middle ground between the boilerplate of the all custom builders and the more composable nature of make-it-easy. For instance:

    FundOfFund fohf = new FundOfFundMaker() {{
        withYearEndPrice(4, december(30, 2010));
    }}.build();

Leave a Reply

Your email address will not be published. Required fields are marked *