2522. Dynamic Tests with JUnit 5 in EclipseJUnit 5 and Maven
Introduce how to create and run JUnit 5 tests in Eclipse.
1. Overview
1.1 JUnit 5
JUnit is one of the most popular unit-testing frameworks in the Java ecosystem. Although the current stable version is JUnit 4.12, a 5.1.0 version contains a number of exciting innovations, with the goal to support new features in Java 8 and above, as well as enabling many different styles of testing. JUnit is an open source project hosted at Github. JUnit 5 requires Java 8
. However, we can still test code that has been compiled with previous versions of the JDK.
1.2 JUnit 5 Architecture
JUnit 5 = JUnit Platform + JUnit Jupiter + JUnit Vintage.
JUnit Platform
- To be able to launch junit tests, IDEs, build tools or plugins need to include and extend platform APIs. It defines the TestEngine API for developing new testing frameworks that runs on the platform. It also provides a Console Launcher to launch the platform from the command line and build plugins for Gradle and Maven.JUnit Jupiter
- It includes new programming and extension models for writing tests. It has all new junit annotations and TestEngine implementation to run tests written with these annotations.JUnit Vintage
- It primary purpose is to support running JUnit 3 and JUnit 4 written tests on the JUnit 5 platform. It’s there are backward compatibility.
1.3 JUnit 5 Dynamic Tests
In JUnit 5, dynamic test cases are represented by DynamicTest class. Here are some essential points:
- Dynamic tests can be generated by a factory method annotated with
@TestFactory
. It’s a new annotation of JUnit5. - @TestFactory method must return a
Stream
,Collection
,Iterable
, orIterator
of DynamicTest instances. - @TestFactory methods must not be private or static, and may optionally declare parameters to be resolved by ParameterResolvers.
2. Setting up Maven Project
2.1 Eclipse IDE
Go to https://www.eclipse.org/, download and install or upgrade Eclipse to Oxygen.1a Release (4.7.1a) or later version.
2.2 Maven Project
In Eclipse, create new Maven project. Specify the location of your new project. Do not select ‘Create a simple project’ option. Press next, filter for the ‘quickstart’ archetype and select the maven-archetype-quickstart entry. This is the classical Maven example archetype for project creation. Press next, specify Group Id, Artifact Id, Package, etc, then click Finish.
2.3 Dependencies in pom.xml
Edit pom.xml
, add properties, build and dependencies for jupiter, vintage and platform.
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>johnny.tutorial</groupId>
<artifactId>JUnitDynamicTest</artifactId>
<version>0.0.1-SNAPSHOT</version>
<packaging>jar</packaging>
<name>JUnitDynamicTest</name>
<url>https://jojozhuang.github.io</url>
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<maven.compiler.source>1.8</maven.compiler.source>
<maven.compiler.target>1.8</maven.compiler.target>
<junit.jupiter.version>5.1.1</junit.jupiter.version>
<junit.vintage.version>5.1.1</junit.vintage.version>
<junit.platform.version>1.1.1</junit.platform.version>
</properties>
<build>
<plugins>
<plugin>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.1</version>
</plugin>
<plugin>
<artifactId>maven-surefire-plugin</artifactId>
<version>2.19</version>
<dependencies>
<dependency>
<groupId>org.junit.platform</groupId>
<artifactId>junit-platform-surefire-provider</artifactId>
<version>${junit.platform.version}</version>
</dependency>
</dependencies>
</plugin>
</plugins>
</build>
<dependencies>
<dependency>
<groupId>org.junit.platform</groupId>
<artifactId>junit-platform-runner</artifactId>
<version>${junit.platform.version}</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter-engine</artifactId>
<version>${junit.jupiter.version}</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.junit.vintage</groupId>
<artifactId>junit-vintage-engine</artifactId>
<version>${junit.vintage.version}</version>
<scope>test</scope>
</dependency>
</dependencies>
</project>
2.4 Java Files
Create a class named ‘Solution’ in the ‘johnny.tutorial.JUnitDynamicTest’ package with the following content. This is the solution for the first algorithm question in LeetCode.
package johnny.tutorial.JUnitDynamicTest;
import java.util.HashMap;
/*
Two Sum
Given an array of integers, return indices of the two numbers such that they add up to a specific target.
You may assume that each input would have exactly one solution, and you may not use the same element twice.
Example:
Given nums = [2, 7, 11, 15], target = 9,
Because nums[0] + nums[1] = 2 + 7 = 9,
return [0, 1].
*/
public class Solution {
public int[] twoSum(int[] nums, int target) {
int[] res = new int[]{0,0};
if (nums == null || nums.length < 2) {
return res;
}
HashMap<Integer, Integer> map = new HashMap<Integer, Integer>();
for (int i = 0; i < nums.length; i++) {
if(map.containsKey(nums[i])) {
res[0] = map.get(nums[i]);
res[1] = i;
return res;
} else {
map.put(target - nums[i], i);
}
}
return res;
}
}
Create JUnit class based on JUnit 4 to test the ‘towSum’ method. We define three test methods here.
- testInvalidInput()
- testSmallInput()
- testLargeInput()
package johnny.tutorial.JUnitDynamicTest;
import static org.junit.Assert.*;
import org.junit.Test;
public class SolutionTest {
Solution solution = new Solution();
@Test
public void testInvalidInput() {
System.out.println("testInvalidInput");
assertArrayEquals(new int[2], solution.twoSum(null, 0));
assertArrayEquals(new int[2], solution.twoSum(new int[] {}, 0));
}
@Test
public void testSmallInput() {
System.out.println("testSmallInput");
assertArrayEquals(new int[] {1, 2}, solution.twoSum(new int[] { 1, 0, -1 }, -1));
assertArrayEquals(new int[] {8, 9}, solution.twoSum(new int[]{1,2,3,4,5,6,7,8,9,10}, 19));
}
@Test
public void testLargeInput() {
System.out.println("testLargeInput");
int[] numbers3 = {230,863,916,585,981,404,316,785,88,12,70,435,384,778,887,755,740,337,86,92,325,422,815,650,920,125,277,336,221,847,168,23,677,61,400,136,874,363,394,199,863,997,794,587,124,321,212,957,764,173,314,422,927,783,930,282,306,506,44,926,691,568,68,730,933,737,531,180,414,751,28,546,60,371,493,370,527,387,43,541,13,457,328,227,652,365,430,803,59,858,538,427,583,368,375,173,809,896,370,789};
assertArrayEquals(new int[]{28, 45}, solution.twoSum(numbers3, 542));
}
}
2.5 Dynamic Test
Now, let’s create dynamic test with JUnit Jupiter based on JUnit5. Create test file SolutionDynamicTest.java. We define one test method here. It will load the test cases from file ‘testcase.txt’ and generate test methods dynamically.
- testTwoSum()
package johnny.tutorial.JUnitDynamicTest;
import static org.junit.jupiter.api.Assertions.*;
import java.io.BufferedReader;
import java.io.FileReader;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.DynamicTest;
import org.junit.jupiter.api.TestFactory;
import org.junit.jupiter.api.function.Executable;
public class SolutionDynamicTest {
private Solution solution;
@BeforeEach
public void setUp() {
solution = new Solution();
}
@TestFactory
public Collection<DynamicTest> testTwoSum() {
Collection<DynamicTest> dynamicTests = new ArrayList<>();
try {
BufferedReader br = new BufferedReader(new FileReader("testcase.txt"));
try {
String line;
while ((line = br.readLine()) != null) {
int[] nums = ParserUtil.stringToIntegerArray(line);
line = br.readLine();
int target = Integer.parseInt(line);
line = br.readLine();
int[] expected = ParserUtil.stringToIntegerArray(line);
// create an test execution
int[] ret = solution.twoSum(nums, target);
Executable exec = () -> assertArrayEquals(expected, ret);
// create a test display name
String testCase = "Test Two Sum: Input: " + Arrays.toString(nums) + ", " + target + "; Your answer:" + Arrays.toString(ret) + "; Expected answer: " + Arrays.toString(expected);
// create dynamic test
System.out.println(testCase);
DynamicTest dTest = DynamicTest.dynamicTest(testCase, exec);
// add the dynamic test to collection
dynamicTests.add(dTest);
}
}
catch (Exception io) {
System.out.println(io.getMessage());
}
finally {
br.close();
}
} catch (IOException ioe) {
System.out.println(ioe.getMessage());
} finally {
}
return dynamicTests;
}
}
Create utility class ParserUtil. It helps to convert string to int array and vice versa.
package johnny.tutorial.JUnitDynamicTest;
public class ParserUtil {
public static int[] stringToIntegerArray(String input) {
// null
if (input.equals("null")) {
return null;
}
// empty array
if (input.equals("[]")) {
return new int[]{};
}
input = input.trim();
input = input.substring(1, input.length() - 1);
if (input.length() == 0) {
return new int[0];
}
String[] parts = input.split(",");
int[] output = new int[parts.length];
for(int index = 0; index < parts.length; index++) {
String part = parts[index].trim();
output[index] = Integer.parseInt(part);
}
return output;
}
public static String integerArrayToString(int[] nums, int length) {
if (length == 0) {
return "[]";
}
String result = "";
for(int index = 0; index < length; index++) {
int number = nums[index];
result += Integer.toString(number) + ", ";
}
return "[" + result.substring(0, result.length() - 2) + "]";
}
public static String integerArrayToString(int[] nums) {
return integerArrayToString(nums, nums.length);
}
}
Create text file named testcase.txt
with the following content. Each 3 lines represent as one test case. The first line is the nums
array, the second line is the target
, and the third line is the expected array needs to be returned after calling Solution.twoSum() method. There are totally 5 test cases defined in this file.
null
0
[0,0]
[]
0
[0,0]
[1,0,-1]
-1
[1,2]
[1,2,3,4,5,6,7,8,9,10]
19
[8, 9]
[230,863,916,585,981,404,316,785,88,12,70,435,384,778,887,755,740,337,86,92,325,422,815,650,920,125,277,336,221,847,168,23,677,61,400,136,874,363,394,199,863,997,794,587,124,321,212,957,764,173,314,422,927,783,930,282,306,506,44,926,691,568,68,730,933,737,531,180,414,751,28,546,60,371,493,370,527,387,43,541,13,457,328,227,652,365,430,803,59,858,538,427,583,368,375,173,809,896,370,789]
542
[28, 45]
3. Testing
3.1 Running JUnit4 Test
In Eclipse, select the SolutionTest
class, right-click on it and select Run-as -> JUnit Test. You should see all the three methods created based on JUnit4 passed the testing.
3.2 Running JUnit5 Test
In Eclipse, select the SolutionDynamicTest
class, right-click on it and select Run-as -> JUnit Test. You should see 5 test methods passed the testing.