π¨ Maintainable tests with Test Data Builders
Refactoring and then spend hours changing your tests? Not sure what a test does? Test Data Builders to the rescue!
Refactoring and then spend hours changing your tests? Not sure what a test does? Test Data Builders to the rescue!
π§ͺ Tests and maintenance
Most developers understand the importance of tests: they provide confidence. Tests allow you to refactor safely.
These developers know that maintainable code is extremely important. Fewer people know that this is just as crucial for test code. If our tests are not maintainable, this will have negative effects. Refactoring becomes more difficult, which eventually leads to worse production code.
Two possible causes of poorly maintainable tests are:
Too much coupling due to duplication in tests.
Tests which are non-expressive.
Letβs see why these aspects affect the maintainability of our test code.
π The pain ofΒ coupling
Youβve probably experienced it: something needs to be changed in the code and thatβs done in no time. The problem is that you spend hours adjusting tests afterwards.
Tests are a form of coupling. They have to be, how else can we call our production code? The problem arises when we modify signatures in our code, such as introducing an extra parameter. The coupling then becomes painfully clear: you have to adjust all the tests that use that piece of code.
In the production code we adhere to the βDonβt Repeat Yourselfβ principle, in testing this is often more difficult. You simply need variations of objects, which is why you create objects more often in tests than in production code. This leads to a subtle form of duplication: creating objects.
This form of coupling has a negative effect on the maintainability of tests, but thatβs not the only thing we need to watch out forβ¦
π£οΈ The importance of expressive tests
Readable code is important. Good developers understand that code is read more often than it is written, which is just as true for tests.
Tests help readers understand the behavior of code. This is why they serve as an effective form of documentation.
Unfortunately, this takes some effort. In order for tests to effectively explain the behavior of code, it is important that you separate main from side issue, but what is what in a test?
Easy. The most important thing in a test is the what (the behavior), not the how (the mechanism). In listing 1 you see an example of a non-expressive test, because the tested behavior is not separated from the test mechanism.
@Test
void an_unreadable_test() {
Country country = new Country("USA", Currency.US_DOLLAR, Language.ENGLISH);
Author author = new Author("Oscar Wilde", country);
Novel novel = new Novel(
"The picture of dorian gray",
50.00,
author,
Language.ENGLISH,
Lists.newArrayList(Genre.MYSTERY)
);
PurchasedBook book = new PurchasedBook(novel, 1);
Invoice invoice = new Invoice("test", country);
invoice.addPurchasedBook(book);
assertEquals(56.35, invoice.computeTotalAmount());
}
Listing 1. Non-expressive test; intent is hidden by mechanism
If these things arenβt properly separated, a test wonβt tell you whatβs happening. This causes readers to take longer to comprehend it, or worse, they become afraid to change it. Fortunately, it doesnβt have to get that far, thanks to Test Data Builders.
π· What are Test DataΒ Builders
Test Data Builders are a form of the Builder Pattern, but applied to creating objects for tests. These builders create objects with safe, logical default values. Like the regular builder pattern, they offer public chainable methods with which the objects can be modified.
This pattern is not always applicable. It is effective for creating complex object structures, especially for Value Objects or Entities.
Besides the advantages, there are also disadvantages. You have to write and maintain these builders yourself, so bugs can arise in them. Do not immediately make builders for everything, but make a weighted decision!
β¨ How Test Data BuildersΒ help
Test Data Builders help decouple your test code more from your production code while increasing its expressiveness. That almost sounds like magic, but as youβll see later, itβs really not.
π Decreased coupling
Test Data Builders solve the issue of coupling by encapsulating construction of objects. You use the builder to create objects for your test, reducing the number of places where this occurs. Suppose you add an argument, then all you need to do now is modify code in the builder!
π Increased expressiveness
The problem of non-expressive tests is also solved by Test Data Builders. Because they create objects with safe defaults, you only have to adjust relevant values for a test. Letβs say you want to validate one property, then thatβs the only one you override when creating the object, the rest doesnβt matter. This way you reduce technical βclutterβ and the test conveys more! Compare listing 1 to listing 2 for example.
@Test
void increased_readability_by_test_data_builder() {
Invoice invoice =
anInvoice()
.from(USA)
.with(
aPurchasedBook().of(aNovel().costing(50.0))
).build();
assertEquals(56.35, invoice.computeTotalAmount());
}
Listing 2. Test Data Builders expose test intent
π Bonus: testΒ DSL
An additional advantage is that builder methods allow you to speak more in terms of your domain. If you compare listing 1 to listing 2 you will see that the builder methods convey more than a constructor call or setting a property. If you name the methods of the builders well, you end up with a Domain Specific Language for your tests!
π WrappingΒ up
Tests are important for a good codebase, but if they are not maintainable they can make changes difficult. This can be due to coupling in tests, or tests that are non-expressive.
Test Data Builders provide a solution to both problems. They make it possible to build test data largely in one place and thus prevent excessive coupling. In addition, they enable you to show the intent of tests better.
As with any pattern, you should also consider the drawbacks when considering using them. If you do this right, they will make your tests more maintainable and expressive!
So next time you write a test, remember this effective solution. Make it easy on yourself and your team!
What is your experience with Test Data Builders? Do you see any other pros or cons? Let us know in the comments!
Originally published at https://www.codecraftr.nl on August 8, 2021.