🤗 ApiHug × {Postman|Swagger|Api...} = 快↑ 准√ 省↓
- GitHub - apihug/apihug.com: All abou the Apihug
- apihug.com: 有爱,有温度,有质量,有信任
- ApiHug - API design Copilot - IntelliJ IDEs Plugin | Marketplace
注解
JUnit5常用注:
- @Test :表示方法是测试方法。但是与JUnit4的@Test不同,他的职责非常单一不能声明任何属性,拓展的测试将会由Jupiter提供额外测试
- @ParameterizedTest :表示方法是参数化测试,下方会有详细介绍
- @RepeatedTest :表示方法可重复执行,下方会有详细介绍
- @DisplayName :为测试类或者测试方法设置展示名称
- @BeforeEach :表示在每个单元测试之前执行
- @AfterEach :表示在每个单元测试之后执行
- @BeforeAll :表示在所有单元测试之前执行
- @AfterAll :表示在所有单元测试之后执行
- @Tag :表示单元测试类别,类似于JUnit4中的@Categories
- @Disabled :表示测试类或测试方法不执行,类似于JUnit4中的@Ignore
- @Timeout :表示测试方法运行如果超过了指定时间将会返回错误
- @ExtendWith :为测试类或测试方法提供扩展类引用
JUnit5 和 Junit4 的变化:
说明 | Junit 4 | Junit 5 |
---|---|---|
定义测试方法即用例 | @Test | @Test |
在当前类中的所有测试方法之前执行 | @BeforeClass | @BeforeAll |
在当前类中的所有测试方法之后执行 | @AfterClass | @AfterAll |
在每个测试用例前执行 | @Before | @BeforeEach |
在每个测试用例后执行 | @After | @AfterEach |
禁用测试方法或类 | @Ignore | @Disabled |
Tagging 和 filtering | @Category | @Tag |
#实战
看下这个例子:
@BeforeAll
public static void init() {
System.out.println("初始化数据");
}
@AfterAll
public static void cleanup() {
System.out.println("清理数据");
}
@BeforeEach
public void tearup() {
System.out.println("当前测试方法开始");
}
@AfterEach
public void tearDown() {
System.out.println("当前测试方法结束");
}
然后再我们的每个测试例子我们看到这样的输出:
初始化数据
当前测试方法开始
当前测试方法结束
当前测试方法开始
我的第二个测试开始测试
当前测试方法结束
当前测试方法开始
我的第一个测试开始测试
当前测试方法结束
当前测试方法开始
当前测试方法结束
#Fixture
Test Fixture 是指一个测试运行所需的固定环境,准确的定义:
The test fixture is everything we need to have in place to exercise the SUT
在进行测试时,我们通常需要把环境设置成已知状态(如创建对象、获取资源等)来创建测试,每次测试开始时都处于一个固定的初始状态;
测试结果后需要将测试状态还原,所以,测试执行所需要的固定环境称为 Test Fixture。
细细分析下这个例子, 代码中使用到的一对注解 @BeforeAll 和 @AfterAll ,它们定义了整个测试类在开始前以及结束时的操作,只能修饰静态方法,主要用于在测试过程中所需要的全局数据和外部资源的初始化和清理。
与它们不同,@BeforeEach 和 @AfterEach 所标注的方法会在每个测试用例方法开始前和结束时执行,主要是负责该测试用例所需要的运行环境的准备和销毁, 也就是 fixture 的管理。
官方的例子open in new window 有非常详尽的描述。
#禁用
禁用执行测试:@Disabled
当我们希望在运行测试类时,跳过某个测试方法,正常运行其他测试用例时,我们就可以用上 @Disabled 注解,表明该测试方法处于不可用,执行测试类的测试方法时不会被 JUnit 执行。
下面看下使用 @Disbaled
之后的运行效果,在原来测试类中添加如下代码:
@DisplayName("我的第三个测试")
@Disabled
@Test
void testThirdTest() {
System.out.println("我的第三个测试开始测试");
}
都让 @Disabled
也可以使用在类上 也可以使用在类上面。
#内嵌
当我们编写的类和代码逐渐增多,随之而来的需要测试的对应测试类也会越来越多。
为了解决测试类数量爆炸的问题,JUnit 5提供了 @Nested
注解,能够以静态内部成员类的形式对测试用例类进行逻辑分组。
并且每个静态内部类都可以有自己的生命周期方法, 这些方法将按从外到内层次顺序执行。
此外,嵌套的类也可以用@DisplayName 标记,这样我们就可以使用正确的测试名称。下面看下简单的用法 NestUnitTestopen in new window:
@DisplayName("内嵌测试类")
public class NestUnitTest {
@BeforeEach
void init() {
System.out.println("测试方法执行前准备");
}
@Nested
@DisplayName("第一个内嵌测试类")
class FirstNestTest {
@Test
void test() {
System.out.println("第一个内嵌测试类执行测试");
}
}
@Nested
@DisplayName("第二个内嵌测试类")
class SecondNestTest {
@Test
void test() {
System.out.println("第二个内嵌测试类执行测试");
}
}
}
#重复
重复性测试:@RepeatedTest
在 JUnit 5 里新增了对测试方法设置运行次数的支持,允许让测试方法进行重复运行。
当要运行一个测试方法 N次时,可以使用 @RepeatedTest
标记它,如下面的代码所示 RepeatedUnitTestopen in new window:
@DisplayName("重复测试")
@RepeatedTest(value = 3)
public void i_am_a_repeated_test() {
System.out.println("执行测试");
}
当然你也可以定制每次 的 DisplayName
:
@DisplayName("自定义名称重复测试")
@RepeatedTest(value = 3, name = "{displayName} 第 {currentRepetition} 次")
public void i_am_a_repeated_test_2() {
System.out.println("执行测试");
}
@RepeatedTest
注解内用 currentRepetition
变量表示已经重复的次数,totalRepetitions
变量表示总共要重复的次数,
displayName
变量表示测试方法显示名称,我们直接就可以使用这些内置的变量来重新定义测试方法重复运行时的名称。
#断言
在断言 API 设计上,JUnit 5 进行显著地改进,并且充分利用 Java 8 的新特性,特别是 Lambda 表达式,最终提供了新的断言类: org.junit.jupiter.api.Assertions
。
许多断言方法接受 Lambda 表达式参数,在断言消息使用 Lambda 表达式的一个优点就是它是 延迟计算 的,如果消息构造开销很大,这样做一定程度上可以节省时间和资源。
注意断言是 Assertions
和后面要介绍的 Assumptions
(假设) 不太一样。
现在还可以将一个方法内的多个断言进行分组,使用 assertAll 方法如下示例代码 AppTestopen in new window:
@Test
void groupAssertions() {
int[] numbers = {0, 1, 2, 3, 4};
assertAll(
"numbers",
() -> assertEquals(numbers[0], 0),
() -> assertEquals(numbers[3], 3),
() -> assertEquals(numbers[4], 4));
}
如果分组断言中任一个断言的失败,都会将以 MultipleFailuresError
错误进行抛出提示。
还有很多:
- assertEquals
- assertNotEquals
- assertArrayEquals
- assertIterableEquals
- assertLinesMatch
- assertNotNull .....
比较有意思的两个: assertTimeout & assertThrows 具体示范下:
#assertTimeout
@Test
@DisplayName("超时方法测试")
void test_should_complete_in_one_second() {
Assertions.assertTimeoutPreemptively(
Duration.of(1, ChronoUnit.SECONDS), () -> Thread.sleep(900));
}
这个测试运行失败,因为代码执行将休眠两秒钟,而我们期望测试用例在一秒钟之内成功
但是如果我们把休眠时间设置一秒钟,测试仍然会出现偶尔失败的情况,这是因为测试方法执行过程中除了目标代码还有额外的代码和指令执行会耗时,
所以在超时限制上无法做到对时间参数的完全精确匹配。
#assertThrows
我们代码中对于带有异常的方法通常都是使用 try-catch 方式捕获处理,针对测试这样带有异常抛出的代码,
而 JUnit 5 提供方法 Assertions#assertThrows(Class<T>, Executable)
来进行测试,第一个参数为异常类型,
第二个为函数式接口参数,跟 Runnable 接口相似,不需要参数,也没有返回,并且支持 Lambda 表达式方式使用,具体使用方式可参考下方代码:
@Test
void shouldThrowException() {
Throwable exception =
assertThrows(
UnsupportedOperationException.class,
() -> {
throw new UnsupportedOperationException("Not supported");
});
assertEquals(exception.getMessage(), "Not supported");
}
#fail
fail()方法指未通过测试。 在尚未写的例子中可以用这个做个占位符!
@Test
void testFailCase() {
Assertions.fail(
"as the method not implement this is a place hold to remind us, usually practice in the TDD");
Assertions.fail(AppTest::message);
}
private static String message() {
return "not found good reason to pass";
}
#假设
JUnit 5假设类提供静态方法来支持基于假设的条件测试执行。假设失败会导致测试中止, 不会导致失败。
当继续执行给定的测试方法没有意义时,通常使用假设。在测试报告中,这些测试将被标记为通过。
JUnit Assumptions(假设)类有以下方法:
- Assumptions.assumeTrue()
- Assumptions.assumeFalse()
- Assumptions.assumingThat()
AssumptionsTestExampleopen in new window 例子。
#assumeTrue/assumeFalse
- assumeTrue 该方法验证给定假设为真,如果假设为真,则测试继续进行,否则,测试执行将中止。
- assumeFalse 该方法验证给定假设为假,如果假设为假,则测试继续,否则,测试执行被中止。
@Test
void testOnDev() {
System.setProperty("ENV", "DEV");
assumeTrue("DEV".equals(System.getProperty("ENV")));
System.out.println("测试继续执行...");
}
/** 测试失败,打印出失败消息 */
@Test
void testOnProd() {
System.setProperty("ENV", "PROD");
assumeTrue("DEV".equals(System.getProperty("ENV")), AssumptionsTestExample::message);
System.out.println("测试不会继续执行,不会打印此行...");
}
private static String message() {
return "测试失败...";
}
#assumingThat
该方法执行提供的可执行上下文,但前提是提供的假设有效。与其他假设方法不同,此方法不会中止测试。
如果该假设无效,则该方法不起任何作用。
如果假设有效且可执行上下文抛出异常,则它将被视为常规测试失败。
抛出的异常将按原样重新抛出,但被掩盖为未经检查的异常。
@Test
void testInAllEnvironments() {
System.setProperty("ENV", "DEV");
assumingThat(
"DEV".equals(System.getProperty("ENV")),
() -> {
// 仅在DEV服务器上执行这些断言和打印
// 即System.setProperty("ENV", "DEV")才会执行
assertEquals(3, 1 + 2);
});
// 在所有环境中执行这些断言
assertEquals(13, 6 + 7);
}