RSS Feed Subscribe to RSS Feed

 

Code Camp: Building Better Tests in Java

Building Better Tests in Java

Speaker: Ted Young

Using the Builder pattern to make tests easier. The Builder Pattern separates the construction of a (complex) object from its representation. A pizza analogy was used to explain the concept.

A Pizza director says a pizza is built using a base, sauce, toppings
Possible Builders could then be

  • ThinCrustBuilder, DeepPanBuilder
  • HotSauceBulder, TomatoSuaceBuidler
  • VegToppingsBuilder, MeatToppingsBuilder

Using a factory method can result in too many parameters.
So, you can create a factory method that defaults many values to solve this – better but…

You may want to control certain values at certain times and things then start getting complicated (too many methods/combinations).

This is basically the Object Mother pattern.

Can cause Merge hell, Copy-n-paste, too long (all sounds familiar!)

Solution…
Builders and chained Methods…
Lots of defaults (if you don’t specify, use default value, e.g. random numbers, default values…)
e.g.

Account account = new AccountBuilder()
    .withDefaultBillingPlan()
    .withPolicies(new PolicyBuilder.withDefaultPolicyPeriod())
    .create();

where for example, withDefaultBillingPlan() returns a Builder rather than an object such as BillingPlan, Account etc

Advantages to this approach:

  • Specify data not possible through the UI (although isn’t this just an advantage of test cases in general, rather than a Builder pattern specifically?)
  • Data is always consistent (but not necessarily valid – you want to test ‘invalid’ scenarios/objects also!)
  • Fills in unspecified data with reasonable defaults (reasonable = wont throw exception)
  • Extensible
  • Can export to SQL

Can migrate bit by bit from huge DataGen/DomainFactory class to Builders,
Or can do big bang.

Writing Builder methods….

  1. Write methods that always return ‘this’
  2. e.g.

    public AccountBuilder withBillingPlan(BillingPlan bp) {
        account.setBillingPlan(bp);
        return this;
    }

    This is what allows for chaining.

  3. Write English-like methods
  4. e.g.

    asSmallBusiness();

    instead of

    withSegment(AccountSegment.SMALLBUSINESS);
  5. creation does happen until create() is called
  6. e.g.

    policy = new PolicyBuilder()
        .onAccount(account)
        .withPlan()
        .create();

    should be the same as

    policy = new PolicyBuilder()
        .withPlan()
        .onAccount(account)
        .create();

Overall:
This was one of the most useful talks I attended this weekend. Perhaps mainly because the project I am currently working on has exactly the kind of monster test-generating class that Ted talked about. I would like to try implementing the Builder patter approach he advocates. I had a chance to discuss the benefits with Ted after the lecture and he summarized them as:

  • Easy to use default values
  • Break the test data generation in to multiple, easier to manage classes rather than one monster one
  • The code is much easier to understand
    e.g.

    Account account = new AccountBuilder()
    .withDefaultBillingPlan()
    .withStandardPeriod())

    is much easier to understand than

    Account account = new Account(1, 4);

Links:

Builder Pattern (Wikipedia)

Leave a Reply