Making Unit Tests Fun and Engaging with Tabular Test Data Format
Unit tests have become an indivisible part of programming. Application code is incomplete without adequate unit test coverage. For a developer, unit tests offer significant advantages, ensuring your code is thoroughly tested thereby boosting your confidence. It allows you to modify existing code, no matter how complex, without fear. When deploying your application to production, you can be confident that it will perform reliably.
One aspect of unit testing that I always found less interesting, hard to maintain, and demotivating was managing the test data objects. In this post, I will share the approach that I follow to simplify and make managing test objects more interesting. And, this will actually encourage you to write more and more unit tests.
Although I will be using Java with JUnit, the approach should be applicable to any other technology.
For demonstration purpose, I will use a small and simple Java program that takes two lists - a list of hotel room bookings and a list of room rates. The program calculates the total bill amount for each booking and returns the list of bookings with the total bill.
Below is the function that performs the above operation and we want to cover that with unit tests:
Let's add a unit test for the below case:
A room with number 10 is booked from 1 Jan 2025 to 10 Jan 2025. Rate for that room is 2000 ¥ per night for the period 1 Jan 2025 to 31 Jan 2025. The expected result is a bill with a total amount of 18000 ¥.
Let's add one more unit test for another case:
A room with number 10 is booked from 1 Jan 2025 to 10 Jan 2025. Rate for that room is 1000 ¥ per night for the period 1 Jan 2025 to 05 Jan 2025 and 2000 ¥ per night for the period 06 Jan 2025 to 31 Jan 2025. The expected result is a bill with a total amount of 13000 ¥.
Such a program requires a lot of unit tests to cover various possible cases. To avoid code duplication and to increase code reuse, you can refactor the test objects into separate classes and use them across your unit tests.
However, this way of managing test objects has couple of drawbacks.
You have to "write" a lot of code just for creating test objects.
The test data (test case) is hard to visualise. You have to "read" the entire code for test objects to understand it.
Things become challenging when the number of tests grows and the test data becomes big and complex. This not only makes developers lazy in writing unit tests but also reduces productivity.
For such cases, to simplify the management of test objects and enhance the readability of unit tests, consider using a tabular data format and automating the creation of test objects. This approach streamlines the process and makes your tests easier to understand and maintain.
With this approach, you define the test data in a text format as a table. For example, a booking with id 100 on room number 10 from 01 Jan 2025 to 10 Jan 2025 can be defined as a text as below:
In same fashion, you can define test data for room rates as below:
Then you write a generic program that takes in this text data and converts it into corresponding data objects. This is the key step that removes the complexity and completely changes the way you write unit tests.
With this new approach, the unit tests that we have seen above will now look like as below:
The function testCalculateBills()
takes the test data in tabular text format, converts them into data objects and use them to test the function billCalculator.calculateBills()
.
Compared to previous code, this unit tests are more readable. This makes it much easier to understand the test data configuration.
You also have to write less code. If you want to write more unit tests, it is just a matter of creating new tables. If you want to update or extend existing unit tests, you only need to make changes in the existing tables.
This is how I made writing unit tests a fun :)
You can find the complete source code on GitHub.