When writing unit tests, it is common to initialize the input parameters of the method and the expected results in the test method itself. In some cases, it is sufficient to use a small set of inputs; however, there are cases where you need to use a large set of values to check all the features in our code. Parameterized tests are a good way to define and run multiple test cases, where the only difference is the data. They can validate code behavior for a variety of values, including border cases. Parameterization tests can increase the code coverage and ensure that the code works as expected.
There are a number of good parameterization frameworks for Java. In this article, we will examine three different frameworks commonly used with JUnit tests, with a comparison between them and examples of how the tests are structured for each. Finally, we will explore how to simplify and accelerate the creation of parameterized tests.
Parameterized JUnit test framework
Compare the 3 most common frameworks: JUnit 4, JunitParams and JUnit 5. Each JUnit parameterization framework has its strengths and weaknesses.
- This is the parameterization framework integrated into JUnit 4, so it does not require additional external dependencies.
- It supports previous versions of Java (JDK 7 and earlier).
- The test classes use fields and constructors to define the parameters, which make the tests more detailed.
- Requires a separate test class for each tested method.
- Simplify parameter syntax by allowing parameters to be passed directly to a test method.
- Allows multiple test methods (each with its own data) per test class.
- Supports CSV data sources and annotation-based values (no method required).
- Requires that the project be configured with the JunitParams dependency.
- During testing and debugging, all tests within the class must be performed – it is not possible to run a single test method within a test class.
- This parameterization framework is integrated into JUnit 5 and improves what was included with JUnit 4.
- It has a simplified parameter syntax like JunitParams.
- Supports multiple types of dataset sources, including CSVs and annotations (no method required).
- Even if no additional dependencies are required, more than one .jar is required.
- Requires Java 8 and newer versions of the build system (Gradle 4.6 or Maven Surefire 2.21).
- It may not yet be supported in your IDE (at the time of writing this document, only Eclipse and IntelliJ support JUnit 5).
For example, suppose we have a method that processes loan requests for a bank. We could write a unit test that specifies the amount of the loan request, the amount of down payment and other values. We will then create assertions that validate the answer: the loan can be approved or rejected and the answer can specify the terms of the loan.
First, let's take a look at a regular test for the method above:
In this example, we are testing our method by requesting a loan of $ 1000, with a down payment of $ 200 and indicating that the applicant has $ 250 in available funds. The test then validates that the loan was approved and did not provide a message in the response.
To be sure ours requestLoan () the method has been thoroughly tested, we need to test a series of down payments, requested loan amounts and available funds. For example, we test a $ 1 million loan request with a down payment, which should be rejected. We could simply duplicate the existing test with different values, but because the test logic would be the same, it is more efficient to parameterize the test.
We will adjust the amount of the loan requested, the down payment and the available funds, as well as the expected results: if the loan has been approved and the message returned after the validation. Each data set of the request, along with the expected results, will become your own test case.
An example of a parameterized test using JUnit 4 parameterized
Let's start with a parameterized example of Junit 4. To create a parameterized test, we must first define the variables for the test. We must also include a constructor to initialize them:
Here, we see that the test uses the @Corri with annotation to specify that the test will be performed with the parameterized Junit4 runner. This runner knows to look for a method that will provide the set of values for the test (annotated with parameters @), initialize the test correctly and run the tests with multiple lines.
Note that each parameter is defined as a field in the test class and the constructor initializes these values (it is also possible to inject values into fields using the @Parameter annotation if you do not want to create a constructor). For each row of the value set, the parameterized the runner instantiates the test class and runs each test in the class.
We add a method that provides the parameters to the parameterized runner:
Value sets are constructed as a List of Object matrices of data() method, which is noted with parameters @. Note that parameters @ set the test name using placeholders, which will be replaced during the test run. This makes it easier to see the values in the test results, as we will see later. Currently, there is only one row of data, which verifies a case in which the loan must be approved. We can add more lines to increase coverage of the method under test.
Here, we have a trial case in which the loan would be approved and two cases in which it should not be approved for different reasons. We may want to add rows in which zero or negative values are used, as well as test boundary conditions.
Now we are ready to create the test method:
Here, we refer to the fields when we invoke the requestLoan () method and validate the results.
Example of JUnitParams
The JunitParams library simplifies parameterized test syntax by allowing parameters to be passed directly to the test method. Parameter values are provided by a separate method whose name is referenced in parameters @ annotation.
JunitParams has the additional advantage that it supports the use of CSV files to provide values in addition to providing values in the code. This allows the test to be separated from the data and data values to be updated without updating the code.
JUnit 5 Example
JUnit 5 addresses some of the limitations and limitations of JUnit 4. Like JunitParams, Junit 5 also simplifies the syntax of parameterized tests. The most important changes in the syntax are:
- The test method is noted with @ParameterizedTest instead of @Test
- The test method directly accepts parameters, instead of using fields and a constructor
- The @Corri with the annotation is no longer necessary
Defining the same example in Junit 5 would look like this:
Efficiently create parameterized tests
As you can imagine, writing the parametric test above can be a bit of work. For each parameterized test framework there is some boilerplate code that must be written correctly. It may be difficult to remember the correct structure and it takes time to write. To make this easier, you can use Parasoft Jtest to generate parameterized tests, automatically, like the ones described above. To do this, simply select the method for which you want to generate a test (in Eclipse or IntelliJ) and click on the Parameterized action in the Parasoft Jtest Unit Test Assistant view:
The test is generated, using predefined values and assertions. You can then configure the test with real input values and assertions and add multiple lines of data to the file data() method.
Performing the parameterized test
Parasoft Jtest can perform parametric tests directly in Eclipse and IntelliJ.
The JUnit view in Eclipse.
Note that the name of each test, as shown, includes the input values from the data set and the expected result values. This can make the debugging of the test much easier when it fails because the input parameters and expected outputs are shown for each case.
You can also use the Run all from Parasoft Jtest action:
The view of the flow shaft in Parasoft Jtest.
It analyzes the test flow and provides detailed information on the previous test session. This allows you to see what happened in the test without having to re-test with breakpoints or debugging statements. For example, you can see the parameterized values in the Variables view:
The variable view in Parasoft Jtest.
The three frameworks we have reviewed are excellent choices and work well. If you use JUnit 4, I prefer JunitParams to the built-in JUnit 4 parameter framework, thanks to the cleaner design of the test classes and the ability to define multiple test methods in the same class. However, if you use JUnit 5, I would recommend the integrated JUnit 5 framework as it addresses the shortcomings in JUnit 4 and does not require additional libraries. I also like to use the Parasoft Jtest unit test capabilities to make the creation, execution and debugging of parameterized tests more efficient.