前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >SpringBoot对单元测试支持、常用单元测试功能使用实例

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

作者头像
愿天堂没有BUG
发布2022-10-28 16:25:16
1.7K0
发布2022-10-28 16:25:16
举报
文章被收录于专栏:愿天堂没有BUG(公众号同名)

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 等。下面以- 一个简单的订单插入的功能示例进行说明。

代码语言:javascript
复制
@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 提供了支持的多种单元测试模式。

代码语言:javascript
复制
@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 的使用。

代码语言:javascript
复制
@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.

代码语言:javascript
复制
<!-- Junit 5 -->
<dependency><groupId>org . junit. jupiter</ groupId>
<artifactId>junit - jupiter</ artifactId>
<version>5.5.2</version>
<scope>test</scope>
/ dependency>

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

代码语言:javascript
复制
<dependency>
<groupId>org. springframework . boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
<exclusions>
<exclusion>
<groupId>org. junit . vintage</ groupId>
<artifactId>junit -vintage - engine</artifactId>
</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 的使用进行讲解。在使用之前,依旧需要先引入对应的依赖。

代码语言:javascript
复制
<dependency>
<groupId>org. springfr amework . boot</ groupId>
<artifactId>spring boot - starter- test</ artifactId>
<scope>test</scope>
</ dependency>
这里创建一一个简单的 TestController,提供一个 hello 方法, 返回一个字符串。
@RestController
public class TestController {
@RequestMapping(" /mock" )
public String mock(String name) {
return "Hello ”+ name + "!"; }}

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

代码语言:javascript
复制
@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());
}
}

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

代码语言:javascript
复制
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给大家分享的内容,大家有收获的话可以分享下,想学习更多的话可以到微信公众号里找我,我等你哦。

本文参与 腾讯云自媒体同步曝光计划,分享自微信公众号。
原始发表:2022-04-20,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 愿天堂没有BUG 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • SpringBoot 单元测试
  • Spring Boot 对单元测试的支持
  • 常用单元测试注解
  • JUnit5 单元测试示例
  • Web 应用单元测试
  • 本文给大家讲解的内容是SpringBoot对单元测试支持、常用单元测试功能使用实例
相关产品与服务
容器服务
腾讯云容器服务(Tencent Kubernetes Engine, TKE)基于原生 kubernetes 提供以容器为核心的、高度可扩展的高性能容器管理服务,覆盖 Serverless、边缘计算、分布式云等多种业务部署场景,业内首创单个集群兼容多种计算节点的容器资源管理模式。同时产品作为云原生 Finops 领先布道者,主导开源项目Crane,全面助力客户实现资源优化、成本控制。
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档