Unit tests have most value when they’re easy to read and understand. Unit tests typically follow a very straightforward pattern:
- Simulate system state
- Call the method under test
- Verify the method’s result and side effects
So long as this pattern is obvious in the test, the test is readable.
@Test public void testDeleteSpanner() throws Exception { // 1. Simulate system state - DAO returns a spanner when requested when(spannersDAO.get(SPANNER_ID)).thenReturn(SPANNER); // 2. Call method under test: controller.deleteSpanner ModelAndView response = controller.deleteSpanner(SPANNER_ID); // 3(a). Verify method side effect - spanner is deleted via DAO verify(spannersDAO).delete(SPANNER); // 3(b). Verify method result - controller forwards to display spanners page assertEquals("view name", VIEW_DISPLAY_SPANNERS, response.getViewName()); }
Three or four line test methods are succinct, focussed and readable. However, simulating the system state often requires creation of complicated stub objects. This can result in long test methods where most of the test is setup followed by one or two lines of verification.
Factoring out object creation
Where possible, it’s best to factor out any hard setup work into separate methods. If it’s not possible to create the necessary stub data in one or two lines, make a private helper method just to create the stub data.
BEFORE:
@Test (expected = IllegalArgumentException.class) public void testZeroSizeSpanner() { Spanner spanner = new Spanner(); spanner.setId(1); // Better set an id. Not necessary for the test but every spanner should have an id. spanner.setName("Bertha"); // Better set a name too spanner.setOwner("Mr Smith"); // Again, we're not testing this, but every spanner should have an owner spanner.setSize(0); // This is the important bit! The important attribute of this spanner is that its size is zero! spannersDAO.create(spanner); }
AFTER:
@Test (expected = IllegalArgumentException.class) public void testZeroSizeSpanner() { // Create a spanner with zero size Spanner spanner = zeroSizeSpanner(); spannersDAO.create(spanner); } /** * Kate * @return A new Spanner with zero size */ private Spanner zeroSizeSpanner() { Spanner spanner = new Spanner(); spanner.setId(1); spanner.setName("Bertha"); spanner.setOwner("Mr Smith"); spanner.setSize(0); return spanner; }
This makes simpler test methods but may result in a glut of private helper methods to create every conceivable variation of test data.
Builder Pattern
In theĀ book Growing Object-Oriented Software, Guided by Tests, authors Steve Freeman and Nat Pryce suggest a neat pattern for cleanly creating test data for unit tests. They suggest using the builder pattern to build test objects which are as simple or as complicated as necessary for the test. The builder can set default data in fields meaning that only data significant to the result of the test needs to be set.
public class SpannerBuilder { //Making default values public can be useful for test assertions public static final int DEFAULT_ID = 1; public static final String DEFAULT_NAME = "Bertha"; public static final int DEFAULT_SIZE = 16; public static final String DEFAULT_OWNER = "Mr Smith"; // Fields all have default values. We only need to call the setters if we want different values private int id = DEFAULT_ID; private String name = DEFAULT_NAME; private int size = DEFAULT_SIZE; private String owner = DEFAULT_OWNER; public SpannerBuilder setId(int id) { this.id = id; return this; } public SpannerBuilder setName(String name) { this.name = name; return this; } public SpannerBuilder setSize(int size) { this.size = size; return this; } public SpannerBuilder setOwner(String owner) { this.owner = owner; return this; } public Spanner buildSpanner() { Spanner spanner = new Spanner(); spanner.setId(this.id); spanner.setName(this.name); spanner.setSize(this.size); spanner.setOwner(this.owner); return spanner; } }
Notice that the setter methods return this. This allows method chaining. So we can now create our zero sized test spanner like so:
@Test (expected = IllegalArgumentException.class) public void testZeroSizeSpanner() { // Create a spanner with zero size Spanner zeroSizeSpanner = new SpannerBuilder().setSize(0).buildSpanner(); spannersDAO.create(zeroSizeSpanner); }
When we use the SpannerBuilder, we don’t need to worry about setting an id, name or owner for the Spanner. If we don’t define an attribute, the spanner will use its default.
Improving readability
We can make a couple of improvements to the syntax of the builder to make it more like a domain specific language (DSL). First, create one or more static factory methods to create the builder instance rather than using the new keyword to create the builder instance. Second, change ‘setter’ method names to words that make the chained calls read more like an English sentence. Like this:
Spanner hazell = aTestSpanner().named("Hazell").ownedBy("Mr Smith").build();
Be First to Comment