# 06.SpringBoot-单元测试
# 1.Junit5简介
JUnit 5官方文档 (opens new window)
JUnit 5 = JUnit Platform + JUnit Jupiter + JUnit Vintage
JUnit Platform: Junit Platform是在JVM上启动测试框架的基础,不仅支持Junit自制的测试引擎,其他测试引擎也都可以接入。
JUnit Jupiter: JUnit Jupiter提供了JUnit5的新的编程模型,是JUnit5新特性的核心。内部包含了一个测试引擎,用于在Junit Platform上运行。
JUnit Vintage: 由于JUint已经发展多年,为了照顾老的项目,JUnit Vintage提供了兼容JUnit4.x,JUnit3.x的测试引擎。
使用 Junit 5 需要添加的依赖
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
2
3
4
5
Spring的JUnit 5的基本单元测试模板
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.Test;//注意不是org.junit.Test(这是JUnit4版本的)
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
@SpringBootTest
class SpringBootApplicationTests {
@Autowired
private Component component;
@Test
//@Transactional 标注后连接数据库有回滚功能
public void contextLoads() {
Assertions.assertEquals(5, component.getFive());
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
# 2.常用注解
@Test:表示方法是测试方法。但是与JUnit4的@Test不同,他的职责非常单一不能声明任何属性,拓展的测试将会由Jupiter提供额外测试
@RepeatedTest:表示方法可重复执行。
@DisplayName:为测试类或者测试方法设置展示名称。
@BeforeEach:表示在每个单元测试之前执行。
@AfterEach:表示在每个单元测试之后执行。
@BeforeAll:表示在所有单元测试之前执行。
@AfterAll:表示在所有单元测试之后执行。
@Disabled:表示测试类或测试方法不执行,类似于JUnit4中的@Ignore。
@Timeout:表示测试方法运行如果超过了指定时间将会返回错误。
@ExtendWith:为测试类或测试方法提供扩展类引用。
@ParameterizedTest:表示方法是参数化测试。
测试示例:
/*
* SpringBootTest包括 @BootstrapWith(SpringBootTestContextBootstrapper.class)
* @ExtendWith({SpringExtension.class})
* 标上此注解,可以使用@Autowired注入Bean对象
*/
@SpringBootTest
@DisplayName("Junit5的注解测试") //测试命名
public class Junit5Test {
@Autowired
private JdbcTemplate jdbcTemplate;
@RepeatedTest(3)
@DisplayName("测试方法1") //测试命名
public void test1() {
System.out.println("test1");
}
@Timeout(1)
@DisplayName("测试方法2")
@Test
public void test2() throws InterruptedException {
Thread.sleep(1500);
System.out.println("test2");
}
@Disabled
@DisplayName("测试方法3")
@Test
public void test3() {
System.out.println("test3");
}
@DisplayName("测试方法4")
@Test
public void test4() {
System.out.println(jdbcTemplate);
}
@BeforeEach
public void beforeEach() {
System.out.println("测试开始了");
}
@AfterEach
public void afterEach() {
System.out.println("测试结束了");
}
@BeforeAll
public static void beforeAll() {
System.out.println("所有测试开始");
}
@AfterAll
public static void afterAll() {
System.out.println("所有测试结束");
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
# 3.断言机制
断言Assertion是测试方法中的核心部分,用来对测试需要满足的条件进行验证。这些断言方法都是org.junit.jupiter.api.Assertions的静态方法。检查业务逻辑返回的数据是否合理。所有的测试运行结束以后,会有一个详细的测试报告。
JUnit 5 内置的断言可以分成如下几个类别:
# 简单断言
用来对单个值进行简单的验证。如:
@Test
@DisplayName("简单断言")
public void simpleAssert(){
assertEquals(1,1);
assertNotEquals(1,2);
Object o = new Object();
Object o1 = new Object();
assertSame(o,o);
assertNotSame(o,o1);
assertFalse(false);
assertTrue(true);
assertNull(null);
assertNotNull(o);
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
# 数组断言
通过 assertArrayEquals 方法来判断两个对象或原始类型的数组是否相等。
@DisplayName("数组断言")
@Test
public void array(){
int[] a1 = {1, 2, 3};
int[] a2 = {1, 2, 4};
assertArrayEquals(a1,a2);
}
2
3
4
5
6
7
# 组合断言
assertAll()方法接受多个 org.junit.jupiter.api.Executable 函数式接口的实例作为要验证的断言,可以通过 lambda 表达式很容易的提供这些断言。
@DisplayName("组合断言")
@Test
public void all(){
assertAll("test",
()->assertEquals(1,1),()->assertTrue(true));
}
2
3
4
5
6
# 异常断言
@DisplayName("异常断言")
@Test
public void exception(){
assertThrows(ArithmeticException.class,()->{
int i=10/0;
});
}
2
3
4
5
6
7
# 快速失败
只要执行到fail语句就会失败
@DisplayName("快速失败")
@Test
public void unSuccess(){
if (1==2){
fail("失败了");
}
}
2
3
4
5
6
7
# 超时断言
JUnit5还提供了Assertions.assertTimeout()为测试方法设置了超时时间。
@DisplayName("超时断言")
@Test
public void timeout(){
assertTimeout(Duration.ofSeconds(1),()->{
Thread.sleep(2000);
});
}
2
3
4
5
6
7
# 4.前置条件
Junit 5中的前置条件(assumption)类似于断言,不同之处在于不满足的断言会使测试方法失败,而不满足的前置条件只会是的测试方法的执行终止,而且他执行不成功的图标与@Disabled 禁用的图标一致
前置条件可以看成测试方法的前提,不满足时就没有执行的必要
@DisplayName("前置条件")
public class AssumptionsTest {
private final String environment = "DEV";
@DisplayName("测试方法1")
@Test
void test1(){
assumeTrue(Objects.equals(environment, "DEV"));
assumeFalse(Objects.equals(environment, "PROD"));
}
@DisplayName("assume then do")
@Test
public void assumeThenDo() {
assumingThat(
Objects.equals(this.environment, "DEV"),
() -> System.out.println("In DEV")
);
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
# 5.嵌套循环
官方文档 - Nested Tests (opens new window)
JUnit 5 可以通过 Java 中的内部类和@Nested 注解实现嵌套测试,从而可以更好的把相关的测试方法组织在一起。在内部类中可以使用@BeforeEach 和@AfterEach注解,而且嵌套的层次没有限制,但是外部不可以使用内部的
package com.demo.boot;
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 java.util.EmptyStackException;
import java.util.Stack;
import static org.junit.jupiter.api.Assertions.*;
@DisplayName("A stack")
class TestingAStackDemo {
Stack<Object> stack;
@Test
@DisplayName("is instantiated with new Stack()")
void isInstantiatedWithNew() {
new 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
@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());
}
}
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
# 6.参数化测试
利用@ValueSource等注解,指定入参,我们将可以使用不同的参数进行多次单元测试,而不需要每新增一个参数就新增一个单元测试,省去了很多冗余代码。
@ValueSource: 为参数化测试指定入参来源,支持八大基础类以及String类型,Class类型
@NullSource: 表示为参数化测试提供一个null的入参
@EnumSource: 表示为参数化测试提供一个枚举入参
@CsvFileSource:表示读取指定CSV文件内容作为参数化测试入参
@MethodSource:表示读取指定方法的返回值作为参数化测试入参(注意方法返回需要是一个流)
package com.demo.boot;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.MethodSource;
import org.junit.jupiter.params.provider.ValueSource;
import java.util.stream.Stream;
public class parameterized {
@ParameterizedTest
@ValueSource(ints ={1,3,4,5})
@DisplayName("测试参数化1")
public void test1(int i){
System.out.println(i);
}
@ParameterizedTest
@MethodSource("stringProvider")
@DisplayName("测试参数化2")
public void test2(String str){
System.out.println(str);
}
static Stream<String> stringProvider(){
return Stream.of("apple","banana");
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31