I have used the Parameterized Junit test in places where you need to test a method with huge combinations of input. Imagine a truth table logic in place.
Form the truth table -> let's take 2 inputs for simplicity sake.
A
|
B
|
Expected
|
1
|
1
|
1
|
1
|
0
|
1
|
0
|
1
|
1
|
0
|
0
|
0
|
Say if the method is,
Method(param1, param2)
Now if I have to go with the conventional junit test cases, I would have to name my methods like,
- testMethodForParam1TrueParam2False()
- testMethodForParam1TrueParam2True()
- testMethodForParam1FalseParam2True()
- testMethodForParam1FalseParam2False()
Sure it looks readable now. But consider the same for a 3 X 3 matrix. It becomes ugly.
Wouldn't it better to actually have this truth table in the start of the test case and feed it to just one test?
This is exactly what Parameterized Junit solves.
There are various blogs and sources to refer the syntax of such test.
But when you want to use it in an integration test, you need to keep the following in mind.
- As long as your data is static, you have nothing to worry about. Say, your service which contains the method, is initialized in @BeforeClass and in your @Parameterized.Parameters the data you feed is coming from a static method in that service. Then you will get NPE. Because @Parameters is called before @BeforeClass. To tackle this, have your initialization in a simple static block. But then, you need to make the test class final. @BeforeClass will apply to all the inherited classes, static block will not.
package com.foo; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertNotNull; import java.util.Arrays; import java.util.Collections; import java.util.Date; import java.util.List; import java.util.Optional; import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; import org.junit.runners.Parameterized; @RunWith(Parameterized.class) public final class ServiceTest { private static Service service; private Integer param1; private Integer param2; private Integer expectedResult; private String caseName; static { setUpClass(); } public static void setUpClass() { service = (Service) CustomContextLoader.getBean("service"); } public ServiceTest(String caseName, Integer param1, Integer param2, Integer expectedResult) { this.caseName = caseName; this.param1 = param1; this.param2 = param2; this.expectedResult = expectedResult; } @Parameterized.Parameters public static Iterable<Object[]> testData() { return Arrays.asList(new Object[][]{ { "case 1 : 1 1 = 1", 1, 1, 1}, { "case 2 : 1 0 = 1", 1, 0, 1}, { "case 3 : 0 1 = 1", 0, 1, 1}, { "case 4 : 0 0 = 0", 0, 0, 0}, }); } @Before public void setup() { System.out.println("@Before"); } @Test public void testMethod() { Integer result = service.orMethod(this.param1, this.param2); assertEquals(this.caseName+" failed", this.expectedResult, result); } }
Remember the order of executionStatic blockFor my case, I had to bootstrap some data (think of inserting some rule and referencing that throughout your test case using an ID. In such case I need to insert the rule and get the ID before I can setup the data) before I can set test data. Hence it was important for the service to be initialized before it hit the @Parameters method. I could not use @BeforeClass because it gets executed after @Parameters. Hence I had to resort to static method.
@Parameter
@BeforeClass
@Before
@Test - Add a description for each test data you setup in the @Parameters. and add this to the assertion string. This will help identify which dataset is failing. Because, since this is parameterized test, the test results would be printed something like this:
com.foo.ServiceTest > testMethod[0] PASSED com.foo.ServiceTest > testMethod[1] PASSED com.foo.ServiceTest > testMethod[2] FAILED java.lang.AssertionError com.foo.ServiceTest > testMethod[3] PASSED
Now if you have setup a caseName with the testData, you will spend less time debugging the issue.com.foo.ServiceTest > testMethod[0] PASSED com.foo.ServiceTest > testMethod[1] PASSED com.foo.ServiceTest > testMethod[2] FAILED java.lang.AssertionError : case 3 : 0 1 = 1 failed com.foo.ServiceTest > testMethod[3] PASSED
Also note that the parameterized junit test runs like there are separate @Test methods for all of those data sets. Means it will continue running all the tests even if one fails. It is not fail-fast.
No comments:
Post a Comment