springboot单元测试JUnit5
1 简介
在Spring Boot2.4之后就不兼容JUnit4及以前的版本了
2 JUnit5依赖
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
3 注意事项
写测试类,包名要和非测试类包名一致
4 常用注解
- @SpringBootTest @Test
@SpringBootTest
public class JUnit5Test {
@DisplayName("测试 @DisplayName()")
@Test
void testDisplayName(){
System.out.println("这是 @DisplayName()的测试!");
}
}
@Autowired自动装配注入
@Transactional标注测试方法,测试结束进行回滚
@Test 用于声明该方法是测试方法
@ParameterizedTest 参数化测试
- @RepeatedTest()
对测试方法进行重复测试
@RepeatedTest(value = 2)
@DisplayName("测试 @DisplayName()")
@Test
void testDisplayName(){
System.out.println("这是 @DisplayName()的测试!");
}
- @DisplayName() 用于指定测试方法或测试类的展示名称
@SpringBootTest
public class JUnit5Test {
@DisplayName("测试 @DisplayName()")
@Test
void testDisplayName(){
System.out.println("这是 @DisplayName()的测试!");
}
}
- @BeforeEach
用于在每个测试方法之前进行操作
@BeforeEach
void testBeforeEach(){
System.out.println("测试即将开始--->");
}
- @BeforeAll
用于在所有测试方法之前进行操作
@BeforeAll
static void testBeforeAll(){
System.out.println("**********测试开始**********");
}
- @AfterAll
用于在所有测试方法之后进行操作
@AfterAll
static void testAfterAll(){
System.out.println("***********测试结束**********");
}
- @AfterEach
用于在每个测试方法之后进行操作
@AfterEach
void testAfterEach(){
System.out.println("测试结束--->");
}
-
@Tag()
用于表示单元测试类别 -
@Disabled
用于取消测试方法或测试类(不执行)
@Disabled
@DisplayName("测试2")
@Test
void testDisplayName2(){
System.out.println("这是 @DisplayName()的第二个测试!");
}
- @Timeout()
用于设定测试方法若超出规定事件就会返回错误
@DisplayName("TimeOut的测试方法")
@Timeout(value = 5,unit = TimeUnit.MILLISECONDS)
@Test
void testTimeOut(){
try {
Thread.sleep(20);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("@TimeOut 的测试");
}
- @ExtendWith()
用于为测试类或测试方法提供扩展类引用
我们看到SpringBoot里使用的@SpringBootTest注解就有这个
完整常用注解测试类
import org.junit.jupiter.api.*;
import org.springframework.boot.test.context.SpringBootTest;
import java.util.concurrent.TimeUnit;
@SpringBootTest
public class JUnit5Test {
@DisplayName("TimeOut的测试方法")
@Timeout(value = 5,unit = TimeUnit.MILLISECONDS)
@Test
void testTimeOut(){
try {
Thread.sleep(20);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("@TimeOut 的测试");
}
@RepeatedTest(value = 2)
@DisplayName("测试 @DisplayName()")
@Test
void testDisplayName(){
System.out.println("这是 @DisplayName()的测试!");
}
@Disabled
@DisplayName("测试2")
@Test
void testDisplayName2(){
System.out.println("这是 @DisplayName()的第二个测试!");
}
@BeforeEach
void testBeforeEach(){
System.out.println("测试即将开始--->");
}
@AfterEach
void testAfterEach(){
System.out.println("测试结束--->");
}
@BeforeAll
static void testBeforeAll(){
System.out.println("**********测试开始**********");
}
@AfterAll
static void testAfterAll(){
System.out.println("***********测试结束**********");
}
}
5 断言机制
用于对测试需要满足的条件进行验证(检测业务逻辑返回的数据是否合理)
断言方法都在junit.jupiter.api.Assertions的静态方法中
当测试出现问题之后会给测试者一个详细的测试报告
断言情况下前面断言出现问题后面则不会执行
5.1 简单断言
assertEquals()方法
用于判断传入的两个对象原始类型是否相同
public static void assertEquals(int expected, int actual, String message) {
AssertEquals.assertEquals(expected, actual, message);
}
第三个参数是可以用来设置测试失败后自定义的信息打印
assertEquals(judgeNum,10,“测试结果错误”);
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Test;
import org.springframework.boot.test.context.SpringBootTest;
import java.util.Scanner;
import static org.junit.jupiter.api.Assertions.*;
@SpringBootTest
public class JUnit5AssertionTest {
@DisplayName("简单断言测试")
@Test
void simpleAssertionTest(){
int judgeNum = judge(2, 5);
assertEquals(judgeNum,10);
}
int judge(int num1,int num2){
int num = num1>num2?num1:num2;
return num;
}
}
assertSame()方法
用于判断两个对象是否是同一个对象
@DisplayName(" assertSame 断言测试")
@Test
void assertSameTest(){
Object obj1 = new Object();
Object obj2 = new Object();
assertSame(obj1,obj2,"测试失败");
}
assertNotEquals()判断两个对象的原始类型是否不同
assertNotSame()判断两个对象是否不是同一个对象
assertTrue()判断给定的布尔值是否为True
assertFalse()判断给定的布尔值是否为false
assertNull()判断给定的对象引用是否为 NULL
assertNotNull()判断给定的对象引用是否不为Null
5.2 数组断言
assertArrayEquals()
判断两个数组中的值是否相同
传入的两个数组一定要是同一个类型的
@Test
void assertArray() {
assertArrayEquals(new int[]{1, 6}, new int[]{6, 1}, "测试失败");
}
5.3 组合断言
assertAll()
assertAll方法可以通过lambda表达式进行判断
@Test
void assertAllTest(){
assertAll("test",
()-> assertArrayEquals(new double[]{1.5,8},new double[]{1,8}),
()-> assertNotSame(8,8));
}
5.4 异常断言
assertThrows()
断定业务逻辑一定出现异常
@DisplayName("异常断言")
@Test
void assertThrowsTest(){
assertThrows(Exception.class,()->{int i = 1/0;},"异常未出现");
}
5.5 超时断言
assertTimeout()
断定在设置的时间里一定会成功超时则出现异常
@DisplayName(“超时断言”)
@Test
void timeOutTest(){
assertTimeout(Duration.ofSeconds(1),()->{Thread.sleep(800);});
}
5.6 快速失败
fail()
通过使用fail方法直接让测试失败
@Test
void failTest(){
fail("我直接失败怎么说?");
}
5.7 前置条件
assumptions
类似于断言,其实就是测试方法执行的前提条件若不满足则无法测试
区别
若不满足前置条件则测试终止而非失败
@DisplayName("前置条件的测试")
@Test
void assumptionTest(){
Assumptions.assumeTrue(false,"前提不是true");
System.out.println("方法执行成功");
}
5.8 嵌套测试
Nested Tests
文档
https://junit.org/junit5/docs/current/user-guide/#writing-tests-nested
@Nested 测试为测试编写者提供了更多的能力来表达多组测试之间的关系。这种嵌套测试使用Java的嵌套类,并促进了对测试结构的分层思考。
注意点
当我们要写嵌套测试的时候要在书写嵌套测试的“大”方法上添加注释@Nested
嵌套测试的情况下,外层的Test无法驱动内层
@Nested
class Outter{
@Test
void test1(){
…
}
@Test
void test2(){
…
}
@Test
void test3(){
…
}
}
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("嵌套测试")
public class NestedTestDemo {
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());
}
}
}
}
5.9 参数化测试
能够使用不同的参数进行多次运行测试
使用注解指定入参
它们的声明方式与常规的 @Test 方法一样,但使用 @ParameterizedTest 注释
文档
https://junit.org/junit5/docs/current/user-guide/#writing-tests-parameterized-tests
参数化测试常用注解
-
@ValueSource
指定入参来源 -
@NullSource
提供一个Null入参 -
@EnumSource
提供一个枚举入参 -
CsvFileSource
提供一个csv文件内容作为参数入参 -
MethodSource
读取指定方法的返回值作为参数化测试的入参(流)
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.EmptySource;
import org.junit.jupiter.params.provider.EnumSource;
import org.junit.jupiter.params.provider.NullSource;
import org.junit.jupiter.params.provider.ValueSource;
import org.junit.platform.commons.util.StringUtils;
import java.time.temporal.ChronoUnit;
import java.time.temporal.TemporalUnit;
import static org.junit.jupiter.api.Assertions.assertNotNull;
import static org.junit.jupiter.api.Assertions.assertTrue;
@DisplayName("参数化测试")
public class ParamTestDemo {
@ParameterizedTest
@ValueSource(ints = { 1, 2, 3 })
void testWithValueSource(int argument) {
assertTrue(argument > 0 && argument < 4);
}
@ParameterizedTest
@ValueSource(strings = { "racecar", "radar", "able was I ere I saw elba" })
void palindromes(String candidate) {
System.out.println(candidate);
}
@ParameterizedTest
@NullSource
@EmptySource
@ValueSource(strings = { " ", " ", "\t", "\n" })
void nullEmptyAndBlankStrings(String text) {
assertTrue(text == null || text.trim().isEmpty());
}
@ParameterizedTest
@EnumSource(ChronoUnit.class)
void testWithEnumSource(TemporalUnit unit) {
assertNotNull(unit);
}
}