Most of people understand the importance and benefits of unit tests and why you should have them in projects you work on. Also, most of people don't like to write unit tests in projects they work on. TDD people are on other side of specter of course, but from my experience they are minority in IT industry.
When it comes to me, well I am with most people :-). I know why having unit tests is good, and how it will improving quality of code and projects. I know why you should invest in them, however I am not super happy to write unit tests, that isn't the thing that keep me wake at night. I prefer much more to create cool software and solve complex problems, then writing unit tests. That is why I am always on lookout on things that can help me in getting more and better unit tests, with less work from my side ,since in the end you should have unit tests in your projects.
One of those things that can help you to write better unit test with less time spent is Junit Params.
Let's us imagine that we have simple class Person, which has first name and last name. Business requirement is that first and last name can't be null or empty strings.
We can end up with some class similar to this one
public class Person {
private final String firstName;
private final String lastName;
public Person(String first, String last) {
if(first == null || first.trim().isEmpty() || last == null || last.trim().isEmpty() ) {
throw new RuntimeException("bad input");
}
this.firstName = first;
this.lastName = last;
}
public String getFirstName() {
return firstName;
}
public String getLastName() {
return lastName;
}
}
Next step is to make sure that code works as intended and that we took into account all corner cases.
We would of course want to cover all that possibilities using unit tests, but if you think about it we will have a lot of repeated code where we test first name for all possibilities, and then last name, and then all combinations and so on if we want to cover all edge cases. We will end up with a lot more code in unit tests then code in our business logic for this simple class.
Let's us look how JUnit Params can help us with this.
In order to use Junit Params you just need to add dependency like for any other library
<dependency>
<groupId>pl.pragmatists</groupId>
<artifactId>JUnitParams</artifactId>
<version>1.1.1</version>
<scope>test</scope>
</dependency>
After this we can start using JUnit Params in our unit tests
Let us write now few unit test by using JUnit Params.
Create simple test class PersonTest and add @RunWith(JUnitParamsRunner.class)
@RunWith(JUnitParamsRunner.class)
public class PersonTest {
.......
}
Now we can write simple unit test to validate our class. Let us first check if all is good if we pass null as first name
@Test(expected = RuntimeException.class)
public void fistNameNull() {
Person p = new Person(null,"dummyLast");
}
Great thing about JUnit Params is that we can still write standard unit tests and combine them with JUnit Params.
In order to leverage JUnit params on this unit test I just need to add @Parameters with appropriate values, and then it would look something like this
@Test(expected = RuntimeException.class)
@Parameters({ ""," " ," ", " "})
public void fistNameParamaters(String param) {
Person p = new Person(param,"dummyLast");
}
with this code I made 4 unit tests, which validate my code for values ""," ", " " and " ".
This shows already how useful JUnit Params are since you write only one unit test and it will be executed for all different possibilities of your params.
What happens if we want to pass parameters for both input values, first name and last name. In that case we would do something like this
@Test(expected = RuntimeException.class)
@Parameters({ " " ," ", // firstName, lastName
" ","",
" ", " ",
" "," "})
public void fistNameLastNameParamaters(String first, String last) {
Person p = new Person(first, last);
}
since there are two input parameters, values provided will be split in pair of two and used as input.
As you can see providing input for multiple parameters is very easy, although in this way we need to provide all combinations that we want to test.
If we are honest although test above works fine for multiple input paramaters, it isn't very user friendly. Let us fix that in next example
@Test(expected = RuntimeException.class)
@Parameters({ " | " , " |", " | ", " | "})
public void fistNameLastNameParamaters2(String first, String last) {
Person p = new Person(first, last);
}
now it is much more clear which value will be used for which input parameter in every iteration.
So far all parameters were Strings. What if our input parameter isn't a String or something that is easily convert from/to String, for example null value.
In that case we can use named parameter
@Test(expected = RuntimeException.class)
@Parameters(named = "emptyStrings")
public void namedParamaters(String first, String last) {
Person p = new Person(first, last);
}
@NamedParameters("emptyStrings")
private Object[] emptyStrings() {
return new Object[]{
new Object[]{null, ""},
new Object[]{"", null},
new Object[]{" ", " "},
new Object[]{" ", " "}
};
}
Code examples mentioned in this post can be found on this url