SpringBoot对单元测试支持、常用单元测试功能使用实例

共 7796字,需浏览 16分钟

 ·

2022-04-25 07:53

SpringBoot 单元测试

Spring Boot 提供了许多注解和工具帮助开发人员测试应用,在其官方文档中也用了大量篇幅介绍单元测试的使用。在谷歌每周的 TGIF (ThanksGod, it's Friday)员工大会中有一项就是 宣布-周单元测试竞赛获胜的工程师。谷歌之所以这么重视单元测试,就是为了保证程序质量,鼓励大家多写测试代码。国内大多数开发人员对单元测试有所忽视,这也是我写本章内容的原因所在。

本章会围绕 Spring Boot 对单元测试的支持、常用单元测试功能的使用实例以及 MockMvc的自动配置机制展开。

Spring Boot 对单元测试的支持

Spring Boot 对单元测试的支持重点在于提供了-系列注解和工具的集成,它们是通过两个项目提 供 的 : 包 含 核 心 功 能 的 spring-boot-test 项 目 和 支 持 自 动 配 置 的
spring-boot-test-autoconfigure.

通常情况下,我们通过 spring-boot-starter-test 的 Starter 来引入 SpringBoot 的核心支持项目以及单元测试库。spring-boot-starter-test 包 含的类库如 JUnit:一个 Java 语言的单元测试框架。

Spring Test & Spring Boot Test:为 Spring Boot 应用提供集成测试和工具支持。

AssertJ:支持流式断言的 Java 测试框架。

.Hamcrest: 一个匹配器库。

Mockito :一个 Java Mock 框架。

JSONassert:一个针对 JSON 的断言库。

JsonPath:一个 JSON XPath 库。

如果 Spring Boot 提供的基础类库无法满足业务需求,我们也可以自行添加依赖。依赖注入的优点之一就是可以轻松使用单元测试。这种方式可以直接通过 new 来创建对象,而不需要涉及 Spring。当然,也可以通过模拟对象来替换真实依赖。

如果需要集成测试,比如使用 Spring 的 ApplicationContext, Spring 同样能够提供无须部署应 用 程 序 或 连 接 到 其 他 基 础 环 境 的 集 成 测 试 。而 SpringBoot 应 用 本 身 就 是 一 个ApplicationContext,因此除了正常使用 Spring.上下文进行测试,无须执行其他操作。

常用单元测试注解

以 Junit 为例,在单元测试中会常用到一些注解,比如 Spring Boot 提供的@SpringBootTest

@MockBean、@SpyBean 、@WebMvcTest@AutoConfigureMockMvc 以及 Junit 提供的@RunWith 等。下面以- 一个简单的订单插入的功能示例进行说明。

@RunWith(SpringRunner .class)
public class OrderServiceTest
{
@Autowired
private OrderService orderService;
ublic void testInsert() {Order order = new Order()
order . setOrderNo("A001");
order. setUserId(100);
orderService. insert (order);
}
}

我们先来看 Junit 中的@RunWith 注解,该注解用于说明此测试类的运行者,比如示例中使用 的 SpringRunner 。SpringRunner 是 由 spring-test 提 供 的 , 它 实 际 上 继 承 了SpringJUnit4ClassRunner 类,并且未重新定义任何方法,我们可以将 SpringRunner 理解为 SpringJUnit4ClassRunner 更简洁的名字。

@SpringBootTest 注解由 Spring Boot 提供,该注解为 SpringApplication 创建上下文并支持 Spring Boot 特性。

该测试项目中引入了 spring-boot-starter-test 依赖,默认情况下此依赖使用的单元测试类库为 J∪nit4,此时@SpringBootTest 注解需要配合@RunWith(SpringRunner.class)注解使用,否则注解会被忽略。

查看@SpringBootTest 注解的源码,会发现其内部枚举类 WebEnvironment 提供了支持的多种单元测试模式。

@Target(ElementType. TYPE)
@Retention(Retent ionPolicy . RUNTIME)
@Documented
@Inherited
@BootstrapWith(SpringBootTestContextBootstrapper. class)
@ExtendWith(SpringExtension. class)
public @interface SpringBootTest {
@AliasFor("properties")
String[] value() default {};
@AliasFor("value" )
String[] properties() default {};
String[] args() default {};
Class[] classes() default {};
WebEnvi ronment webEnvironment() default WebEnvironment . MOCK;
enum WebEnvironment {
MOCK(false),
RANDOM PORT(true)
DEFINED_ PORT(true),
NONE(false); } }

从@SpringBootTest 的源代码中可以看出,通过 WebEnvironment 枚举类提供了 MOCK、RANDOM_ PORT、DEFINED_ PORT 和 NONE 这 4 种环境配置。

:Mock:加载 WebApplicationContext 并提供 Mock Servlet 环境,嵌入的 Servlet 容器不会被启动。

:RANDOM_ PORT:加载一个
EmbeddedWebApplicationContext 并提供真实的 Servlet 环境。嵌入的 Servlet 容器将被启动,并在一个随机端口上监听。

:DEFINED_ PORT:加载一个
EmbeddedWebApplicationContext 并提供真实的 Servlet 环境。嵌入的 Servlet 容器将被启动,并在一个默认的端口上监听(application.properties 配 置端口或者默认端口 8o8o)。

:NONE:使用 SpringApplication 加载一个 ApplicationContext,但是不提供任何 Servlet 环境。

示例中默认采用此种方式。

关于其他的注解就不再展开了,在后面章节中会结合具体示例进行说明。


JUnit5 单元测试示例

在上节中已经提到 JUnit5 与 JUnit4 有所不同,本节还是用同样的示例来看一下 JUnit5 的使用。

@SpringBootTest
public class OrderServiceTest {
@Resource
private OrderService orderService;
@Test
public void testInsert()
Order order
= new Order();
order . setOrderNo( "A001");
order . setUserId(100);
orderService . insert (order);}

通过上面的代码,我们可以看出默认情况下只需要使用@SpringBootTest 注解即可,而在上节@SpringBootTest 源代码中已经看到组合了@ExtendWith(SpringExtension.class)注解,因此此示例无须注解。

这里需要注意的是 Spring Boot 的版本信息,在 2.1.x 之后@SpringBootTest 注解中才组合了@ExtendWith(SpringExtension.class)注解。因此,需要根据具体使用的版本来确定是否需要@ExtendWith(SpringExtension.class)注解,否则可能会出现注解无效的情况虽然单元测试类的代码与 JUnit4 基本相同,但本质上还是有区别的。比如,在使用 JUnit5时, 默认的 spring-boot- starter-test 依赖类库已经无法满足,需要手动引|入 junit-jupiter.


<dependency><groupId>org . junit. jupitergroupId>
<artifactId>junit - jupiterartifactId>
<version>5.5.2version>
<scope>testscope>
/ dependency>

同时,如果必要则需要将 junit-vintage-engine 进行排除。

<dependency>
<groupId>org. springframework . bootgroupId>
<artifactId>spring-boot-starter-testartifactId>
<scope>testscope>
<exclusions>
<exclusion>
<groupId>org. junit . vintagegroupId>
<artifactId>junit -vintage - engineartifactId>
exc lusion>
exclusions>
dependency>

上面的测试代码还有一个经常会遇到的问题,就是从 JUnit4 升级到 JUnit5 时,如果你只是把类上的注解换了,会发现通过@Resource 或@Autowired 注入的 OrderService 会抛出空指针异常。这是为什么呢?

原因很简单,从 JUnit4 升级到 JUnit5 时,在 testInsert 方法 上的@Test 注解变了。在 JUnit4中默认使用的@Test 注解为 org.junit.Test,而在 JUnit5 中需要使用
org.junit.jupiter.api.Test.因此,如果在升级的过程中出现莫名其妙的空指针异常时,需考虑到此处。

总体来说,JUnit5 的最大变化是 @Test 注解改为由几个不同的模块组成,其中包括 3 个不同子项目: JUnit Platform、JUnit Jupiter 和 JUnit Vintage.同时,JUnit5 也提供了一套自己的注解。

.@ BeforeAll 类似于 JUnit 4 的@BeforeAll,表示使用了该注解的方法应该在当前类中所有使用了@Test、@ RepeatedTest、@ ParameterizedTest 或者@TestFactory 注解的方法之前执行,且必须为 static。

.@ BeforeEach 类似于 JUnit 4 的@Before,表示使用了该注解的方法应该在当前类中所有使用了@Test、@ RepeatedTest、@ ParameterizedTest 或者@TestFactory 注解的方法之前执行。

.@Test 表示该方法是一个测试方法。

.@ DisplayName 为测试类或测试方法声明一个自定义的显示名称。

.@AfterEach 类似于 JUnit 4 的@After,表示使用了该注解的方法应该在当前类中所有使用了@Test、@RepeatedTest 、@ ParameterizedTest 或者@ TestFactory 注解的方法之后执行。

.@AfterAll 类似于 JUnit 4 的@AfterClass, 表示使用了该注解的方法应该在当前类中所有使用了@Test、@RepeatedTest、 @ ParameterizedTest 或者@ TestFactory 注解的方法之后执行,且必须为 static。

.@Disable 用于禁用一个测试类或测试方法,类似于 JUnit 4 的@Ignore.

.@ExtendWith 用于注册自定义扩展功能。

关于这些注解的详细使用,我们就不一一举例了。


Web 应用单元测试

在面向对象的程序设计中,模拟对象(mock object)是以可控的方式模拟真实对象行为的假对象。在编程过程中,通常通过模拟一些输入数据,来验证程序是否达到预期效果。

模拟对象-般应用于真实对象有以下特性的场景:行为不确定、真实环境难搭建、行为难触发、速度很慢、需界面操作、回调机制等。

在上面章节中实现了 Service 层的单元测试示例,而当对 Controller 层进行单元测试时,便需要使用模拟对象,这里采用 spring-test 包中提供的 MockMvc。MockMvc 可以做到不启动项目工程就可以对接口进行测试。

MockMvc 实现了对 HTTP 请求的模拟,能够直接使用网络的形式,转换到 Controller 的调用,这样可以使得测试速度快、不依赖网络环境,同时提供了一套验证的工具, 使得请求的验证统-一而且方便。

下面以一个具体的示例来对 MockMvc 的使用进行讲解。在使用之前,依旧需要先引入对应的依赖。


org. springfr amework . boot
spring boot - starter- test
test

这里创建一一个简单的 TestController,提供一个 hello 方法, 返回一个字符串。
@RestController
public class TestController {
@RequestMapping(" /mock" )
public String mock(String name) {
return "Hello ”+ name + "!"; }}

下面编写单元测试的类和方法,我们这里都采用基于 JUnit4 和 SpringBoot 2.x 版本进行操作。

@RunWith(SpringRunner . class)@SpringBootTest
@AutoConfigureMockMvc
public class TestControllerTest {
@Autowired
private MockMvc mockMvc;
@Test
public void testMock() throws Exception {
// mockMvc. perform 执行一 个请求
mockMvc . perform(MockMvcRequestBuilders
// MockMvcRequestBuilders. get( "XX")构造一个请求. get(" /mock")
//设置返回值类型为 utf-8, 否则默认为 ISO- 8859-1
. accept (MediaType . APPLICATION_ JSON_ _UTF8_ _VALUE)
/ ResultActions . param 添加请求传值
. param( "name", "MockMvc"))
// Resul tActions . andExpect 添加执行完成后的断言
. andExpect (MockMvcResultMatchers . status(). isOk())
. andExpect (MockMvcResultMatchers . content(). string("Hello MockMvc!"))
// Resul tActions . andDo 添加一一个结果处理器,此处打印整个响应结果信息
. andDo(MockMvcResultHandlers . print());
}
}

执行该单元测试打印结果部分内容如下。

MockHttpServletRequest:
HTTP Method
GET
Request URI = /mock
Parameters = {name=[MockMvc]}
Headers = [Accept:" application/json; charset=UTF-8" ]
Body = null
Session Attrs = {}
Handler:
Type = com. secbro2. learn. controller. TestController
Method = public java. lang . String com. secbro2 . learn. controller .Test
Controller .mock(java. lang. String)
MockHttpServletResponse:
Status = 200Error message = nul
Headers = [Content -Type :"application/json;charset=UTF-8", Content-Length:
"14" ]
Content type = application/json;charset-UTF-8
Body = Hello MockMvc!

在以上单元测试中,@RunWith(SpringRunner. class )和@SpringBootTest 的作用我们已经知道,另外的@AutoConfigureMockMvc 注解提供了自动配置 MockMvc 的功能。因此,只需通过@Autowired 注入 MockMvc 即可。

MockMvc 对象也可以通过接口 MockMvcBuilder 的实现类来获得。该接口提供一个唯一的build 方法来构造 MockMvc。主要有两个实现类:

StandaloneMockMvcBuilder 和 DefaultMockMvcBuilder,分别对应两种测试方式,即独立安装和集成 Web 环境测试(并不会集成真正的 web 环境,而是通过相应的 Mock API 进行模拟测 试 , 无 须 启 动 服 务 器 ) 。MockMvcBuilders 提 供 了 对 应 的 standaloneSetup 和webAppContextSetup 两种创建方法,在使用时直接调用即可。MockMvc 对象的创建默认使用 DefaultMockMvcBuilder,后面章节会详细介绍这一过程。

整个单元测试包含以下步骤:准备测试环境、执行 MockMvc 请求、 添加验证断言、添加结果处理器、得到 MvcResult 进行自定义断言/进行下一步的异步请求、卸载测试环境。

关于 Web 应用的测试,还有许多其他内容,比如:检测 Web 类型、检测测试配置、排除测试配置以及事务回滚(通过@ Transactional 注解),读者朋友可根据需要自行编写单元测试用例进行尝试。


本文给大家讲解的内容是SpringBoot对单元测试支持、常用单元测试功能使用实例

  1. 下篇文章给大家讲解的是MockMvc的自动配置;

  2. 觉得文章不错的朋友可以转发此文关注小编;

  3. 感谢大家的支持!

本文就是愿天堂没有BUG给大家分享的内容,大家有收获的话可以分享下,想学习更多的话可以到微信公众号里找我,我等你哦。

浏览 31
点赞
评论
收藏
分享

手机扫一扫分享

分享
举报
评论
图片
表情
推荐
点赞
评论
收藏
分享

手机扫一扫分享

分享
举报