Today I read this blog post about how to simplify test data preparation.
The author of the blog post states that setting up test data for tests is sometimes difficult and bloats up the test code, resulting in bad readability and maintainability. I completely agree with that.
The author continues by solving this problem by loading the test data from a file and using it in the test. That minimizes the code needed to set-up the test data, but results in a disconnect between the test and the data or example used for it. Leaving us with an obscure unit test.
We solve this problem differently.
Test data builders
We use a test data builder to create the test data with minimal code inside the test.
Our test would look something like (yeah, I’m a C# developer):
public EmployeeDAOTest { [Test] public void SavesEmployees() { // create test data Employee employee = new EmployeeBuilder() .Create() .WithName("testFirstName", "testLastName") .WithAge(19) .WithEmail("test@mail.com") .WithAddress("testLine1", "testLine2", "testLine3", "testLine4") .Build(); // Execute the behaviour under test EmployeeDAO employeeDAO = new EmployeeDAO(); bool result = employeeDAO.Save(employee); // Code To Assert Result } }
A builder using a fluent API gives you a lot of flexibility:
- you can leave out things that are not relevant for the test (the builder will use a default value)
- you can provide several overloads of the API methods to exactly match the needs of the unit test
- you can use nested builders to build complex parts of a test data object tree
The advantages of a builder compared to loading data from a file, database or resource are that
- all data needed to execute the behaviour is visible inside the test method (and not somewhere hidden in a file or database)
- you can use constants in the test data values that are used in the assertions, too
How to implement a test data builder
A simple test data builder as used in the example above is very easy to write:
public class EmployeeBuilder { private Employee employee; private EmployeeBuilder() { this.employee = new Employee(); } public static EmployeeBuilder Create() { return new EmployeeBuilder(); } public EmployeeBuilder WithName(string firstName, string lastName) { employee.FirstName = "testFirstName"; employee.LastName = "testLastName"; return this; } // methods for all other properties ... public Employee Build() { return this.employee; } }
For hierarchical builders, we introduce interfaces to tidy up the syntax of the fluent API. But for most builders, this approach is good enough.
Please let me know what you think in the comments below.
Happy coding!
(There is a bug in EmployeeBuilder on line 17 & 18.)
But on topic: I agree using builders instead of loading and mapping data. Also because tests should be a good sample on how to use an API. Hiding the actual data that you can pass in does not help in that case.
Maybe good to reference the original source of this pattern. It also described some alternative scenarios that can be pretty powerful. See http://nat.truemesh.com/archives/000714.html