什么是JUnit5
Junit是Java版的单元测试框架,单元测试也就是方法测试,从JUnit5开始支持Java8的语法。
本项目所有代码均在github开源:https://github.com/initit-com/JUnit5Demo
安装
推荐Maven引入
<dependency>
<groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter</artifactId>
<version>RELEASE</version>
<scope>test</scope>
</dependency
Maven会自动引入三个jar,如下图
快速启动
@Test标注在一个void方法上,即一个单元测试
/**
* 方法必须是viod ,junit5以后可以不用public修饰
*/
@Test
void test() {
//断言 2等于2
assertEquals(2, 2);
}
显示名称
@DisplayName(“指定显示测试名称”) 标注在方法上,指定测试的名称
@Test
@DisplayName("显示测试名称")
void testWithDisplayNameContainingSpaces() {
}
@Test
@DisplayName("特殊符号:【╯°□°)╯】 或表情: 😱")
void testWithDisplayNameContainingSpecialCharacters() {
}
显示结果如下
常用的断言assert
断言(预言)结果是否成立,成立则测试通过,否则报错提示
@Test
void standardAssertions() {
//断言相等
assertEquals(2, 2);
//断言相等 并设置默认报错信息
assertEquals(4, 4, "必须相等");
//判断为true 并使用lambda表达式方式设置默认报错信息
assertTrue(3 < 4, () -> "必须为true");
}
@Test
void groupedAssertions() {
// 断言一组
assertAll("person",
() -> assertEquals("Jane", "Jane"),
() -> {
System.out.println("这里模拟执行多行代码");
assertAll(() -> {
System.out.println("这里模拟多层深度测试");
assertTrue(5 < 7);
}
);
}
);
}
//断言异常
@Test
void exceptionTesting() {
Exception exception = assertThrows(RuntimeException.class, () -> {
throw new RuntimeException("模拟抛出运行时异常");
});
assertEquals("模拟抛出运行时异常", exception.getMessage());
}
//断言执行时间
@Test
void timeoutNotExceeded() {
//一秒内执行完毕
assertTimeout(ofSeconds(1), () -> {
System.out.println("执行");
Thread.sleep(2000);
});
}
//更多断言方式参考官方文档
假设assume
只有假设成立才执行后边的业务代码。
@Test
void testAssume() {
assumeTrue("aa".equals("aa"));
System.out.println("假设成功后才执行后边的代码,否则不执行");
}
//只有在windows系统下执行测试
@Test
void testOnlyWindows() {
assumeTrue( System.getProperty("os.name").toLowerCase().contains("windows"));
System.out.println("当前是windows系统时执行一下测试代码");
//todo 测试代码
}
禁用测试
//标注在类上 该类的所有测试方法都被禁止执行
@Disabled("禁止测试,因为#99bug已经修复")
public class D005_禁止测试 {
@Test
void testWillBeSkipped() {
}
//标注在方法上 该方法禁止执行
@Disabled("Disabled until bug #42 has been resolved")
@Test
void testWillBeSkipped2() {
}
}
测试的生命周期
- @BeforeAll 在所有测试方法执行之前执行
- @AfterAll 在所有测试方法执行之后执行
- @BeforeEach 在每一个测试方法执行之前执行
- @AfterEach 在每一个测试方法之执行之后执行
配合Java8接口的静态方法/默认方法语法,我们实现如下功能:
@TestInstance(TestInstance.Lifecycle.PER_CLASS)
public interface D006_生命周期共用接口 {
//默认方法default 也可以是 静态方法 static
@BeforeAll
default void beforeAllTests() {
System.out.println("所有测试之前执行");
}
@AfterAll
default void afterAllTests() {
System.out.println("所有测试之后执行");
}
@BeforeEach
default void beforeEachTest() {
System.out.println("每个测试方法之前执行");
}
@AfterEach
default void afterEachTest() {
System.out.println("每个测试方法之后执行");
}
}
注意:生命周期需要用@TestInstance注解标注在测试类上,看下@TestInstance的源码
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Inherited
@Documented
@API(status = STABLE, since = "5.0")
public @interface TestInstance {
/**
* Enumeration of test instance lifecycle <em>modes</em>.
*
* @see #PER_METHOD
* @see #PER_CLASS
*/
enum Lifecycle {
/**
* When using this mode, a new test instance will be created once per test class.
*
* @see #PER_METHOD
*/
PER_CLASS,
/**
* When using this mode, a new test instance will be created for each test method,
* test factory method, or test template method.
*
* <p>This mode is analogous to the behavior found in JUnit versions 1 through 4.
*
* @see #PER_CLASS
*/
PER_METHOD;
}
/**
* The test instance lifecycle <em>mode</em> to use.
*/
Lifecycle value();
}
写一个测试类实现上述接口
public class D006_生命周期 implements D006_生命周期共用接口 {
@Test
void test1(){
System.out.println("test1执行");
}
@Test
void test2(){
System.out.println("test2执行");
}
}
运行结果如下
重复执行测试
@RepeatedTest(执行次数),方法会执行n次
@RepeatedTest(3)
void repeatedTest() {
System.out.println("该方法会重复执行3次");
}
参数测试
//参数值测试
@ParameterizedTest
@ValueSource(strings = {"red", "read", "rgb"})
void test(String str) {
assertTrue(str.contains("r"), "参数必须包含字母r");
}
//参数是枚举值测试
@ParameterizedTest
@EnumSource(TimeUnit.class)
void testWithEnumSource(TimeUnit timeUnit) {
assertNotNull(timeUnit);
}
//参数是方法
@ParameterizedTest
@MethodSource("stringProvider")
void testWithExplicitLocalMethodSource(String argument) {
assertNotNull(argument);
}
static Stream<String> stringProvider() {
return Stream.of("apple", "banana");
}
//。。。等等其他参数值类型测试
参数值类型主要在包org.junit.jupiter.params.provider下,如下图所示:
超时测试
@Test
@Timeout(2) //默认是秒 可以修改
void test() {
//超过2秒报错
}
//@Timeout源码如下,默认为秒,可以更改为毫秒
@Target({ ElementType.TYPE, ElementType.METHOD })
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@API(status = EXPERIMENTAL, since = "5.5")
public @interface Timeout {
/**
* The duration of this timeout.
*
* @return timeout duration; must be a positive number
*/
long value();
/**
* The time unit of this timeout.
*
* @return time unit
* @see TimeUnit
*/
TimeUnit unit() default TimeUnit.SECONDS;
}
测试执行顺序
@Order(执行顺序的数字)表示执行顺序
@TestMethodOrder(MethodOrderer.OrderAnnotation.class)指定执行顺序
@TestMethodOrder(MethodOrderer.OrderAnnotation.class)
public class D010_测试执行顺序 {
@Test
@Order(2)
void t2() {
System.out.println(2);
}
@Test
@Order(1)
void t1() {
System.out.println(1);
}
@Test
@Order(3)
void t3() {
System.out.println(3);
}
}
常用注解
注释 | 描述 |
---|---|
@Test |
表示方法是单元测试方法,区别于JUnit4,可以不用public关键词修饰 |
@ParameterizedTest |
表示方法为参数化测试 |
@RepeatedTest |
表示方法是重复测试的测试模板 |
@TestFactory |
表示方法是动态测试的测试工厂 |
@TestTemplate |
表示方法是测试用例的模板,根据注册提供程序返回的调用上下文的数量,设计为多次调用。 |
@TestMethodOrder |
用于配置已分号测试类的测试方法执行顺序;类似于 JUnit 4 的 |
@TestInstance |
用于为测试类配置测试实例生命周期。 |
@DisplayName |
声明测试类或测试方法的自定义显示名称 |
@DisplayNameGeneration |
声明测试类的自定义显示名称生成器 |
@BeforeEach |
每个方法执行之前执行 |
@AfterEach |
每个方法执行之后执行 |
@BeforeAll |
所有方法执行之前执行 |
@AfterAll |
所有方法执行之后执行 |
@Nested |
表示注释类是非静态嵌套测试类 |
@Tag |
用于在类或方法级别声明用于筛选测试的标记;类似于 JUnit 4 中的 TestNG 或类别中的测试组 |
@Disabled |
用于禁用测试类或测试方法;类似于 JUnit 4 的 @Ignore |
@Timeout |
标注超时测试 |
@ExtendWith |
用于声明性地注册扩展 |
@RegisterExtension |
用于通过字段以编程方式注册扩展 |
@TempDir |
用于在生命周期方法或测试方法中通过字段注入或参数注入提供临时目录;位于包中。org.junit.jupiter.api.io |
参考资料
- JUnit5 官网:https://junit.org/junit5/
- JUnit5 使用手册: https://junit.org/junit5/docs/current/user-guide/
- JUnit5 API: https://junit.org/junit5/docs/current/api/
- JUnit5 源代码: https://github.com/junit-team/junit5