If you are a governed by conscience developer, you write tests, period.
As any piece of code, well written tests should be clear, concise, easy to maintain and actually test something. As everything in life, those skills come with experience.
Today we will talk about parameterized unit tests, as they provide ways of making your unit test development more complete, less tedious, more readable and much easier to maintain.
It is worth noticing that JUnit 4 has not chosen a very flexible approach to parametrized tests. They improved significantly in JUnit 5 (which I will visit in a future post).
If you aim for the maximum power and flexibility, then you should take a look to JUnitParams (I will also visit this JUnit addon in a future post).
What kind of problems does a Parameterized Unit Test solves ?
You are aware of the DRY principle, however if you are not yet using parameterized tests, you may have found yourself in situations where you have to repeat the same code across several tests methods just to assert some outcome, based on a situation where only one, or a small number of field from the same object change.
That is precisely the problem solved by parameterized tests.
Let’s create a small, but illustrative problem and see how we can benefit from a parameterized unit test.
This is not a post about TDD, so if you are a unit test purist, please accept my apologies for the “code first” approach and for the lack of null checks.
We have a Purchase
1 |
|
The purchase can have 4 different statuses that are enumerated
1 | public enum PurchaseStatus { |
We need to process the purchases. For the purpose we have a purchaseJob that will do the work.
As long as the purchase has a COMPLETED
status it will be processed and for all the other statuses it will not. In our example, we just return true or false, depending on the Purchase status.
1 | public class PurchaseJob { |
So, what can we see from the code ?
A purchase can arrive to the method process() with any of the 4 possible status.
Depending on that status things will happen (although here as example, the only thing happening is returning a boolean).
We easily realize that if we want to test all the 4 possibilities we will have to write 4 test methods, however the only thing that changes is the status field of the purchase.
There is a better way, and lets go directly to it.
We start by creating a pretty standard unit test to deal with the positive outcome
1 | public class PurchaseJobProcessPositiveTest { |
If we where to create methods to test all the 4 possibilities, then we would need to write 3 additional @test methods, where the only difference amongst them would be the status of the Purchase Object.
Imagine if we had 10 different statuses, or even if the process() method received 2 parameters, each with a couple of different possible status.
When we encounter this kind of situations, we can take advantage of parameterized tests.
We will do so to handle the negative outcomes.
1 | (Parameterized.class) |
Let’s dissect what we have.
We start by annotating the class with @RunWith(Parameterized.class)
to ensure that JUnit will invoke the referenced class to run the tests instead of the default runner built into JUnit (see JUnit-runWith-JavaDoc)
The second step is to create a public static method annotated with @Parameterized.Parameters
that returns a Collection of object arrays. I’ve used the Iterable interface which is the root interface in the collection hierarchy, but we could have chosen to return a List <Purchase>
and, in our case, it would work without any problems.
The objects that are part of the collection returned from this dataProvider() method are the ones that will be supplied to all the test methods in the class.
Although not practical, in JUnit4 that’s how it works. The test methods will run as many times as the number of objects present in the collection. In other words, the runner will iterate trough each @test method as many times as the number of objects in the collection, and in every iteration one object from that collection will be supplied.
Bear in mind that, had we included the positive test method in this class, the test would continue to pass, as we are forcing the purchase to be in a COMPLETE
status manually, however this positive test method would run 3 times, which is a completely waste of time and resources.
The final step is the not mandatory @Parameterized.Parameter(0)
. The advantage of this annotation is that it will handle the injection of the parameter for us, therefore we do not need a constructor for the test class. If we choose not to use this annotation, then we will have to provide a constructor for the test class.
Note the number zero in the annotation. It is also not mandatory as it always defaults to zero and if you just type @Parameterized.Parameter
, in this case it will work without any problem.
I prefer to use it (although some IDE’s might complaint), to be explicit that is the object present in the first array column. This means, obviously that we can have more than one parameter being supplied, and we will annotate their respective fields with @Parameterized.Parameter(1)
, @Parameterized.Parameter(2)
, and so forth, according to the column they live in the array.
pros :
- We only have one test method that covers 3 possible status.
- If one day we need to add a new status, we just need to add the purchase to the data() method.
- It’s small, clean and readable.
- Executes faster than 3 methods, due to the way JUnit handles the work internally.
and now for the cons :
- We should have one parameterized test class per method of the UUT (unit under test). (JUnit 5 solves this).
- We can achieve the same results with less code by adding JUnitParams library to our projects.
I hope this little example is useful, and drives your curiosity to explore more about JUnit.
An example for the code in this post can be found in git-hub
Happy coding :)