赞
踩
Spring Boot 2.2.0 版本开始引入 JUnit 5 作为单元测试默认库。JUnit 5作为最新版本的JUnit框架,与之前版本的Junit框架有很大的不同。JUnit 5由三个不同子项目的几个不同模块组成。JUnit 5 = JUnit Platform + JUnit Jupiter + JUnit Vintage
注意:
SpringBoot 2.4 以上版本移除了默认对 Vintage 的依赖。如果需要兼容junit4需要自行引入(不能使用junit4的功能 @Test)。
<dependency>
<groupId>org.junit.vintage</groupId>
<artifactId>junit-vintage-engine</artifactId>
<scope>test</scope>
<exclusions>
<exclusion>
<groupId>org.hamcrest</groupId>
<artifactId>hamcrest-core</artifactId>
</exclusion>
</exclusions>
</dependency>
加入test依赖后:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
在springboot2.4后,创建项目会自动生成如下的测试类:
@SpringBootTest
class WebAdminApplicationTests {
@Test
void contextLoads() {
}
}
在springboot2.4后,只需要@SpringBootTest标明是测试类,在通过@Test标明在方法上即可。
在springboot2.4以前,需要通过@SpringBootTest + @RunWith才能实现测试方法,比如以下代码:
@RunWith(SpringRunner.class)
@SpringBootTest
class TaskRepositoryTest2 {
@Autowired
private TaskRepository taskRepository;
@Test
void test() {
Optional<Task> task = taskRepository.findById((long) 1);
System.out.println("Task:: " + task.get());
}
}
SpringBoot整合Junit以后,写测试类的方法:
JUnit5的注解与JUnit4的注解有所变化,详细可浏览官方文档https://junit.org/junit5/docs/current/user-guide/#writing-tests-annotations
注意:
import org.junit.jupiter.api.Test; //注意这里使用的是jupiter的Test注解!!
public class TestDemo {
@Test
@DisplayName("第一次测试")
public void firstTest() {
System.out.println("hello world");
}
该注解标注的方法不执行
重复执行几次。
断言(assertions)是测试方法中的核心部分,用来对测试需要满足的条件进行验证。也就是说,我们认为在测试过程中必须出现的现象,没有出现则意味着程序出错。这些断言方法都是 org.junit.jupiter.api.Assertions 的静态方法。
断言的优点:
在项目上线之前跑一下测试,我们可以看到会有一个详细的测试报告
JUnit 5 内置的断言可以分成如下几个类别:
用来对单个值进行简单的验证。如
方法 | 说明 |
---|---|
assertEquals | 判断两个对象或两个原始类型是否相等 |
assertNotEquals | 判断两个对象或两个原始类型是否不相等 |
assertSame | 判断两个对象引用是否指向同一个对象 |
assertNotSame | 判断两个对象引用是否指向不同的对象 |
assertTrue | 判断给定的布尔值是否为 true |
assertFalse | 判断给定的布尔值是否为 false |
assertNull | 判断给定的对象引用是否为 null |
assertNotNull | 判断给定的对象引用是否不为 null |
@Test @DisplayName("simple assertion") public void simple() { assertEquals(3, 1 + 2, "simple math"); assertNotEquals(3, 1 + 1); assertNotSame(new Object(), new Object()); Object obj = new Object(); assertSame(obj, obj); assertFalse(1 > 2); assertTrue(1 < 2); assertNull(null); assertNotNull(new Object()); }
assertEquals :判断两个对象或两个原始类型是否相等。该方法需要导入static org.junit.jupiter.api.Assertions.*包,static说明这个包里面的方法是静态的方法,可以直接调用里面的方法,不用包名。该方法一般有两个参数,一个是期望的值,一个是实际计算结果的值,他会判断至两个是否相等,若不相等则会报错,我们可以给该方法在添加一个message的参数,该参数会在报错信息中展现出来。要注意,断言执行失败,后面的代码都不会执行。
assertSame:判断两个对象引用是否指向同一个对象
通过 assertArrayEquals 方法来判断两个对象或原始类型的数组是否相等
@Test
@DisplayName("array assertion")
public void array() {
assertArrayEquals(new int[]{1, 2}, new int[] {1, 2});
}
可以通过测试方法的名字判断出该测试方法已经执行了,没有报错说明期望和实际的数组一样。
assertAll 方法接受多个 org.junit.jupiter.api.Executable 函数式接口的实例作为要验证的断言,可以通过 lambda 表达式很容易的提供这些断言
@Test
@DisplayName("assert all")
public void all() {
assertAll("Math",
() -> assertEquals(2, 1 + 1),
() -> assertTrue(1 > 0)
);
}
声明的断言所有都成功才会成功,如果有一个失败后面的代码都不会执行。
在JUnit4时期,想要测试方法的异常情况时,需要用@Rule注解的ExpectedException变量还是比较麻烦的。而JUnit5提供了一种新的断言方式Assertions.assertThrows() ,配合函数式编程就可以进行使用。(因为异常一定会抛出异常)
咦,是不是很好奇,怎么执行成功了?这是因为我们在这断言他会出现算术运算异常(ArithmeticException.class),因此执行成功。如果业务逻辑没有算术异常的情况的话,则会报错。
Junit5还提供了Assertions.assertTimeout() 为测试方法设置了超时时间
@Test
@DisplayName("超时测试")
public void timeoutTest() {
//如果测试方法时间超过1s将会异常
Assertions.assertTimeout(Duration.ofMillis(1000), () -> Thread.sleep(500));
}
通过 fail 方法直接使得测试失败
JUnit 5 中的前置条件(assumptions【假设】)类似于断言,不同之处在于不满足的断言会使得测试方法失败,而不满足的前置条件只会使得测试方法的执行终止。前置条件可以看成是测试方法执行的前提,当该前提不满足时,就没有继续执行的必要。
@DisplayName("前置条件") public class AssumptionsTest { private final String environment = "DEV"; @Test @DisplayName("simple") public void simpleAssume() { assumeTrue(Objects.equals(this.environment, "DEV")); assumeFalse(() -> Objects.equals(this.environment, "PROD")); } @Test @DisplayName("assume then do") public void assumeThenDo() { assumingThat( Objects.equals(this.environment, "DEV"), () -> System.out.println("In DEV") ); } }
注意:
JUnit 5 可以通过 Java 中的内部类和@Nested注解实现嵌套测试,从而可以更好的把相关的测试方法组织在一起。在内部类中可以使用@BeforeEach 和@AfterEach 注解,而且嵌套的层次没有限制。
package com.xzz.web_admin; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Nested; import org.junit.jupiter.api.Test; import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.MethodSource; import org.junit.jupiter.params.provider.ValueSource; import java.util.EmptyStackException; import java.util.Stack; import java.util.stream.Stream; import static org.junit.jupiter.api.Assertions.*; @DisplayName("嵌套测试") public class TestingAStackDemo { Stack<Object> stack; @ParameterizedTest @DisplayName("参数化测试") @ValueSource(ints = {1,2,3,4,5}) void testParameterized(int i){ System.out.println(i); } @ParameterizedTest @DisplayName("参数化测试") @MethodSource("stringProvider") void testParameterized2(String i){ System.out.println(i); } static Stream<String> stringProvider() { return Stream.of("apple", "banana","atguigu"); } @Test @DisplayName("new Stack()") void isInstantiatedWithNew() { new Stack<>(); //嵌套测试情况下,外层的Test不能驱动内层的Before(After)Each/All之类的方法提前/之后运行 assertNull(stack); } @Nested @DisplayName("when new") class WhenNew { @BeforeEach void createNewStack() { stack = new Stack<>(); } @Test @DisplayName("is empty") void isEmpty() { assertTrue(stack.isEmpty()); } @Test @DisplayName("throws EmptyStackException when popped") void throwsExceptionWhenPopped() { assertThrows(EmptyStackException.class, stack::pop); } @Test @DisplayName("throws EmptyStackException when peeked") void throwsExceptionWhenPeeked() { assertThrows(EmptyStackException.class, stack::peek); } @Nested @DisplayName("after pushing an element") class AfterPushing { String anElement = "an element"; @BeforeEach void pushAnElement() { stack.push(anElement); } /** * 内层的Test可以驱动外层的Before(After)Each/All之类的方法提前/之后运行 */ @Test @DisplayName("it is no longer empty") void isNotEmpty() { assertFalse(stack.isEmpty()); } @Test @DisplayName("returns the element when popped and is empty") void returnElementWhenPopped() { assertEquals(anElement, stack.pop()); assertTrue(stack.isEmpty()); } @Test @DisplayName("returns the element when peeked but remains not empty") void returnElementWhenPeeked() { assertEquals(anElement, stack.peek()); assertFalse(stack.isEmpty()); } } } }
注意:
参数化测试是JUnit5很重要的一个新特性,它使得用不同的参数多次运行测试成为了可能,也为我们的单元测试带来许多便利。
利用@ValueSource等注解,指定参数,我们将可以使用不同的参数进行多次单元测试,而不需要每新增一个参数就新增一个单元测试,省去了很多冗余代码。
@ValueSource: 为参数化测试指定参数的来源,支持八大基础类以及String类型,Class类型
@NullSource: 表示为参数化测试提供一个null的参数
@EnumSource: 表示为参数化测试提供一个枚举的参数
@CsvFileSource:表示读取指定CSV文件内容作为参数化测试的参数
@MethodSource:表示读取指定方法的返回值作为参数化测试的参数(注意方法返回需要是一个流)
当然如果参数化测试仅仅只能做到指定普通的入参还达不到让我觉得惊艳的地步。让我真正感到他的强大之处的地方在于他可以支持外部的各类入参。如:CSV,YML,JSON 文件甚至方法的返回值也可以作为入参。只要实现ArgumentsProvider接口,任何外部文件都可以作为参数化测试方法的入参。
@ParameterizedTest表示的是参数化测试
上面这些测试的方法都是基于Junit5进行测试的,如果之前是用Junit4进行测试,那怎么迁移到Junit5呢?官方文档给出了明确的答案https://junit.org/junit5/docs/current/user-guide/#migrating-from-junit4
另外,我们在从Junit5迁移到Junit4的时候还需注意如下的变化:
这些仅仅是Junit5的冰山一角,如果要对Junit5进行更深入的学习可以参考官方文档https://junit.org/junit5/docs/current/user-guide/
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。