JUnit5的测试不是通过名称,而是通过注解来标识的。

测试类与方法

Test Class:测试类,必须包含至少一个test方法,包括:

  • 最外层的class
  • static member class
  • @Nested class

Test Method:测试方法,包括:

  • @Test
  • @RepeatedTest
  • @ParameterizedTest
  • @TestFactory
  • @TestTemplate

Lifecycle Method:生命周期方法,包括:

  • @BeforeAll
  • @AfterAll
  • @BeforeEach
  • @AfterEach

注意:

  1. Test Method和Lifecycle Method不能是abstract也不能return。它们可以在当前测试类中声明,也可以继承自父类或接口。
  2. Test class、Test Method和Lifecycle Method都不能是private。

示例代码:

import static org.junit.jupiter.api.Assertions.fail;
import static org.junit.jupiter.api.Assumptions.assumeTrue; import org.junit.jupiter.api.AfterAll;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeAll;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Disabled;
import org.junit.jupiter.api.Test; class StandardTests { @BeforeAll
static void initAll() {
} @BeforeEach
void init() {
} @Test
void succeedingTest() {
} @Test
void failingTest() {
fail("a failing test");
} @Test
@Disabled("for demonstration purposes")
void skippedTest() {
// not executed
} @Test
void abortedTest() {
assumeTrue("abc".contains("Z"));
fail("test should have been aborted");
} @AfterEach
void tearDown() {
} @AfterAll
static void tearDownAll() {
} }

自定义显示名字

Test class和test method可以使用@DisplayName自定义在测试报告中的显示名字,支持空格、特殊字符和emoji表情符号。

示例:

import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Test; @DisplayName("A special test case")
class DisplayNameDemo { @Test
@DisplayName("Custom test name containing spaces")
void testWithDisplayNameContainingSpaces() {
} @Test
@DisplayName("╯°□°)╯")
void testWithDisplayNameContainingSpecialCharacters() {
} @Test
@DisplayName("")
void testWithDisplayNameContainingEmoji() {
} }

除了@DisplayName@DisplayNameGeneration 注解能用来对显示名字做统一处理,JUnit Jupiter自带了一些生成器:

  • Standard 匹配标准行为
  • Simple 删除没有参数的方法后面的括号
  • ReplaceUnderscores 用空格替换下划线
  • IndicativeSentences 把test class和test method名字连接起来

示例代码:

import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.DisplayNameGeneration;
import org.junit.jupiter.api.DisplayNameGenerator;
import org.junit.jupiter.api.IndicativeSentencesGeneration;
import org.junit.jupiter.api.Nested;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.ValueSource; class DisplayNameGeneratorDemo { @Nested
@DisplayNameGeneration(DisplayNameGenerator.ReplaceUnderscores.class)
class A_year_is_not_supported { @Test
void if_it_is_zero() {
} @DisplayName("A negative value for year is not supported by the leap year computation.")
@ParameterizedTest(name = "For example, year {0} is not supported.")
@ValueSource(ints = { -1, -4 })
void if_it_is_negative(int year) {
} } @Nested
@IndicativeSentencesGeneration(separator = " -> ", generator = DisplayNameGenerator.ReplaceUnderscores.class)
class A_year_is_a_leap_year { @Test
void if_it_is_divisible_by_4_but_not_by_100() {
} @ParameterizedTest(name = "Year {0} is a leap year.")
@ValueSource(ints = { 2016, 2020, 2048 })
void if_it_is_one_of_the_following_years(int year) {
} } }

@IndicativeSentencesGeneration可以自定义separator和generator。

结果:

+-- DisplayNameGeneratorDemo [OK]
+-- A year is not supported [OK]
| +-- A negative value for year is not supported by the leap year computation. [OK]
| | +-- For example, year -1 is not supported. [OK]
| | '-- For example, year -4 is not supported. [OK]
| '-- if it is zero() [OK]
'-- A year is a leap year [OK]
+-- A year is a leap year -> if it is divisible by 4 but not by 100. [OK]
'-- A year is a leap year -> if it is one of the following years. [OK]
+-- Year 2016 is a leap year. [OK]
+-- Year 2020 is a leap year. [OK]
'-- Year 2048 is a leap year. [OK]

除了注解,也能通过配置设定全局的默认Generator,比如在 src/test/resources/junit-platform.properties文件中:

junit.jupiter.displayname.generator.default = \
org.junit.jupiter.api.DisplayNameGenerator$ReplaceUnderscores

既可以指定现有Generator,也可以指定实现了DisplayNameGenerator接口的类。

自定义显示名字的优先级是:

  1. @DisplayName
  2. @DisplayNameGeneration
  3. junit.jupiter.displayname.generator.default
  4. org.junit.jupiter.api.DisplayNameGenerator.Standard

断言(Assertions)

JUnit5的断言是包含在org.junit.jupiter.api.Assertions中的静态方法,比如assertTrue、assertEquals、assertNotNull、assertAll、assertThrows、assertTimeout、assertTimeoutPreemptively等。

示例代码如下:

import static java.time.Duration.ofMillis;
import static java.time.Duration.ofMinutes;
import static org.junit.jupiter.api.Assertions.assertAll;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertNotNull;
import static org.junit.jupiter.api.Assertions.assertThrows;
import static org.junit.jupiter.api.Assertions.assertTimeout;
import static org.junit.jupiter.api.Assertions.assertTimeoutPreemptively;
import static org.junit.jupiter.api.Assertions.assertTrue; import java.util.concurrent.CountDownLatch; import example.domain.Person;
import example.util.Calculator; import org.junit.jupiter.api.Test; class AssertionsDemo { private final Calculator calculator = new Calculator(); private final Person person = new Person("Jane", "Doe"); @Test
void standardAssertions() {
assertEquals(2, calculator.add(1, 1));
assertEquals(4, calculator.multiply(2, 2),
"The optional failure message is now the last parameter");
assertTrue('a' < 'b', () -> "Assertion messages can be lazily evaluated -- "
+ "to avoid constructing complex messages unnecessarily.");
} @Test
void groupedAssertions() {
// In a grouped assertion all assertions are executed, and all
// failures will be reported together.
assertAll("person",
() -> assertEquals("Jane", person.getFirstName()),
() -> assertEquals("Doe", person.getLastName())
);
} @Test
void dependentAssertions() {
// Within a code block, if an assertion fails the
// subsequent code in the same block will be skipped.
assertAll("properties",
() -> {
String firstName = person.getFirstName();
assertNotNull(firstName); // Executed only if the previous assertion is valid.
assertAll("first name",
() -> assertTrue(firstName.startsWith("J")),
() -> assertTrue(firstName.endsWith("e"))
);
},
() -> {
// Grouped assertion, so processed independently
// of results of first name assertions.
String lastName = person.getLastName();
assertNotNull(lastName); // Executed only if the previous assertion is valid.
assertAll("last name",
() -> assertTrue(lastName.startsWith("D")),
() -> assertTrue(lastName.endsWith("e"))
);
}
);
} @Test
void exceptionTesting() {
Exception exception = assertThrows(ArithmeticException.class, () ->
calculator.divide(1, 0));
assertEquals("/ by zero", exception.getMessage());
} @Test
void timeoutNotExceeded() {
// The following assertion succeeds.
assertTimeout(ofMinutes(2), () -> {
// Perform task that takes less than 2 minutes.
});
} @Test
void timeoutNotExceededWithResult() {
// The following assertion succeeds, and returns the supplied object.
String actualResult = assertTimeout(ofMinutes(2), () -> {
return "a result";
});
assertEquals("a result", actualResult);
} @Test
void timeoutNotExceededWithMethod() {
// The following assertion invokes a method reference and returns an object.
String actualGreeting = assertTimeout(ofMinutes(2), AssertionsDemo::greeting);
assertEquals("Hello, World!", actualGreeting);
} @Test
void timeoutExceeded() {
// The following assertion fails with an error message similar to:
// execution exceeded timeout of 10 ms by 91 ms
assertTimeout(ofMillis(10), () -> {
// Simulate task that takes more than 10 ms.
Thread.sleep(100);
});
} @Test
void timeoutExceededWithPreemptiveTermination() {
// The following assertion fails with an error message similar to:
// execution timed out after 10 ms
assertTimeoutPreemptively(ofMillis(10), () -> {
// Simulate task that takes more than 10 ms.
new CountDownLatch(1).await();
});
} private static String greeting() {
return "Hello, World!";
} }

假设(Assumptions)

JUnit5的断言是包含在org.junit.jupiter.api.Assumptions中的静态方法,比如assumeTrue、assumingThat等。

示例代码:

import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assumptions.assumeTrue;
import static org.junit.jupiter.api.Assumptions.assumingThat; import example.util.Calculator; import org.junit.jupiter.api.Test; class AssumptionsDemo { private final Calculator calculator = new Calculator(); @Test
void testOnlyOnCiServer() {
assumeTrue("CI".equals(System.getenv("ENV")));
// remainder of test
} @Test
void testOnlyOnDeveloperWorkstation() {
assumeTrue("DEV".equals(System.getenv("ENV")),
() -> "Aborting test: not on developer workstation");
// remainder of test
} @Test
void testInAllEnvironments() {
assumingThat("CI".equals(System.getenv("ENV")),
() -> {
// perform these assertions only on the CI server
assertEquals(2, calculator.divide(4, 2));
}); // perform these assertions in all environments
assertEquals(42, calculator.multiply(6, 7));
} }

Assertions与Assumptions区别

Assertions如果失败,test会被标记为failed。Assumptions如果失败,test会被标记为ignored,测试不会执行

示例:

import org.junit.jupiter.api.Test;

import static org.junit.jupiter.api.Assertions.assertTrue;
import static org.junit.jupiter.api.Assumptions.assumeTrue; public class Demo {
@Test
void assertTest() {
assertTrue(false);
} @Test
void assumeTest() {
assumeTrue(false);
}
}

结果:

禁用测试

@Disabled能用来禁用test class或test method,建议在括号内填写上禁用理由。

示例:

import org.junit.jupiter.api.Disabled;
import org.junit.jupiter.api.Test; @Disabled("Disabled until bug #99 has been fixed")
class DisabledClassDemo { @Test
void testWillBeSkipped() {
} }
import org.junit.jupiter.api.Disabled;
import org.junit.jupiter.api.Test; class DisabledTestsDemo { @Disabled("Disabled until bug #42 has been resolved")
@Test
void testWillBeSkipped() {
} @Test
void testWillBeExecuted() {
} }

小结

本文首先介绍了如何使用测试类与方法,来编写一个JUnit5的基本测试,然后介绍了如何自定义测试报告中的显示名字。使用断言(Assertions)可以把test标记为failed,使用假设(Assumptions)可以把test标记为ignored。最后介绍了如何禁用测试。除了基本测试,JUnit5还能编写带条件的测试。

参考资料:

https://junit.org/junit5/docs/current/user-guide/#writing-tests

https://stackoverflow.com/questions/44628483/assume-vs-assert-in-junit-tests

JUnit5编写基本测试的更多相关文章

  1. 新书《编写可测试的JavaScript代码 》出版,感谢支持

    本书介绍 JavaScript专业开发人员必须具备的一个技能是能够编写可测试的代码.不管是创建新应用程序,还是重写遗留代码,本书都将向你展示如何为客户端和服务器编写和维护可测试的JavaScript代 ...

  2. 编写可测试的JavaScript代码

    <编写可测试的JavaScript代码>基本信息作者: [美] Mark Ethan Trostler 托斯勒 著 译者: 徐涛出版社:人民邮电出版社ISBN:9787115373373上 ...

  3. 使用FsCheck编写Property-based测试

    使用FsCheck编写Property-based的测试 在编写基于Property-based的单元测试一文中,我们介绍了什么是Property-based测试.同时我们也总结了Property-b ...

  4. 编写Avocado测试

    编写Avocado测试 现在我们开始使用python编写Avocado测试,测试继承于avocado.Test. 基本例子 创建一个时间测试,sleeptest,测试非常简单,只是sleep一会: i ...

  5. springboot快速入门02--Controller编写和测试

    02springboot快速入门--Controller编写和测试 1.新建一个HelloController import org.springframework.boot.SpringApplic ...

  6. Shell脚本的编写及测试

                                                      Shell脚本的编写及测试 1.1问题 本例要求两个简单的Shell脚本程序,任务目标如下: 编写一 ...

  7. 098 01 Android 零基础入门 02 Java面向对象 03 综合案例(学生信息管理) 02 案例分析及实现 02 编写并测试Subject类

    098 01 Android 零基础入门 02 Java面向对象 03 综合案例(学生信息管理) 02 案例分析及实现 02 编写并测试Subject类 本文知识点:编写并测试Subject类 说明: ...

  8. 099 01 Android 零基础入门 02 Java面向对象 03 综合案例(学生信息管理) 02 案例分析及实现 03 编写并测试Student类

    099 01 Android 零基础入门 02 Java面向对象 03 综合案例(学生信息管理) 02 案例分析及实现 03 编写并测试Student类 本文知识点:编写并测试Subject类 说明: ...

  9. JUnit5的条件测试、嵌套测试、重复测试

    条件测试 JUnit5支持条件注解,根据布尔值判断是否执行测试. 自定义条件 @EnabledIf和@DisabledIf注解用来设置自定义条件,示例: @Test @EnabledIf(" ...

随机推荐

  1. Linux查看PCIe版本及速率# lspci -vvv |grep Width -i

    Linux查看PCIe版本及速率 https://www.cnblogs.com/lsgxeva/p/9542975.html# lspci -vvv |grep Width -i # lspci | ...

  2. wps中新罗马字体如何设置Times New Roman

    word wps中新罗马字体如何设置Times New Roman  ###   WPS字体自带 Times New Roman  ###

  3. Win10 禁用摄像头的方法及注意事项

    Win10 禁用摄像头的方法及注意事项 windows教程 2020-03-04  223 最新的Windows10系统中应该如何禁用摄像头呢?下面MS酋长与大家分享一下.当然,如果你说用个便利贴把摄 ...

  4. 什么是CPU缓存

    一.什么是CPU缓存 1. CPU缓存的来历 众所周知,CPU是计算机的大脑,它负责执行程序的指令,而内存负责存数据, 包括程序自身的数据.在很多年前,CPU的频率与内存总线的频率在同一层面上.内存的 ...

  5. Docker Swarm(三)Service(服务)分配策略

    Service的分配原則 預設分散至多個nodes上 使用率較低的node優先配置 使用者可自行定義此分配模式 Service分配的3種方式 Service Constraints (服务约束) 参考 ...

  6. shell应用之习题一

    1 #!/bin/bash 2 #.写一个脚本/root/bin/argsnum.sh,接受一个文件路径作 为参数:如果参数个数小于1,则提示用户"至少应该给一个 参数",并立即退 ...

  7. xss-代码角度理解与绕过filter

    0x00 原理   xss全称为cross site scripting,中文为跨站脚本攻击.它允许web用户将恶意代码植入到提供给用户使用的页面.代码包括HTML代码和客户端脚本. 0x01 危害 ...

  8. 3D-camera结构光原理

    3D-camera结构光原理 目前主流的深度探测技术是结构光,TOF,和双目.具体的百度就有很详细的信息. 而结构光也有双目结构光和散斑结构光等,没错,Iphone X 的3D深度相机就用 散斑结构光 ...

  9. 深度树匹配模型(TDM)

    深度树匹配模型(TDM) 算法介绍 Tree-based Deep Match(TDM)是由阿里妈妈精准定向广告算法团队自主研发,基于深度学习上的大规模(千万级+)推荐系统算法框架.在大规模推荐系统的 ...

  10. NSight Compute 用户手册(上)

    NSight Compute 用户手册(上) 非交互式配置文件活动 从NVIDIA Nsight Compute启动目标应用程序 启动NVIDIA Nsight Compute时,将出现欢迎页面.单击 ...