JUnit 5是适用于Java的下一代单元测试框架,具有许多有趣的功能,包括嵌套测试,参数化测试,新的扩展API或Java 8支持。
本文展示了JUnit 5的基本概念,包括测试生命周期,参数注入和断言(基本,超时和异常)。
文献资料
首先,我认为JUnit 5文档很棒。 它不仅包含全面的框架文档,还包含许多示例,包括许多示例。 学习JUnit 5时请不要错过文档: http : //junit.org/junit5/docs/current/user-guide/
依存关系
首先,JUnit 5需要Java 8才能运行。 最后。 这带来了在测试中使用Lambda表达式的可能性,并使它们更加简洁(Lambda表达式主要用于断言中)。 其次,JUnit 5由按JUnit平台,JUnit Jupiter和JUnit Vintage分组的多个工件组成。 这听起来可能很吓人,但是如今使用Maven或Gradle之类的工具根本不是问题,要开始使用,您实际上只需要一个依赖项。 Gradle的基本配置如下所示:
buildscript {
ext {
junitPlatformVersion = '1.0.1'
junitJupiterVersion = '5.0.1'
}
repositories {
mavenCentral()
}
dependencies {
classpath "org.junit.platform:junit-platform-gradle-plugin:${junitPlatformVersion}"
}
}
apply plugin: 'java'
apply plugin: 'org.junit.platform.gradle.plugin'
sourceCompatibility = 1.8
repositories {
mavenCentral()
}
dependencies {
testRuntime("org.junit.jupiter:junit-jupiter-engine:${junitJupiterVersion}")
}
task wrapper(type: Wrapper) {
gradleVersion = '4.1'
}
JUnit 5测试类和方法
在测试类(从org.junit.jupiter.api
导入)中使用的常见测试注释是:
-
@BeforeAll
–在测试类中的所有方法之前执行 -
@BeforeEach
–在测试类中的每个测试方法之前执行 -
@Test
–实际测试方法 -
@AfterEach
–在测试环境中的每个测试方法之后执行 -
@AfterAll
–在测试过程中的所有方法之后执行
其他基本但有用的注释:
-
@DisplayName
–测试类或方法的自定义显示名称 -
@Disabled
–禁用测试类或方法 -
@RepeatedTest
–用测试方法制作测试模板 -
@Tag
–标记测试类或方法以进一步选择测试
一个基本的例子:
import org.junit.jupiter.api.*;
@DisplayName("JUnit5 - Test basics")
class JUnit5Basics {
@BeforeAll
static void beforeAll() {
System.out.println("Before all tests (once)");
}
@BeforeEach
void beforeEach() {
System.out.println("Runs before each test");
}
@Test
void standardTest() {
System.out.println("Test is running");
}
@DisplayName("My #2 JUnit5 test")
@Test
void testWithCustomDisplayName() {
System.out.println("Test is running");
}
@DisplayName("Tagged JUnit5 test ")
@Tag("cool")
@Test
void tagged() {
System.out.println("Test is running");
}
@Disabled("Failing due to unknown reason")
@DisplayName("Disabled test")
@Test
void disabledTest() {
System.out.println("Disabled, will not show up");
}
@DisplayName("Repeated test")
@RepeatedTest(value = 2, name = "#{currentRepetition} of {totalRepetitions}")
void repeatedTestWithRepetitionInfo() {
System.out.println("Repeated test");
}
@AfterEach
void afterEach() {
System.out.println("Runs after each test");
}
}
注意,测试类和方法不必是公共的 ,它们可以是包私有的 。
测试执行生命周期
在JUnit 5中,默认情况下会为测试类中的每个测试方法创建一个新的测试实例。 可以使用类级别@TestInstance
注释来调整此行为:
import org.junit.jupiter.api.*;
@TestInstance(TestInstance.Lifecycle.PER_CLASS)
@DisplayName("JUnit5 - Test lifecycle adjustments")
class JUnit5PerClassLifecycle {
private Object first = new Object();
private Object second;
@BeforeAll
void beforeAll() {
this.second = this.first;
System.out.println("Non static before all.");
}
@BeforeEach
void beforeEach() {
Assertions.assertEquals(first, second);
}
@Test
void first() {
Assertions.assertEquals(first, second);
}
@Test
void second() {
Assertions.assertEquals(first, second);
}
@AfterAll
void afterAll() {
System.out.println("Non static after all.");
}
@AfterEach
void afterEach() {
Assertions.assertEquals(first, second);
}
}
在PER_CLASS
模式下,将为所有测试创建单个测试实例,并且@BeforeAll
和@AfterAll
方法不再需要是静态的。
参数解析
测试和回调方法现在可以采用org.junit.jupiter.api.TestInfo
, org.junit.jupiter.api.RepetitionInfo
或org.junit.jupiter.api.TestReporter
类的参数。
另外,由于使用了非常简单但功能强大的JUnit 5扩展API,因此在方法中解析自定义参数只需提供自己的org.junit.jupiter.api.extension.ParameterResolver
实现即可。
class JUnit5BuiltInParameterResolution {
@BeforeAll
static void beforeAll(TestInfo testInfo) {
System.out.println("Before all can take parameters. Started: " + testInfo.getDisplayName());
}
@BeforeAll
static void beforeAll(TestReporter testReporter) {
testReporter.publishEntry("myEntry", "myValue");
}
@BeforeAll
static void beforeAll(TestInfo testInfo, TestReporter testReporter) {
testReporter.publishEntry("myOtherEntry", testInfo.getDisplayName());
}
@BeforeEach
void beforeEach(TestInfo testInfo) {
}
@Test
void standardTest(TestInfo testInfo) {
}
@DisplayName("Repeated test")
@RepeatedTest(value = 2, name = "#{currentRepetition} of {totalRepetitions}")
void repeatedTest(RepetitionInfo repetitionInfo) {
System.out.println("Repeated test - " + repetitionInfo.toString());
}
@AfterAll
static void afterAll() {
}
@AfterAll
static void afterAll(TestInfo testInfo) {
}
@AfterEach
void afterEach() {
}
}
断言
JUnit 5带有许多标准断言,可以在org.junit.jupiter.api.Assertions
类中找到。
基本断言
基本主张是: assertEquals
, assertArrayEquals
, assertSame
, assertNotSame
, assertTrue
, assertFalse
, assertNull
, assertNotNull
, assertLinesMatch
, assertIterablesMatch
例:
@Test
void basicAssertions() {
// arrange
List<String> owners = Lists.newArrayList("Betty Davis", "Eduardo Rodriquez");
// assert
assertNotNull(owners);
assertSame(owners, owners);
assertFalse(owners::isEmpty); // Lambda expression
assertEquals(2, owners.size(), "Found owner names size is incorrect");
assertLinesMatch(newArrayList("Betty Davis", "Eduardo Rodriquez"), owners);
assertArrayEquals(
new String[]{"Betty Davis", "Eduardo Rodriquez"},
owners.toArray(new String[0])
);
}
断言所有
Assertions.assertAll
断言所有提供的可执行文件均不会引发异常:
Assertions.assertAll(
() -> Assertions.assertNotNull(null, "May not be null"),
() -> Assertions.assertTrue(false, "Must be true")
);
上面将报告多个失败:
org.opentest4j.MultipleFailuresError: Multiple Failures (2 failures)
May not be null ==> expected: not <null>
Must be true
注意:您可能想阅读有关JUnit 4和AssertJ中的替代方法的信息-http: //blog.codeleak.pl/2015/09/assertjs-softassertions-do-we-need-them.html
超时断言
使用超时断言来验证未超过任务的执行时间。 超时断言有两种: assertTimeout
和assertTimeoutPreemptively
。 都拿两个
- 同步执行任务,等待任务完成,然后声明超时:
@Test
void assertTimeout() {
// arrange
Executable task = () -> Thread.sleep(1000);
// waits for the task to finish before failing the test
Assertions.assertTimeout(Duration.ofMillis(100), task::execute);
}
@Test
void assertTimeoutWithThrowingSupplier() {
// arrange
ThrowingSupplier<String> task = () -> "result";
// waits for the task to finish before failing the test
Assertions.assertTimeout(Duration.ofMillis(100), task::get);
}
- 异步执行任务(在新线程中),到超时时中止执行:
@Test
void assertTimeoutPreemptively() {
// arrange
Executable task = () -> Thread.sleep(1000);
// abort execution when timeout exceeded
Assertions.assertTimeoutPreemptively(Duration.ofMillis(100), task::execute);
}
@Test
void assertTimeoutPreemptivelyWithThrowingSupplier() {
// arrange
ThrowingSupplier<String> task = () -> "result";
// abort execution when timeout exceeded, return the result
String result = Assertions.assertTimeoutPreemptively(Duration.ofMillis(100), task::get);
Assertions.assertEquals("result", result);
}
异常断言
JUnit 5内置的assertThrows
获取预期的异常类型作为第一个参数,而可执行文件(功能接口)则可能将异常作为第二个参数。 如果未引发任何异常或其他类型的异常,则该方法将失败。 该方法返回异常本身,该异常可用于进一步的声明:
@Test
void assertException() {
// arrange
Executable throwingExecutable = () -> {
throw new RuntimeException("Unexpected error!");
};
// act and assert
RuntimeException thrown = Assertions.assertThrows(
RuntimeException.class, throwingExecutable::execute, "???"
);
Assertions.assertAll(
() -> Assertions.assertEquals("Unexpected error!", thrown.getMessage()),
() -> Assertions.assertNotNull(thrown.getCause())
);
}
注意:您可能想阅读有关JUnit 4中的替代方法的信息-http: //blog.codeleak.pl/2013/07/3-ways-of-handling-exceptions-in-junit.html
摘要
JUnit 5具有许多功能。 在本文中,仅演示了基础知识,但这足以让您开始编写第一个JUnit 5测试。
也可以看看
- 使用JUnit 5进行更清洁的参数化测试– http://blog.codeleak.pl/2017/06/cleaner-parameterized-tests-with-junit-5.html
翻译自: https://www.javacodegeeks.com/2017/10/junit-5-basics-2.html