Growing Object-Oriented Software, Guided By Tests (chapter 22)

Feb 25, 22

When creating tests, we want to reduce the amount of hardcoded data that we need to feed into them.

With lots of manual data, the tests can become hard to read and reason about.

The authors suggest using a pattern called builder to help abstract away some of the data ‘gloop’.

The methods in a builder object should be chainable, allowing us to fluently build an object within a test.

By giving these objects a name, we make them a lot more descriptive within our tests.

e.g we can create an object like the below:

public class OrderBuilder
{
	public int Discount { get; private set }
	public int Total { get; private set }

	public OrderBuilder withTotal(int total)
	{
		this.Total = total
			return this
	}

	public OrderBuilder withDiscount(int discount)
	{
		this.Discount = discount
			return this
	}

	public OrderBuilder build(){
		this.Total - this.Discount
			// other setup code here
			return this
	}
}

and then use it in tests like this:

var orderWithRefund = new OrderBuilder().withTotal(1000).withDiscount(100).build()

This seems like a really powerful pattern and will make for fluid assertions.

Look at how the builder methods return self/this - This allows to chain the methods together in the tests. Very smart!

Try to reduce duplication in tests by using clever naming and instantiation of objects. For example, create one builder object, and then only chain methods when required.

var order = new OrderBuilder().withTotal(1000)
// test 1 setup
var orderWithDiscount = orderWithTotal.withDiscount(100).build()

// test 2 setup 
var orderThatHasShipped = orderWithTotal.hasShipped(true).build()

// test 3 setup  
var orderThatHasDiscountAndHasShipped = orderWithTotal.hasShipped(true).withDiscount(100).build()

In complex cases, rather than mutating the builder object itself in the with methods, consider returning a new object.

Don’t be afraid to pass builder objects to different builder objects - the world is your oyster when it comes to setting up test code! It will also make reading the tests a lot nicer. The end goal here is to make the tests as readable as possible for future maintainers (i.e us :D ). It should be easy to explain what the test is doing, just by reading the setup code.

he real end goal is that a non-technical person should be able to read the tests and figure out what is going on (albeit with some wonky punctuation!)