JUnitParams - parameterized unit tests

Less unit test code doesn't necessarily result into poor coverage. I've recently entered the paradigm of parameterized tests when refactoring a larger test suite. I ended up writing significantly less code without compromising on the extent. In fact, adding a few corner cases was almost a no-brainer. In this post I'd like to share my experience with JUnitParams and discuss advantages of parameterized unit tests.

Suppose I have just implemented a few of elementary sorting algorithms (Insertion, Quick and Selection Sort in this case) and can't wait to test it.
 public interface ISort {  
  // Identifies the algorithm  
  String getName();  
  // Sorts the given numeric sequence  
  Result sort(int... x);  
 }  
 // Implementation - details are not important just now  
 public class InsertSort implements ISort {..}  
 public class QuickSort implements ISort {..}  
 public class SelectSort implements ISort {..}  
Not only do I want to check if the given numeric sequence gets sorted, but I am also interested to see that the sorting was done in sync with the expected algorithm. For that reason I keep track of individual iterations so that they can be checked later on.
 public class Result {  
  private String algorithm;  
  private List<int[]> steps = new ArrayList<int[]>();  
  ..  
  @Override  
  public String toString() {  
   // pretty-prints the sorting result, includes individual steps    
  }  
 }  
After a while I come up with fairly decent test code. I even have a customized builder, just to keep things clean and tidy.
 public class StandardSortTest {  
   private InsertSort insertSort = new InsertSort();  
   ..  
   @Test  
   public void insertSortNull() {  
     assertResult(insertSort.sort(null),   
      new ResultBuilder(insertSort.getName()).build());  
   }  
   @Test  
   public void insertSortEmpty() {  
     assertResult(insertSort.sort(new int[] {}),  
      new ResultBuilder(insertSort.getName()).build());  
   }  
   @Test  
   public void insertSort() {  
     assertResult(insertSort.sort(input),  
         new ResultBuilder(insertSort.getName())  
           .addStep(3, 7, 4, 9, 5, 2, 6, 1)  
           .addStep(3, 7, 4, 9, 5, 2, 6, 1)  
           .addStep(3, 4, 7, 9, 5, 2, 6, 1)  
           .addStep(3, 4, 7, 9, 5, 2, 6, 1)  
           .addStep(3, 4, 5, 7, 9, 2, 6, 1)  
           .addStep(2, 3, 4, 5, 7, 9, 6, 1)  
           .addStep(2, 3, 4, 5, 6, 7, 9, 1)  
           .addStep(1, 2, 3, 4, 5, 6, 7, 9)  
           .build()  
     );  
   }  
   // etc, similar for the other 2 sorting algorithms  
 }  
Despite all the efforts however, I couldn't entirely remove a certain amount of repetitive boilerplate. My test methods might be one-liners, but clearly they only differ in subtle details.

Let's take look how the situation changes when I arrive at the conclusion to parameterize my tests.
 import junitparams.JUnitParamsRunner;  
 import junitparams.Parameters;  
 import org.junit.runner.RunWith;  
 ..  
 @RunWith(JUnitParamsRunner.class)  
 public class ParameterizedSortTest {  
   @Test  
   @Parameters  
   public void sort(ISort sortAlg, int[] toSort, Result expectedResult) {  
    Result result = sortAlg.sort(toSort);  
    Assert.assertEquals(result.toString(), expectedResult.toString());  
   }  
   // Provides parameters  
   // see: https://code.google.com/p/junitparams  
   private Object[] parametersForSort() {..}  
 }  
Suddenly, the actual functional code shrinks to a single method. That's for the boiler plate, though.. Isn't it somewhat too vague and generic now? Test report matters after all, and with many test methods in place it was easy to tell what implementation aspect didn't live up to the expectations: 
Someone forgot to check those unwieldy null values

Well, let's see what the report would look like in case of the parameterized tests comprising a single test method:
Hover over effect reveals parameter values (captured in IntelliJ IDEA 12)

As you can see, the results are determined by the sets of passed parameters and not by the number of written test methods. I find it highly useful as it allows for data-driven tests with a minimal functional overhead.

Thanks for reading up to this point and I hope you feel inspired. Please find a complete example under the link below. The example demoes an additional feature - test data providers - which allows to keep data sets separate from the actual test code.

Source Code