Junit5的变化
Spring Boot 2.2.0版本开始引入Junit5作为单元测试默认库
整合SpringBoot之后加入@SpringBootTest可以使用Spring的相关功能如@Autowired
加入@Transactional 标注的 测试方法完成之后会自动回滚
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
Junit5 与之前 Junit框架有很大的不同,Junit 5主要由Junit Platform + Junit jupiter + Junit Vintage组成
- Junit Platform 主要是支持JVM上面启动测试框架,不仅针对Junit自己的测试引擎,其他引擎也可以接入
- Junit Jupiter Junit的核心库,测试引擎,在Junit Platform上运行
- Junit Vintage 用于兼容老版本的Junit 如Junit4 3的用户
注意 在Spring Boot 2.4以上移除了对Vintage 的依赖,以为了使用老的Junit4的模式已经不适用了,如果需要兼容需要自行引入参考
<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>
常用Junit5的注解
- @Test表示方法是测试方法。但是与JUnit4的@Test不同,他的职责非常单一不能声明任何属性,拓展的测试将会由Jupiter提供额外测试
- @ParameterizedTest表示方法是参数化测试,配合参数生成注解如@ValueSource
- @RepeatedTest表示方法可重复执行,传入执行次数参数
- @DisplayName为测试类或者测试方法设置展示名称
- @BeforeEach表示在每个单元测试之前执行 类似Junit4 @Before
- @AfterEach表示在每个单元测试之后执行 类似Junit4 @After
- @BeforeAll表示在所有单元测试之前执行,方法要求是静态的 类似Junit4 @BeforeClass
- @AfterAll表示在所有单元测试之后执行,方法要求是静态的 类似Junit4 @AfterClass
- @Tag表示单元测试类别,类似于JUnit4中的@Categories
- @Disabled表示测试类或测试方法不执行,类似于JUnit4中的@Ignore
- @Timeout表示测试方法运行如果超过了指定时间将会返回错误
- @ExtendWith为测试类或测试方法提供扩展类引用 类似Junit4 的@Runwith
常用示例 注意@Test 需要使用org.junit.jupiter.api包下的注解
package com.corn.junit;
import org.junit.jupiter.api.*;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.*;
import java.util.concurrent.TimeUnit;
import java.util.stream.Stream;
/**
* @author Jim Wu
* @version 1.0
* @function
* @since 2021/2/3 9:46
*/
@DisplayName("单元测试示例")
public class Junit5Test {
@BeforeAll
static void beforeAll() {
System.out.println("运行在所有单元测试之前...");
}
@AfterAll
static void afterAll() {
System.out.println(" 运行在所有单元测试之后 ...");
}
@BeforeEach
public void beforeEach() {
System.out.println(" 单元测试开始了... ");
}
@AfterEach
public void afterEach() {
System.out.println(" 单元测试结束了... ");
}
@DisplayName("简单测试")
@Test
public void simpleTest() {
System.out.println("Simple test");
}
@DisplayName("重复测试")
@RepeatedTest(5)
public void repeatTest() {
System.out.println("重复执行多次");
}
/**
* ParameterizedTest需要配合参数源注解
* <p>
* {@link ValueSource} 可以通可选值中读取参数
* {@link EnumSource} 从枚举中获取参数值
* {@link CsvSource} 从csv中读取
* {@link NullSource} 空参数
* {@link MethodSource} 空集合参数
* {@link EmptySource} 从方法中获取参数 要求方法必须是静态的 要求返回值必须是java.util.stream.Stream流
*/
@DisplayName("参数化测试")
@ParameterizedTest
@ValueSource(ints = {1, 2, 3, 4, 5})
public void parameterizedTest(int i) {
System.out.println(i);
}
@DisplayName("从方法中获取参数化测试")
@ParameterizedTest
@MethodSource(value = "getMethodParam")
public void paramMethodTest(int i) {
System.out.println(i);
}
static Stream<Integer> getMethodParam() {
return Stream.of(11, 22, 33, 44, 55);
}
@DisplayName("被标注Disabled的方法")
@Disabled
@Test
public void testDisable() {
System.out.println("这个方法被标注了@Disabled不会执行");
}
@DisplayName("测试tag单元测试")
@Tag("tag1")
@Test
public void testTag() {
System.out.println("test tag");
}
@DisplayName("带超时的单元测试")
@Timeout(value = 500, unit = TimeUnit.MILLISECONDS)
@Test
public void testTimeout() throws InterruptedException {
TimeUnit.MILLISECONDS.sleep(600);
}
}
断言 Assertions
断言是UnitTest的核心功能,用于断言测试结果是否满足某一个条件的验证.org.junit.jupiter.api.Assertions中的静态方法 所有测试运行结束之后会生成一个测试报告
常用断言API
方法 | 说明 |
---|---|
assertEquals | 判断两个对象或两个原始类型是否相等 |
assertNotEquals | 判断两个对象或两个原始类型是否不相等 |
assertSame | 判断两个对象引用是否指向同一个对象 |
assertNotSame | 判断两个对象引用是否指向不同的对象 |
assertTrue | 判断给定的布尔值是否为 true |
assertFalse | 判断给定的布尔值是否为 false |
assertNull | 判断给定的对象引用是否为 null |
assertNotNull | 判断给定的对象引用是否不为 null |
代码示例
package com.corn.junit;
import org.assertj.core.util.Lists;
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Test;
import java.time.Duration;
import java.util.List;
import java.util.concurrent.TimeUnit;
import static org.hamcrest.MatcherAssert.assertThat;
import static org.hamcrest.collection.IsIterableContainingInAnyOrder.containsInAnyOrder;
/**
* @author Jim Wu
* @version 1.0
* @function Junit5 的Assertion断言机制
* @since 2021/2/3 11:02
*/
public class AssertionTest {
/**
* 断言Boolean
*/
@Test
public void testAssertionBoolean() {
Assertions.assertTrue(true);
Assertions.assertFalse(false);
}
/**
* 断言相等Equal
*/
@Test
public void testAssertEquals() {
Assertions.assertEquals(1, 1);
Assertions.assertNotEquals("dd", "cc");
}
/**
* 断言数组
*/
@Test
public void testAssertArray() {
// 有序比较
int[] arr1 = {1, 2, 3};
int[] arr2 = {1, 2, 3};
Assertions.assertArrayEquals(arr1, arr2);
// 无顺序比较
List<Integer> list1 = Lists.newArrayList(1, 2, 3);
List<Integer> list2 = Lists.newArrayList(3, 1, 2);
// 注意Matcher参数list需要转成toArray 底层是循环出来一个一个判断
assertThat(list1, containsInAnyOrder(list2.toArray()));
}
/**
* 断言异常
*/
@Test
public void testAssertThrows() {
Assertions.assertThrows(RuntimeException.class, () -> {
int i = 1 / 0;
});
Assertions.assertDoesNotThrow(() -> 1 / 1);
}
/**
* 断言超时 如果超时断言失败
*/
@Test
public void testAssertTimeout() {
Assertions.assertTimeout(Duration.ofMillis(300), () -> TimeUnit.MILLISECONDS.sleep(200));
}
/**
* 断言对象是否相等
*/
@Test
public void testAssertSame() {
Object obj1 = new Object();
Object obj2 = new Object();
Assertions.assertSame(obj1, obj1);
Assertions.assertNotSame(obj1, obj2);
}
/**
* 断言 是否为空
*/
@Test
public void testNullValue() {
Assertions.assertNull(null);
Assertions.assertNotNull(1);
}
/**
* 多个断言组合 都成功 断言成成功
*/
@Test
public void testAssertAll() {
Assertions.assertAll("断言所有",
() -> {
int i = 1 / 1;
},
() -> {
int i = 1 / 2;
}
);
}
@DisplayName("快速失败")
@Test
public void quickFail() {
Assertions.fail("快速失败");
}
}
前置条件Assumption
Assumption翻译过来就是假设, 在Junit用于判断前置条件,类似断言功能,不同之处在于如果不满足断言的话,这个测试会失败,而不满足前置条件则该测试方法就会终止执行.前置条件可以看做测试方法执行之前做的一些逻辑处理,如果前置条件不满足就不执行这个测试用例
package com.corn.junit;
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.Assumptions;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.condition.DisabledOnOs;
import org.junit.jupiter.api.condition.OS;
import static org.junit.jupiter.api.Assumptions.assumingThat;
/**
* @author Jim Wu
* @version 1.0
* @function
* @since 2021/2/3 14:34
*/
public class AssumptionTest {
@Test
public void testAssumptionBoolean() {
Assumptions.assumeTrue(false);
System.out.println(123);
}
@Test
public void testAssert() {
Assertions.assertTrue(false);
}
@Test
public void testAssumptionThat() {
assumingThat(true, () -> System.out.println("assumption_success"));
assumingThat(false, () -> System.out.println("assumption_success"));
}
}
上图看到Assumption失败的方法会是禁止标志,表示该方法没有满足某一定条件被忽略ignore,该用例不会往下执行,而Assertion失败的会是一个×,表示测试用例失败
排序测试
@Order注解
package com.corn.junit;
import org.junit.jupiter.api.Order;
import org.junit.jupiter.api.Test;
/**
* @author Jim Wu
* @version 1.0
* @function
* @since 2021/2/3 15:36
*/
public class OrderTest {
@Order(3)
@Test
public void testOrder3() {
System.out.println("test3");
}
@Order(1)
@Test
public void testOrder1() {
System.out.println("test1");
}
@Order(2)
@Test
public void testOrder2() {
System.out.println("test2");
}
}
条件测试
方法 | 用途 |
---|---|
@EnabledOnOs/@DisabledOnOs | 在某一个操作系统上执行或不执行 |
@EnabledOnJre/DisabledOnJre | 在某一个特定JDK环境下运行 |
@EnabledIfSystemProperty/@DisabledSystemProperty | 某一个JVM参数下运行 |
@EnabledIfEnvironmentVariable/@DisabledIfEnvironmentVariable | 某一个环境变量下运行在程序启动的之后可以添加到启动命令后 |
@EnabledIf / @DisabledIf | Spring包下对Junit的扩展支持 支持SpEL表达式#{1==1},新版的Junit5也有了自己的支持到自定义静态方法 |
package com.corn.junit;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.condition.*;
import org.springframework.test.context.junit.jupiter.DisabledIf;
import org.springframework.test.context.junit.jupiter.EnabledIf;
/**
* @author Jim Wu
* @version 1.0
* @function
* @since 2021/2/3 14:54
*/
public class ConditionTest {
/**
* 指定OS上运行
*/
@EnabledOnOs(OS.WINDOWS)
@Test
public void testOS() {
System.out.println("just running on windows os");
}
@DisabledOnOs(OS.WINDOWS)
@Test
public void testDisableOS() {
System.out.println("nor running on windows");
}
@EnabledOnJre(JRE.JAVA_8)
@Test
public void testRunningOnJDK8() {
System.out.println(" just running on JDK8");
}
@DisabledOnJre(JRE.JAVA_8)
@Test
public void testDisableOnJdk8() {
System.out.println(" not running on JDK8");
}
/**
* 系统变量 JVM的启动参数参数
*/
@EnabledIfSystemProperty(named = "os.arch", matches = ".*64.*")
@EnabledIfSystemProperty(named = "file.encoding", matches = "UTF-8")
@Test
public void testSystemProp() {
System.out.println("just running on system prop");
}
/**
* 环境变量 程序启动可以+
*/
@EnabledIfEnvironmentVariable(named = "ooxx", matches = "true")
@Test
public void testEnvProp() {
System.out.println("just running on environment prop ooxx");
}
/**
* 自定义方法计算出条件
*/
@Test
@EnabledIf("#{2*3==6}")
void testEnableIf() {
// ...
}
@Test
@DisabledIf("#{1==1}")
void testDisableIf() {
// ...
}
}
嵌套测试
@Nested
JUnit 5 可以通过 Java 中的内部类和@Nested 注解实现嵌套测试,从而可以更好的把相关的测试方法组织在一起。在内部类中可以使用@BeforeEach 和@AfterEach 注解,而且嵌套的层次没有限制
package com.corn.junit;
import org.junit.jupiter.api.*;
import java.util.EmptyStackException;
import java.util.NoSuchElementException;
import java.util.Stack;
import java.util.concurrent.ThreadLocalRandom;
/**
* @author Jim Wu
* @version 1.0
* @function
* @since 2021/2/3 15:37
*/
@DisplayName("嵌套测试")
public class NestTest {
Stack<Object> stack;
@DisplayName("初始化stack")
@BeforeEach
void iniStack() {
stack = new Stack<>();
}
@Test
public void testEmpty() {
Assertions.assertTrue(stack.isEmpty());
}
@Nested
@DisplayName("里面的Test")
class InterTest {
@Test
public void testNotnull() {
Assertions.assertNotNull(stack);
}
@Test
public void testPopException() {
Assertions.assertThrows(EmptyStackException.class, () -> stack.pop());
}
@Nested
@DisplayName("最内层的Test")
class InterestTest {
@BeforeEach
public void beforeEach() {
stack.push(ThreadLocalRandom.current().nextInt());
}
@AfterEach
public void afterEach() {
stack.clear();
}
@Test
public void testNotNull() {
Assertions.assertNotNull(stack.pop());
}
}
}
}
参数化测试
参数化测试是JUnit5很重要的一个新特性,它使得用不同的参数多次运行测试
参数化测试核心注解@ParameterizedTest 表明这个方法是一个参数化的测试方法
配合常用的参数生成注解
- @ValueSource: 为参数化测试指定入参来源,支持八大基础类以及String类型,Class类型
- @NullSource: 表示为参数化测试提供一个null的入参
- @EnumSource: 表示为参数化测试提供一个枚举入参
- @CsvFileSource:表示读取指定CSV文件内容作为参数化测试入参
- @MethodSource:表示读取指定方法的返回值作为参数化测试入参(注意方法返回需要是一个流)
/**
* ParameterizedTest需要配合参数源注解
* <p>
* {@link ValueSource} 可以通可选值中读取参数
* {@link EnumSource} 从枚举中获取参数值
* {@link CsvSource} 从csv中读取
* {@link NullSource} 空参数
* {@link MethodSource} 空集合参数
* {@link EmptySource} 从方法中获取参数 要求方法必须是静态的 要求返回值必须是java.util.stream.Stream流
*/
@DisplayName("参数化测试")
@ParameterizedTest
@ValueSource(ints = {1, 2, 3, 4, 5})
public void parameterizedTest(int i) {
System.out.println(i);
}
@DisplayName("从方法中获取参数化测试")
@ParameterizedTest
@MethodSource(value = "getMethodParam")
public void paramMethodTest(int i) {
System.out.println(i);
}
static Stream<Integer> getMethodParam() {
return Stream.of(11, 22, 33, 44, 55);
}