JUnit5单元测试(超详细教程)
1.JUnit5版本的出现
相信很多小伙伴们都用过JUnit4,但是自从SpringBoot 2.2.0 版本的出现,JUnit5就作为了单元测试的默认库。
毛哥(博主)我使用的SpringBoot2.6.7,默认的JUnit版本仲裁 5.8.2:
所以今天咱们就来聊聊最新版本JUnit5:
JUnit5与之前版本(JUnit3/4)做了很大的改变。它由三个不同子项目的几个模块组成:
-
JUnit Platform:Junit Platform是在JVM上启动测试框架的基础,不仅支持Junit自制的测试引擎,其他测试引擎也都可以接入。
-
JUnit Jupiter:Junit Jupiter提供了Junit5的新的编程模型,是Junit新特性的核心。内部包含了一个测试引擎,用于在Junit Platform上运行。
-
JUnit Vintage:JUnit Vintage 提供了兼容JUnit4.x,JUnit3.x的测试引擎,可以在Junit5平台上运行JUnit3和4的测试用例。
想了解更多内容:点击去junit5官网
看完之后,相信大家对Junit5有了大概的了解!!!
2.JUnit5的使用
系统环境要求:
- java8及以上
- maven3.3及以上
- idea开发工具
1、导入test的场景
- 如果创建的是maven项目需导入单元测试test的场景:
<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-test</artifactId> <scope>test</scope> </dependency>
- 如果是创建的Spring Initilizr 会自动引入单元测试test的场景,就不需要我们手动添加:
2、JUnit5的常用注解
所有的核心注释基本上都位于 junit-jupiter-api 模块中的 org.junit.jupiter.api 包中。
注解 | 说明 |
---|---|
@Test | 表示该方法是一个测试方法。但是与JUnit4的注解不同是,它没有声明任何属性,因为JUnit Jupiter中的测试扩展是基于它们自己的专用注解来完成的 |
@ParameterizedTest | 表示该方法是一个参数化测试方法 |
@RepeatedTest | 表示方法可重复执行的测试方法 |
@TestFactory | 表示该方法是一个测试工厂 |
@DisplayName | 标注测试名称,用来方便查看 |
@DisplayNameGeneration | 用来生成@DisplayName的注解,配合DisplayNameGeneration类来使用 |
@BeforeEach | 表示在当前类中每个使用@Test、@RepeatedTest、@ParameterizedTest或者@TestFactory注解的方法之前执行 |
@AfterEach | 表示在当前类中每个使用@Test、@RepeatedTest、@ParameterizedTest或者@TestFactory注解的方法之后执行 |
@BeforeAll | 表示在所有使用@Test、@RepeatedTest、@ParameterizedTest或者@TestFactory注解的方法之前执行 |
@AfterAll | 表示在所有使用@Test、@RepeatedTest、@ParameterizedTest或者@TestFactory注解的方法之后执行 |
@Nested | 表示使用了该注解的类是一个内嵌、非静态的测试类(下面会详细解释) |
@Tag | 用于声明过滤测试的tags,该注解可以用在方法或类上,类似于TesgNG的测试或Junit4的分类 |
@Disabled | 表示禁用测试,可以禁用一个测试类或者一个测试方法,类似于Junti4的@Ignore |
@Timeout | 表示测试方法运行如果超过了指定时间将会返回错误 |
@ExtendWith | 为测试类或测试方法提供扩展类引用,可以用于注册自定义 |
… |
```java
//
import org.junit.jupiter.api.Test;//可以这里使用的的是Junit5的Test注解
@Test
@DisplayName("第一次测试")
public void firstName(){
System.out.println("hello world");
}
```
3、JUnit5的 断言(assertions)
断言(assertions):测试方法的核心部分,用来对测试需要满足的条件验证。断言方法都是 org.junit.jupiter.api.Assertions 中的静态方法,可以使用Lambda表达式。断言的主要功能就是检查业务逻辑返回的数据是否合理
JUnit5内置的断言可以分为几个类别:
1. 简单断言
用来对单个数据进行验证,如下方法:
方法 | 说明 |
---|---|
assertEquals | 判断两个对象或两个原始类型是否相等 |
assertNotEquals | 判断两个对象或两个原始类型是否不相等 |
assertSame | 判断两个对象引用是否指向同一个对象 |
assertNotSame | 判断两个对象引用是否指向不同的对象 |
assertTrue | 判断给定的布尔值是否为 true |
assertFalse | 判断给定的布尔值是否为 false |
assertNull | 判断给定的对象引用是否为 null |
assertNotNull | 判断给定的对象引用是否不为 null |
示例代码:
/**
* 注意:断言:前面断言失败,后面的代码都不会执行
*/
@Test
@DisplayName("测试简单断言")
void testSimpleAssertions(){
//测试method:assertEquals
Assertions.assertEquals(1, 1, "不相等");
//测试method:assertNotSame
Assertions.assertNotSame(new String("maoge"),new String("maoge") ,"两个对象不相同" );
//测试method:assertNull
Assertions.assertTrue(1<3, "值为false");
//测试method:assertNotNull
Assertions.assertNotNull(new Object(),"对象不为空");
}
2. 数组断言
数组断言主要是通过 assertArrayEquals 方法来判断两个对象或原始类型的数组是否相等:
示例代码:
@Test
@DisplayName("测试数组断言")
void array(){
Assertions.assertArrayEquals(new String[]{"maoge"}, new String[]{"maoge"},"数组不相同");
}
3. 组合断言
assertAll 方法接受多个 org.junit.jupiter.api.Executable函数式接口的实例作为要验证的断言,可以通过lambda表达式很容易的提供这些断言
示例代码:
@Test
@DisplayName("测试组合断言")
void assertAll(){
/**
* 所有断言全部需要成功方可通行
*/
Assertions.assertAll("assert All",
() -> assertEquals(1, 2 - 1),
() -> assertTrue(true),
() -> assertNotNull(new Object())
);
}
4. 异常断言
相比于Junit4,junit5的测试方法异常的断言方式要比junit4简单很多。Junit5提供了一种新的断言方式:Assertions.assertThrow(),然后配合函数式编程进行使用。
@DisplayName("测试异常断言")
@Test
void testException(){
//断定业务逻辑一定出现异常
assertThrows(ArithmeticException.class, ()->{int i=1/0;},"业务逻辑居然正常运行?");
System.out.println("除数不能为0的原则");
}
5. 超时断言
-
以前的超时断言写法(使用@Timeout注解):
/** * 规定方法的超时时间,超出时间测试出异常 * @throws InterruptedException * * @Timeout参数说明: * value:设置的超时时间 * unit:指定的超时参数单位 */ @Timeout(value = 500,unit = TimeUnit.MILLISECONDS) @Test @DisplayName("测试超时断言") void testTimeout() throws InterruptedException { Thread.sleep(600); }
-
有了Junit5通过 Assertions.assertTimeout() 方法为测试方法设置超时时间,如果测试方法的执行时间大于指定的超时参数,测试方法将抛出异常,测试结果为失败。
/* * assertionTimeout()参数说明: * 参数一:Duration 设置超时时间 * 参数二:函数式对象调用超时的方法 */ @Test @DisplayName("测试超时断言") public void timeoutTest() { //如果测试方法时间超过1s将会异常 Assertions.assertTimeout(Duration.ofMillis(1000), () -> Thread.sleep(500)); }
6. 快速失败
通过fail方法直接让测试失败
@Test
@DisplayName("测试快速失败")
public void shouldFail() {
if (true){
fail("测试失败");
}
}
4、JUnit5的 假设(assumptions)
假设(assumptions)类似于断言,不同之处在于:
- 不满足的断言会使测试方法失败
- 不满足的假设会使测试方法执行终止
所以得出以下总结:假设可以看成是测试方法执行的前提,当该前提不满足时,就没有继续执行的必要。
方法 | 说明 |
---|---|
assumeTrue | 条件为true |
assertNotEquals | 判断两个对象或两个原始类型是否不相等 |
assertSame | 判断两个对象引用是否指向同一个对象 |
assertNotSame | 判断两个对象引用是否指向不同的对象 |
5、嵌套测试(@Nested)
Junit5可以通过@Nested注解和java中内部类实现嵌套测试方法,从而更好的把相关的测试组织在一起。
@Nested测试类必须是非静态内部类,并且可以有任意多层的嵌套。不过@BeforeAll和@AfterAll的方法不能直接在@Nested的测试类中使用(除非"per-class”被使用)
示例代码:
@SpringBootTest
@DisplayName("嵌套测试")
public class Nested_test {
ArrayList<String> list;
@Test
@DisplayName("外层")
void isInstantiatedWithNew(){
// new ArrayList<>();
//外层的test方法不能驱动内层中的@BeforeEach和@AfterEach方法
Assertions.assertNull(list, "list不是空的");
System.out.println("list为空");
}
@BeforeAll
static void beforeAll(){
System.out.println("所有方法执行之前");
}
@AfterAll
static void afterAll(){
System.out.println("所有方法执行之后");
}
//嵌套的第一层
//再次强调:java不允许内部类中存在static成员,而@Before(After)All是修饰static方法的,所以不允许有@Before(After)All.(除非开启了"per-class"模式)
@Nested
@DisplayName("嵌套操作")
class nestedOperate{
@BeforeEach
void beforeEach(){
System.out.println("单个方法调用前");
list=new ArrayList<String>();
}
@AfterEach
void afterEach(){
System.out.println("单个方法调用后");
}
@Test
@DisplayName("list是否为空")
void isEmpty(){
Assertions.assertTrue(list.isEmpty(), "list不为空");
System.out.println("list为空");
}
//嵌套的第二层
@Nested
@DisplayName("list增加一个元素")
class afterAdd{
String element = "springboot2好牛逼";
@BeforeEach
void pushAnElement(){
list.add(element);
}
/**
* 内层的Test可以驱动外层的Before(After)Each之类的方法
*/
@Test
@DisplayName("list是否为空")
void isNotEmpty(){
Assertions.assertFalse(list.isEmpty(),"list为空");
System.out.println("list不为空");
}
}
}
}
6、参数化测试(@ParameterizedTest)
参数化测试是Junit5中的一个新特性,所以不得不提它。
使用 以下注解,使得测试可以测试多次使用不同的参数值,为我们的单元测试带来了许多便利!
注解 | 说明 |
---|---|
@ValueSource | 为参数化测试指定参数源,支持八大基本数据类型以及String、class类型 |
@NullSource | 为参数化测试提供了一个null的参数源 |
@EnumSource | 为参数化测试提供一个Enum参数源,该注解提供了一个可选的names参数,你可以用它来指定使用哪些常量,如果忽略,意味着所有常量将被使用 |
@MethodSource | 表示指定方法的返回值作为参数化测试的参数源 |
@CsvSource | 允许将参数列表定义为以逗号分隔的值(即String类型的值),它使用单引号作为引用字符,一个空的""表示一个空的字符串,而一个’'被当成一个null引用 |
@CsvFileSource | 可以使用类路径下的CSV文件,CSV文件中每一行都会触发参数化测试的一次调用 |
@ArgumentsSource | 用来指定一个自定义且能够复用的ArgumentsProvider |
示例代码:
//@ValueSource示例
@ParameterizedTest
@DisplayName("参数化测试")
@ValueSource(ints = {1,2,3,4,5})
void testParameterized(int i){
System.out.println(i);
}
//@MethodSource示例
@ParameterizedTest
@DisplayName("参数化测试")
@MethodSource("stringProviders")
void testParameterized(String a){
System.out.println(a);
}
static Stream<String> stringProviders(){
return Stream.of("apple","banana","maoge");
}
7、SpringBoot整合JUnit5
想要使用Spring的功能,需要使用@SpringBootTest注解:
@SpringBootTest
class Junti5_test {
@Test
void contextLoads() {
}
}
- 编写测试方法:@Test(JUnit5版本)
- JUnit类具有Spring的功能,@Autowired、比如 使用**@Transactional** 标注测试方法,测试完成后自动回滚
8、如何兼容JUnit4
SpringBoot 2.4 以上版本移除了默认对 Vintage 的依赖。如果需要兼容junit4需要自行引入vintage (兼容版本):
<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>
如果想使用JUnit4的小伙伴想迁移JUnit5,需要注意以下变化:
● 断言在 org.junit.jupiter.api.Assertions 类中,假设在 org.junit.jupiter.api.Assumptions 类中
● 把@Before 和 @After 替换成@BeforeEach 和@AfterEach
● 把@BeforeClass 和@AfterClass 替换成@BeforeAll 和 @AfterAll。
● 把 @Ignore 替换成@Disabled。
● 把 @Category 替换成 @Tag。
● 把 @RunWith、@Rule 和 @ClassRule 替换成@ExtendWith。