当前位置:网站首页>Boot的单元测试
Boot的单元测试
2022-06-26 18:40:00 【51CTO】
1、JUnit5 的变化
- Spring Boot 2.2.0 版本开始引入 JUnit 5 作为单元测试默认库
- 作为最新版本的JUnit框架,JUnit5与之前版本的Junit框架有很大的不同。由三个不同子项目的几个不同模块组成。
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的测试引擎。

- 注意:
- SpringBoot 2.4 以上版本移除了默认对 Vintage 的依赖。如果需要兼容junit4需要自行引入(不能使用junit4的功能 @Test)
- JUnit 5’s Vintage Engine Removed from
spring-boot-starter-test,如果需要继续兼容junit4需要自行引入vintage
引入junit场景启动器
添加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
>
- 1.
- 2.
- 3.
- 4.
- 5.
- 6.
- 7.
- 8.
- 9.
- 10.
- 11.
- 12.
现在版本:
- 以前:
- SpringBoot整合Junit5以后。
- @SpringBootTest 有spring的一些自动注入等功能
- 编写测试方法:@Test标注(注意需要使用junit5版本的注解)
- Junit类具有Spring的功能,@Autowired、比如 @Transactional 标注测试方法,测试完成后自动回滚
2、JUnit5常用注解
JUnit5的注解与JUnit4的注解有所变化
官方文档: https://junit.org/junit5/docs/current/user-guide/#writing-tests-annotations
- @Test : 表示方法是测试方法。但是与JUnit4的@Test不同,他的职责非常单一不能声明任何属性,拓展的测试将会由Jupiter提供额外测试
- @ParameterizedTest : 表示方法是参数化测试,下方会有详细介绍
- @RepeatedTest : 表示方法可重复执行,下方会有详细介绍
- @DisplayName : 为测试类或者测试方法设置展示名称
- @BeforeEach : 表示在每个单元测试之前执行
- @AfterEach : 表示在每个单元测试之后执行
- @BeforeAll : 表示在所有单元测试之前执行
- @AfterAll : 表示在所有单元测试之后执行
- @Tag : 表示单元测试类别,类似于JUnit4中的 @Categories
- @Disabled : 表示测试类或测试方法不执行,类似于JUnit4中的 @Ignore
- @Timeout : 表示测试方法运行如果超过了指定时间将会返回错误
- @ExtendWith : 为测试类或测试方法提供扩展类引用
//加入springboot的功能,加载springboot整个
(
"junit5单元测试")
public
class
junit5Text1 {
UserServiceImpl
userServiceImpl;
(
"测试方法一")
public
void
text(){
System.
out.
println(
"哈哈");
}
(
"测试方法二")
//把这个方法禁用掉,不会运行
public
void
text1(){
System.
out.
println(
"呵呵");
}
//每个单元测试方法之前都会运行
void
text2(){
System.
out.
println(
"测试就要开始了!!");
}
//每个单元测试方法结束后都会运行
void
text3() {
System.
out.
println(
"测试结束啦!!");
}
//所有单元测试之前会运行
//静态的方法
//在运行一次单元测试的时候,最前面和最后面运行
static
void
text4() {
System.
out.
printf(
"所有测试开始啦!!");
}
//所有单元测试结束后运行
static
void
text5() {
System.
out.
println(
"所有测试结束了!!");
}
//如果超过了指定时间将会返回超时异常错误
(
value
=
500,
unit
=
TimeUnit.
MILLISECONDS)
//value为数值,unit为单位
void
text6() {
try {
Thread.
sleep(
600);
}
catch (
InterruptedException
e) {
e.
printStackTrace();
}
}
void
text7() {
System.
out.
println(
userServiceImpl);
}
//重复测试注解
(
5)
//重复次数
void
text8() {
System.
out.
println(
"a");
}
}
- 1.
- 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.
3、断言(assertions)
- 断言(assertions)是测试方法中的核心部分,用来对测试需要满足的条件进行验证。
- 检查业务逻辑返回的数据是否合理。
- 所有的测试运行结束以后,会有一个详细的测试报告;
- 前面断言失败,后面断言不会执行
- 这些断言方法都是 org.junit.jupiter.api.Assertions 的静态方法。
- JUnit 5 内置的断言可以分成如下几个类别:
- 简单断言、组合断言、数组断言、异常断言、超时断言、快速失败
1、简单断言
用来对单个值进行简单的验证。如:
方法 | 说明 |
assertEquals | 判断两个对象或两个原始类型是否相等 |
assertNotEquals | 判断两个对象或两个原始类型是否不相等 |
assertSame | 判断两个对象引用是否指向同一个对象 |
assertNotSame | 判断两个对象引用是否指向不同的对象 |
assertTrue | 判断给定的布尔值是否为 true |
assertFalse | 判断给定的布尔值是否为 false |
assertNull | 判断给定的对象引用是否为 null |
assertNotNull | 判断给定的对象引用是否不为 null |
(
"simple assertion")
public
void
simple() {
assertEquals(
3,
1
+
2,
"simple math");
assertNotEquals(
3,
1
+
1);
assertNotSame(
new
Object(),
new
Object());
Object
obj
=
new
Object();
assertSame(
obj,
obj);
assertFalse(
1
>
2);
assertTrue(
1
<
2);
assertNull(
null);
assertNotNull(
new
Object());
}
- 1.
- 2.
- 3.
- 4.
- 5.
- 6.
- 7.
- 8.
- 9.
- 10.
- 11.
- 12.
- 13.
- 14.
- 15.
- 16.
2、数组断言
通过 assertArrayEquals 方法来判断两个对象或原始类型的数组是否相等
3、组合断言
assertAll 方法接受多个 org.junit.jupiter.api.Executable 函数式接口的实例作为要验证的断言,可以通过 lambda 表达式很容易的提供这些断言
4、异常断言
在JUnit4时期,想要测试方法的异常情况时,需要用 @Rule 注解的ExpectedException变量还是比较麻烦的。而JUnit5提供了一种新的断言方式Assertions.assertThrows() ,配合函数式编程就可以进行使用。
5、超时断言
Junit5还提供了Assertions.assertTimeout()为测试方法设置了超时时间
6、快速失败
通过 fail() 方法直接使得测试失败
4、前置条件(assumptions)
- JUnit 5 中的前置条件(assumptions【假设】)类似于断言,
- 不同断言之处在于不满足的断言会使得测试方法失败,而不满足的前置条件只会使得测试方法的执行终止。
- 前置条件可以看成是测试方法执行的前提,当该前提不满足时,就没有继续执行的必要。
(
"前置条件")
public
class
AssumptionsTest {
private
final
String
environment
=
"DEV";
(
"simple")
public
void
simpleAssume() {
assumeTrue(
Objects.
equals(
this.
environment,
"DEV"));
assumeFalse(()
->
Objects.
equals(
this.
environment,
"PROD"));
}
(
"assume then do")
public
void
assumeThenDo() {
assumingThat(
Objects.
equals(
this.
environment,
"DEV"),
()
->
System.
out.
println(
"In DEV")
);
}
}
- 1.
- 2.
- 3.
- 4.
- 5.
- 6.
- 7.
- 8.
- 9.
- 10.
- 11.
- 12.
- 13.
- 14.
- 15.
- 16.
- 17.
- 18.
- 19.
- 20.
- assumeTrue 和 assumFalse 确保给定的条件为 true 或 false,不满足条件会使得测试执行终止
- assumingThat 的参数是表示条件的布尔值和对应的 Executable 接口的实现对象。
- 只有条件满足时,Executable 对象才会被执行;当条件不满足时,测试执行并不会终止。
5、嵌套测试
- JUnit 5 可以通过 Java 中的内部类和@Nested 注解实现嵌套测试,从而可以更好的把相关的测试方法组织在一起。
- 在内部类中可以使用@BeforeEach 和@AfterEach 注解,而且嵌套的层次没有限制。
- 在内部类外面的方法执行,不会执行内部类的@BeforeEach 和@AfterEach方法
- 内部类中的方法执行,即会驱动外面的的@BeforeEach 和@AfterEach方法,也会驱动内部类中的@BeforeEach 和@AfterEach方法
(
"A stack")
class
TestingAStackDemo {
Stack
<
Object
>
stack;
(
"is instantiated with new Stack()")
void
isInstantiatedWithNew() {
new
Stack
<>();
}
(
"when new")
class
WhenNew {
void
createNewStack() {
stack
=
new
Stack
<>();
}
(
"is empty")
void
isEmpty() {
assertTrue(
stack.
isEmpty());
}
(
"throws EmptyStackException when popped")
void
throwsExceptionWhenPopped() {
assertThrows(
EmptyStackException.
class,
stack::
pop);
}
(
"throws EmptyStackException when peeked")
void
throwsExceptionWhenPeeked() {
assertThrows(
EmptyStackException.
class,
stack::
peek);
}
(
"after pushing an element")
class
AfterPushing {
String
anElement
=
"an element";
void
pushAnElement() {
stack.
push(
anElement);
}
(
"it is no longer empty")
void
isNotEmpty() {
assertFalse(
stack.
isEmpty());
}
(
"returns the element when popped and is empty")
void
returnElementWhenPopped() {
assertEquals(
anElement,
stack.
pop());
assertTrue(
stack.
isEmpty());
}
(
"returns the element when peeked but remains not empty")
void
returnElementWhenPeeked() {
assertEquals(
anElement,
stack.
peek());
assertFalse(
stack.
isEmpty());
}
}
}
}
- 1.
- 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.
6、参数化测试
- 参数化测试是JUnit5很重要的一个新特性,它使得用不同的参数多次运行测试成为了可能,也为我们的单元测试带来许多便利。@ParameterizedTest 指定当前方法是一个参数化方法
- 利用 @ValueSource 等注解,指定入参,我们将可以使用不同的参数进行多次单元测试,而不需要每新增一个参数就新增一个单元测试,省去了很多冗余代码。
- @ValueSource : 为参数化测试指定入参来源,支持八大基础类以及String类型,Class类
- @NullSource : 表示为参数化测试提供一个null的入参
- @EnumSource : 表示为参数化测试提供一个枚举入参
- @CsvFileSource :表示读取指定CSV文件内容作为参数化测试入参
- @MethodSource :表示读取指定方法的返回值作为参数化测试入参(注意方法返回需要是一个流)
当然如果参数化测试仅仅只能做到指定普通的入参还达不到让我觉得惊艳的地步。让我真正感到他的强大之处的地方在于他可以支持外部的各类入参。如:CSV,YML,JSON 文件甚至方法的返回值也可以作为入参。只需要去实现ArgumentsProvider接口,任何外部文件都可以作为它的入参。
//代表当前的测试方法是参数化测试
(
strings
= {
"one",
"two",
"three"})
//参数的传递,传递一个参数执行一次方法
(
"参数化测试1")
public
void
parameterizedTest1(
String
string) {
System.
out.
println(
string);
Assertions.
assertTrue(
StringUtils.
isNotBlank(
string));
}
(
"method")
//指定方法名
(
"方法来源参数")
public
void
testWithExplicitLocalMethodSource(
String
name) {
System.
out.
println(
name);
Assertions.
assertNotNull(
name);
}
static
Stream
<
String
>
method() {
return
Stream.
of(
"apple",
"banana");
}
- 1.
- 2.
- 3.
- 4.
- 5.
- 6.
- 7.
- 8.
- 9.
- 10.
- 11.
- 12.
- 13.
- 14.
- 15.
- 16.
- 17.
- 18.
- 19.
- 20.
7、迁移指南
从junit4进行迁移的时候需要注意如下的变化:
- 注解在 org.junit.jupiter.api 包中,断言在 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。
边栏推荐
- Microservice architecture
- ARM裸板调试之串口打印及栈初步分析
- Map and list < map > transfer to corresponding objects
- The cross compilation environment appears So link file not found problem
- The eigen library calculates the angle between two vectors
- Filebeat安装及使用
- LeetCode 238 除自身以外数组的乘积
- LeetCode 128最长连续序列
- (multi threading knowledge points that must be mastered) understand threads, create threads, common methods and properties of using threads, and the meaning of thread state and state transition
- Which securities company is better for a novice to open a stock trading account? How is it safer to speculate in stocks??
猜你喜欢
随机推荐
Using recursion to find all gray codes with n bits
元宇宙链游开发案例版 NFT元宇宙链游系统开发技术分析
软考备战多媒体系统
Idea collection code, quickly open favorites collection window
读书笔记:《过程咨询 III》
ros::spinOnce()和ros::spin()的使用和区别
转:实事求是
Project practice 6: distributed transaction Seata
PC end records 515 ground sweeping robot /scan data
Redis single sign on system + voting system
Boyun, standing at the forefront of China's container industry
Redis Basics
sql中的几种删除操作
Why don't I recommend going to sap training institution for training?
Deep understanding of MySQL lock and transaction isolation level
Eigen库计算两个向量夹角
Clion编译catkin_ws(ROS工作空间包的简称)加载CMakeLists.txt出现的问题
8VC Venture Cup 2017 - Final Round C. Nikita and stack
Paging query and join Association query optimization
The eigen library calculates the angle between two vectors









