Over the past few months on my project, a pattern emerged for using Builders and Object Mothers to build-up objects for unit testing. We were able to keep our tests minimal and clean, decouple test code from production code, and solve some design issues such as constructors with too many parameters. In this multi-part series, I will take you through the evolution of the pattern over a series of refactorings.
Note: This is contrived for simplicity
The basics: We are building a system that schedules shipments of widgets to a specified address.
The interesting bit: We have an
Address class that we use quite often in our tests. By “use”, I mean instantiate one with some valid data to either exercise it or assert against it.
1) Initial Code:
The two hot spots here are the
Address constructor and the customization of the
Address fields in the tests.
Let’s talk about the constructor:
- It has too many parameters - four! (And you could imagine a real
Addressobject having at least a couple more) And, multiple parameters in a row of the same type is even worse. If I accidentally swap
citymy program will still compile as both parameters are Strings, but it will probably fail at an unexpected time later.
- The arbitrary values used to construct the
Addressdo not reveal intention. Why “123 Main St”? Why “IL”? Could I change that to be any state? Which values are effecting the outcome of each test and which are completely arbitrary?
- Optional parameters.
lineTwoseems to be optional because of the empty String. Should we overload the constructor instead? Provide a setter?
And customizing the
Address fields for each test:
- Prefer immutable state. The
Addressmutable. Before that, we had a nice, happy immutable object, as you can see by the presence of the
finalkeyword on the other instance fields.
Because we were using the same
Addressobject in many tests and we needed a different value for only one field, we added a setter instead of calling the constructor again. On the upside though, at least this test reveals its intention - that this test only cares about the
- Keep test-only code out of production code. It’s very likely that
setStatewas added for this one test, and is not called by any production code. This is a smell that should be avoided.
2) Introducing a Builder
First, we attempt to give
Address it’s immutability back and avoid adding telescoping constructors by introducing a Builder. The Builder allows us to separate the steps for constructing an object from the final representation of it.
|Looking good||Needs improvement|
The Builder lets tests construct
The addition of default values to the Builder pulls that arbitrary data out of the tests themselves, which further helps to highlight any tests that need a specific value, and without needing a setter.
This is yet another class to maintain.
By making the Builder a separate object, we still need
Additionally, our Builder actually has two responsibilities. First is how to construct the
Part II will look at moving the Builder into a static inner class to fix the constructor with too many parameters issue.