JUnit 5
最后修改于 2024 年 1 月 27 日
JUnit 5 教程展示了如何使用 JUnit 在 Java 中进行测试。
Java JUnit 5
JUnit 5 是 Java 中最流行的测试框架。 JUnit 5 由三个模块组成:JUnit Platform、JUnit Jupiter 和 JUnit Vintage。
JUnit Platform 是启动 JUnit 5 测试的基础。它提供了与各种 IDE 和构建工具的集成。 JUnit Jupiter 是编写测试的核心 API。 JUnit Vintage 用于向后兼容 JUnit 3 和 JUnit 4 测试。
JUnit 5 Maven 依赖项和插件
在本教程的示例中,我们使用以下 Maven 依赖项
<dependency> <groupId>org.junit.jupiter</groupId> <artifactId>junit-jupiter-engine</artifactId> <version>5.3.2</version> <scope>test</scope> </dependency> <dependency> <groupId>org.junit.jupiter</groupId> <artifactId>junit-jupiter-params</artifactId> <version>5.3.2</version> <scope>test</scope> </dependency>
我们使用 junit-jupiter-engine
和 junit-jupiter-params
依赖项。
<plugin> <artifactId>maven-surefire-plugin</artifactId> <version>3.0.0-M1</version> </plugin>
Surefire 插件在构建生命周期的测试阶段用于执行应用程序的单元测试。
我们可以使用 Maven 或 IDE 的工具来运行测试。 IDE 有快捷方式来为我们创建测试类;例如,IntelliJ IDEA 有 Ctrl + Shift + T 来为我们生成一个测试类。
JUnit 5 注解
JUnit 5 API 的核心是注解。 例如,@Test
注解用于标记测试方法。 @ParameterizedTest
表示一个方法是一个参数化测试。 @RepeatedTest
表示一个方法是一个重复测试的测试模板。
JUnit 5 @BeforeAll
@BeforeAll
注解表示带注解的方法应在当前类中的所有 @Test
、@RepeatedTest
、@ParameterizedTest
和 @TestFactory
方法之前执行。 它只执行一次。
package com.zetcode.utils; import java.util.List; import java.util.stream.Collectors; public class MathUtils { public static Integer sum(List<Integer> vals) { var sum = vals.stream().reduce(Integer::sum); return sum.get(); } public static List<Integer> positive(List<Integer> vals) { return vals.stream().filter(val -> val > 0).collect(Collectors.toList()); } public static List<Integer> negative(List<Integer> vals) { return vals.stream().filter(val -> val < 0).collect(Collectors.toList()); } }
我们有一个带有三个方法的 MathUtils
帮助类;我们为这个帮助类创建测试用例。
package com.zetcode.utils; import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; import java.util.List; import static org.junit.jupiter.api.Assertions.assertEquals; class MathUtilsTest { private static List<Integer> vals; @BeforeAll static void setup() { vals = List.of(2, 1, -2, 3, -1, 0, -1); } @Test @DisplayName("testing sum of values") void sumTest() { var sum = MathUtils.sum(vals); assertEquals(Integer.valueOf(2), sum); } @Test @DisplayName("should get positive values") void positiveTest() { var positiveValues = MathUtils.positive(vals); assertEquals(positiveValues, List.of(2, 1, 3)); } @Test @DisplayName("should get negative values") void negativeTest() { var negativeValues = MathUtils.negative(vals); assertEquals(negativeValues, List.of(-2, -1, -1)); } }
MathUtilsTest
是 MathUtils
帮助类的测试类。
@BeforeAll static void setup() { vals = List.of(2, 1, -2, 3, -1, 0, -1); }
在三个测试方法之前,执行 setup
方法。 它为我们的测试创建值列表。
@Test @DisplayName("testing sum of values") void sumTest() { var sum = MathUtils.sum(vals); assertEquals(Integer.valueOf(2), sum); }
在 sumTest
方法中,我们测试 MathUtils.sum
方法是否计算出正确的值。 使用 assertEquals
方法,我们断言计算出的值等于 2。 @DisplayName
注解在 IntelliJ IDEA 等 IDE 中使用,以便在其工具中为测试命名。
$ mvn test ... [INFO] [INFO] ------------------------------------------------------- [INFO] T E S T S [INFO] ------------------------------------------------------- [INFO] Running com.zetcode.utils.MathUtilsTest [INFO] Tests run: 3, Failures: 0, Errors: 0, Skipped: 0, Time elapsed: 0.07 s - in com.zetcode.utils.MathUtilsTest [INFO] [INFO] Results: [INFO] [INFO] Tests run: 3, Failures: 0, Errors: 0, Skipped: 0 [INFO] [INFO] ------------------------------------------------------------------------ [INFO] BUILD SUCCESS [INFO] ------------------------------------------------------------------------
这是测试的示例输出。
JUnit 5 @BeforeEach
@BeforeEach
注解表示带注解的方法应在当前类中的每个 @Test
、@RepeatedTest
、@ParameterizedTest
和 @TestFactory
方法之前执行。 它只执行一次。
package com.zetcode.bag; import java.util.ArrayList; import java.util.HashSet; import java.util.List; import java.util.Set; public class ColorBag { private Set<String> colors = new HashSet<>(); public void add(String color) { colors.add(color); } public void remove(String color) { colors.remove(color); } public List<String> toList() { return new ArrayList<>(colors); } public boolean contains(String color) { return colors.contains(color); } public int size() { return colors.size(); } }
我们有一个 ColorBag
要测试。
package com.zetcode.bag; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; import java.util.Arrays; import java.util.Collections; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertFalse; import static org.junit.jupiter.api.Assertions.assertTrue; class ColorBagTest { private ColorBag colorBag; @BeforeEach void setupEach() { colorBag = new ColorBag(); colorBag.add("red"); colorBag.add("green"); colorBag.add("yellow"); colorBag.add("blue"); colorBag.add("magenta"); colorBag.add("brown"); } @Test @DisplayName("added color value should be in bag") void add() { var newColor = "pink"; colorBag.add(newColor); assertTrue(colorBag.contains(newColor), "failure - added colour not it the bag"); } @Test @DisplayName("removed color should not be in bag") void remove() { var color = "green"; colorBag.remove(color); assertFalse(colorBag.contains(color), "failure - removed color still in bag"); } @Test @DisplayName("color bag should be transformed into List") void toList() { var myList = Arrays.asList("red","green", "yellow", "blue", "magenta", "brown"); var colorList = colorBag.toList(); Collections.sort(myList); Collections.sort(colorList); assertEquals(colorList, myList, "failure - color bag not transformed into List"); } @Test @DisplayName("size of a color bag should match") void size() { int bagSize = colorBag.size(); assertEquals(6, bagSize, "failure - size of bag does not match"); } }
带有 @BeforeEach
注解的方法将在每个方法之前运行。 对于不同的上下文,我们使用不同的断言:assertEquals
、assertTrue
和 assertFalse
。
@BeforeEach void setupEach() { colorBag = new ColorBag(); colorBag.add("red"); colorBag.add("green"); colorBag.add("yellow"); colorBag.add("blue"); colorBag.add("magenta"); colorBag.add("brown"); }
由于我们在测试方法中修改了 ColorBag
对象,因此我们在每个方法之前创建一个新的 ColorBag
。
JUnit 5 @ParameterizedTest
参数化测试是使用 @ParameterizedTest
创建的。
package com.zetcode.utils; public class StringUtils { public static boolean isPalindrome(String text) { var cleaned = text.replaceAll("\\s+", "").toLowerCase(); var plain = new StringBuilder(cleaned); var reversed = plain.reverse().toString(); return reversed.equals(cleaned); } }
我们有 StringUtils
的 isPalindrome
方法要测试。
package com.zetcode.utils; import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.ValueSource; import static org.junit.jupiter.api.Assertions.assertTrue; class StringUtilsTest { @ParameterizedTest @ValueSource(strings = { "racecar", "radar", "level", "refer", "deified", "civic" }) void isPalindrome(String word) { assertTrue(StringUtils.isPalindrome(word)); } }
@ValueSource
注解指定一个字符串数组作为测试方法的参数源。
$ mvn test ... [INFO] ------------------------------------------------------- [INFO] T E S T S [INFO] ------------------------------------------------------- [INFO] Running com.zetcode.utils.StringUtilsTest [INFO] Tests run: 6, Failures: 0, Errors: 0, Skipped: 0, Time elapsed: 0.107 s - in com.zetcode.utils.StringUtilsTest [INFO] [INFO] Results: [INFO] [INFO] Tests run: 6, Failures: 0, Errors: 0, Skipped: 0 [INFO]
Maven 报告六个测试运行。 测试方法针对源中的每个单词运行。
JUnit 5 @RepeatedTest
重复测试使用 @RepeatedTest
注解运行。 我们指定所需的重复总数。
package com.zetcode.sort; public class MySelectionSort { public static int[] doSort(int[] arr){ for (int i = 0; i < arr.length; i++) { int idx = i; for (int j = i + 1; j < arr.length; j++) { if (arr[j] < arr[idx]) { idx = j; } } int smallerNumber = arr[idx]; arr[idx] = arr[i]; arr[i] = smallerNumber; } return arr; } }
我们有一个选择排序算法要测试。
package com.zetcode.sort; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.RepeatedTest; import org.junit.jupiter.api.RepetitionInfo; import java.util.Arrays; import java.util.Random; import static org.junit.jupiter.api.Assertions.assertEquals; class MySelectionSortTest { private final int N = 10; private int[] vals = new int[N]; @BeforeEach void beforeEach(RepetitionInfo info) { System.out.printf("Test #%d%n", info.getCurrentRepetition()); var r = new Random(System.nanoTime()); for (int i=0; i < N; i++) { vals[i] = r.nextInt(100); } } @RepeatedTest(value = 40, name = "#{displayName} {currentRepetition}/{totalRepetitions}") @DisplayName("should sort values") void doSort() { System.out.println(Arrays.toString(vals)); var sorted = MySelectionSort.doSort(vals); Arrays.sort(vals); System.out.println(Arrays.toString(sorted)); assertEquals(sorted, vals); } }
我们正在运行四十次测试。
@BeforeEach void beforeEach(RepetitionInfo info) { System.out.printf("Test #%d%n", info.getCurrentRepetition()); var r = new Random(System.nanoTime()); for (int i=0; i < N; i++) { vals[i] = r.nextInt(100); } }
在每次测试运行之前,我们随机创建四十个值并将它们放入数组中。
@RepeatedTest(value = 40, name = "#{displayName} {currentRepetition}/{totalRepetitions}") @DisplayName("should sort values") void doSort() { System.out.println(Arrays.toString(vals)); var sorted = MySelectionSort.doSort(vals); Arrays.sort(vals); System.out.println(Arrays.toString(sorted)); assertEquals(sorted, vals); }
在测试方法中,我们将我们的排序方法的结果与 Arrays.sort
中的现有算法进行比较。 @RepeatedTest
注解有三个占位符:{displayName}
是方法的显示名称; {currentRepetition}
是当前重复计数; {totalRepetitions}
是重复的总数。
来源
在本文中,我们介绍了 JUnit 5 测试框架的基础知识。
作者
列出所有Java教程。