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判断给定的布尔值是否为 trueassertFalse判断给定的布尔值是否为 falseassertNull判断给定的对象引用是否为 nullassertNotNull判断给定的对象引用是否不为 null
**示例代码:**
/**
- 注意:断言:前面断言失败,后面的代码都不会执行
*/@Test@DisplayName("测试简单断言")voidtestSimpleAssertions(){//测试method:assertEqualsAssertions.assertEquals(1,1,"不相等");//测试method:assertNotSameAssertions.assertNotSame(newString("maoge"),newString("maoge"),"两个对象不相同");//测试method:assertNullAssertions.assertTrue(1<3,"值为false");//测试method:assertNotNullAssertions.assertNotNull(newObject(),"对象不为空");}
### 2. 数组断言
数组断言主要是通过 **assertArrayEquals** 方法来判断两个对象或原始类型的数组是否相等:
**示例代码:**
@Test@DisplayName("测试数组断言")voidarray(){Assertions.assertArrayEquals(newString[]{"maoge"},newString[]{"maoge"},"数组不相同");}
### 3. 组合断言
**assertAll** 方法接受多个 **org.junit.jupiter.api.Executable**函数式接口的实例作为要验证的断言,可以通过lambda表达式很容易的提供这些断言
**示例代码:**
@Test@DisplayName("测试组合断言")voidassertAll(){/**
* 所有断言全部需要成功方可通行
*/Assertions.assertAll("assert All",()->assertEquals(1,2-1),()->assertTrue(true),()->assertNotNull(newObject()));}
### 4. 异常断言
相比于Junit4,junit5的测试方法异常的断言方式要比junit4简单很多。Junit5提供了一种新的断言方式:Assertions.assertThrow(),然后配合函数式编程进行使用。
@DisplayName("测试异常断言")@TestvoidtestException(){//断定业务逻辑一定出现异常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("测试超时断言")voidtestTimeout()throwsInterruptedException{Thread.sleep(600);}```
- 有了**Junit5**通过 **Assertions.assertTimeout()** 方法为测试方法设置超时时间,如果测试方法的执行时间大于指定的超时参数,测试方法将抛出异常,测试结果为失败。```/* * assertionTimeout()参数说明: * 参数一:Duration 设置超时时间 * 参数二:函数式对象调用超时的方法 */@Test@DisplayName("测试超时断言")publicvoidtimeoutTest(){//如果测试方法时间超过1s将会异常Assertions.assertTimeout(Duration.ofMillis(1000),()->Thread.sleep(500));}```
### 6. 快速失败
通过**fail**方法直接让测试失败
@Test@DisplayName("测试快速失败")publicvoidshouldFail(){if(true){fail("测试失败");}}
## 4、JUnit5的 假设(assumptions)
假设(assumptions)类似于断言,不同之处在于:
- **不满足的断言会使测试方法失败**
- **不满足的假设会使测试方法执行终止**
所以得出以下总结:**假设可以看成是测试方法执行的前提,当该前提不满足时,就没有继续执行的必要。**
方法说明assumeTrue条件为trueassertNotEquals判断两个对象或两个原始类型是否不相等assertSame判断两个对象引用是否指向同一个对象assertNotSame判断两个对象引用是否指向不同的对象
## 5、嵌套测试(@Nested)
Junit5可以通过@Nested注解和java中内部类实现嵌套测试方法,从而更好的把相关的测试组织在一起。
**@Nested**测试类必须是非静态内部类,并且可以有任意多层的嵌套。**不过@BeforeAll和@AfterAll的方法不能直接在@Nested的测试类中使用(除非"per-class”被使用)**
**示例代码:**
@SpringBootTest@DisplayName("嵌套测试")publicclassNested_test{ArrayList<String> list;@Test@DisplayName("外层")voidisInstantiatedWithNew(){// new ArrayList<>();//外层的test方法不能驱动内层中的@BeforeEach和@AfterEach方法Assertions.assertNull(list,"list不是空的");System.out.println("list为空");}@BeforeAllstaticvoidbeforeAll(){System.out.println("所有方法执行之前");}@AfterAllstaticvoidafterAll(){System.out.println("所有方法执行之后");}//嵌套的第一层//再次强调:java不允许内部类中存在static成员,而@Before(After)All是修饰static方法的,所以不允许有@Before(After)All.(除非开启了"per-class"模式)@Nested@DisplayName("嵌套操作")class nestedOperate{@BeforeEachvoidbeforeEach(){System.out.println("单个方法调用前");
list=newArrayList<String>();}@AfterEachvoidafterEach(){System.out.println("单个方法调用后");}@Test@DisplayName("list是否为空")voidisEmpty(){Assertions.assertTrue(list.isEmpty(),"list不为空");System.out.println("list为空");}//嵌套的第二层@Nested@DisplayName("list增加一个元素")class afterAdd{String element ="springboot2好牛逼";@BeforeEachvoidpushAnElement(){
list.add(element);}/**
* 内层的Test可以驱动外层的Before(After)Each之类的方法
*/@Test@DisplayName("list是否为空")voidisNotEmpty(){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})voidtestParameterized(int i){System.out.println(i);}//@MethodSource示例@ParameterizedTest@DisplayName("参数化测试")@MethodSource("stringProviders")voidtestParameterized(String a){System.out.println(a);}staticStream<String>stringProviders(){returnStream.of("apple","banana","maoge");}
## 7、SpringBoot整合JUnit5
想要使用Spring的功能,需要使用@SpringBootTest注解:
@SpringBootTestclassJunti5_test{@TestvoidcontextLoads(){}}
- 编写测试方法:@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。
```
版权归原作者 springboot大神 所有, 如有侵权,请联系我们删除。