Spring Boot 应用的测试

简介: Spring Boot 应用的测试《Spring Boot 实战开发》(陈光剑)—— 基于 Gradle + Kotlin的企业级应用开发最佳实践本书写到这里,Spring Boot 2.0.0.RC1版本已经于2018.1.31 发布。

Spring Boot 应用的测试

《Spring Boot 实战开发》(陈光剑)
—— 基于 Gradle + Kotlin的企业级应用开发最佳实践

本书写到这里,Spring Boot 2.0.0.RC1版本已经于2018.1.31 发布。这是本书最后一章,本章介绍 Spring Boot 应用的测试(质量保障)相关的内容。我们在项目开发中使用分层架构,在测试中也进行分层测试。
1.1 准备工作
本节先来创建一个基于Spring MVC、 Spring Data JPA的 Spring Boot, 完成Dao 层、 Service 层、Controller 层代码的编写,为后面的测试代码的编写做准备。
使用http://start.spring.io/ 创建项目、导入此 Gradle 项目到 IDEA 中。配置 Kotlin Compiler 版本与Target JVM 版本。最后等待项目构建完毕。我们将得到一个初始Spring Boot 工程。详细的代码参考本章给出的示例工程源码。
下面我们来详细讲解怎样针对 Spring Boot 项目进行分层测试。
1.2 分层测试
我们在开发阶段过程中,单元测试通常是必要的。Spring Boot 提供的spring-boot-test 模块基于 spring-test 模块和junit 框架,封装集成了功能强大的结果匹配校验器assertj 、hamcrest Matcher、 Web 请求 Mock 对象、 httpclient、JsonPath (测试 JSON 数据)、mockito、selenium等。
测试代码通常放在 src/test 目录下,包目录规范是跟 src/main 目录保持一致。测试代码目录结构设计如下

图15-1 测试代码目录结构
测试代码的分层逻辑与项目源代码中的 dao层、service 层、controller 层各自对应。
下面我们来开发具体的测试类。
1.2.1 Dao 层测试
在包com.easy.springboot.demo_testing_and_deploy.dao下面添加UserDaoTest.kt测试类,代码如下

@RunWith(SpringRunner::class)
@SpringBootTest
class UserDaoTest {
    @Autowired lateinit var userDao: UserDao
    @Test
    fun testFindAll() {
        Assert.assertTrue(userDao.findAll().size == 2)
    }
}

其中,需要测试类上需要添加@RunWith(SpringRunner.class) 和 @SpringBootTest 注解。这里的 @RunWith这里就不多做解释了,在 JUnit中这个是最常用的注解。
@SpringBootTest这个注解是SpringBoot项目测试的核心注解,标识该测试类以SpringBoot方式运行,该注解的定义如下:

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@BootstrapWith(SpringBootTestContextBootstrapper.class)
public @interface SpringBootTest{}

在上面的 @SpringBootTest 注解源码中最重要的是 @BootstrapWith,该注解配置了测试类的启动核心类SpringBootTestContextBootstrapper。
在UserDaoTest测试类中可以直接使用@Autowired来装配UserDao这个 Bean。而且,@SpringBootTest 注解会自动帮我们完成启动一个 Spring 容器 ApplicationContext,然后连接数据库,执行一套完整的业务逻辑。
1.2.2 Service 层测试
Service 层的代码测试类跟 Dao 层类似,例如UserServiceTest.kt 测试代码如下

@RunWith(SpringRunner::class)
@SpringBootTest
class UserServiceTest {
    // 直接使用@Autowired注解注入 Service 对象
    @Autowired lateinit var userService: UserService

    @Test
    fun testFindAll() {
        Assert.assertTrue(userService.findAll().size == 2)
    }
}

1.2.3 使用 Mockito 测试 Service 层代码

上面的测试代码是连接真实的数据库来执行真实的 Dao 层数据库查询逻辑。
而在实际开发的场景中,我们有时候需要独立于数据库进行 Service 层逻辑的开发。这个时候就可以直接把数据库Dao层代码Mock 掉。例如在UserService中有一个 getOne()方法,具体的实现代码是

interface UserService {
    ...
    fun getOne(id:Long):User?
}

@Service
class UserServiceImpl : UserService {
    @Autowired lateinit var userDao: UserDao
    ...

    override fun getOne(id: Long): User? {
        return userDao.getOne(id)
    }
}

下面,我们就使用 Mockito 来把 UserDao 层代码 Mock 掉。Mockito 主要用于 service 层的 mock 测试。mock 的对象一般是对 DAO 层的依赖; 另外就是别人的Service实现类。
新建测试类MockUserServiceTest.kt 代码如下:

@RunWith(MockitoJUnitRunner::class)
class MockUserServiceTest {
    @Mock
    lateinit var mockUserDao: UserDao // mock 一个DAO层的接口
    @InjectMocks
    lateinit var userService: UserServiceImpl// Mock一个 Service 的实现类,用 @InjectMocks。注意这里是实现类 UserServiceImpl

    @Before
    fun setUp() {
        // initMocks 必须,否则 @Mock 注解无效
        MockitoAnnotations.initMocks(this)
    }

    @Test
    fun testGetOne() {
        val mockUser = User()
        mockUser.id = 101
        mockUser.username = "mockUser"
        mockUser.password = "123456"

        val roles = mutableSetOf<Role>()
        val r1 = Role()
        r1.role = "ROLE_USER"
        val r2 = Role()
        r1.role = "ROLE_ADMIN"
        roles.add(r1)
        roles.add(r2)
        mockUser.roles = roles
        //模拟 UserDao对象
        `when`(mockUserDao.getOne(1)).thenReturn(mockUser)

        val u = userService.getOne(1)
        println(ObjectMapper().writeValueAsString(u))
        Assert.assertTrue(u?.password == "123456")
    }
}

需要注意的是,该测试的执行 Runner 是 @RunWith(MockitoJUnitRunner::class) 。
 使用 @Mock 注解标记这个对象是被 Mock 的。
 使用 @InjectMocks 注解标注一个实现类UserServiceImpl,Mockito 会自动把 @Spy 或 @Mock标注的 Mock 对象注入到实现类UserServiceImpl的方法执行中,相当于把实现类中的UserDao对象使用mockUserDao对象给“偷梁换柱”了。
运行上面的测试类,可以发现测试成功

图15-2 MockUserServiceTest测试成功
在测试代码的打印日志中,输出的 getOne(1)方法的返回对象是我们 Mock 的对象mockUser :

{"id":101,"gmtCreate":"2018-02-09 01:48:33","gmtModify":"2018-02-09 01:48:33","username":"mockUser","password":"123456","roles":[{"id":-1,"gmtCreate":"2018-02-09 01:48:33","gmtModify":"2018-02-09 01:48:33","role":"ROLE_ADMIN"},{"id":-1,"gmtCreate":"2018-02-09 01:48:33","gmtModify":"2018-02-09 01:48:33","role":"ROLE_USER"}]}

提示:更多关于 Mockito 的使用请参考官网文档:http://site.mockito.org/

1.2.4 Controller 层测试
通过上面的实例,我们已经了解了在实际项目开发测试中对dao层代码和service层代码的测试,还学习了 Mockito 技术的相关内容。spring-boot-starter-test中提供了对项目测试功能的强大支持,更难得的是其中增加了对Controller层测试的支持。
下面我们来测试接口 http://127.0.0.1:8012/user/1 。该接口的输出的JSON数据如下

{
  "id": 1,
  "gmtCreate": "2018-02-08 12:58:14",
  "gmtModify": "2018-02-08 12:58:14",
  "username": "user",
  "password": "user",
  "roles": [
    {
      "id": 1,
      "gmtCreate": "2018-02-08 12:58:14",
      "gmtModify": "2018-02-08 12:58:14",
      "role": "ROLE_USER"
    }
  ]
}

UserControllerTest测试代码如下

@RunWith(SpringJUnit4ClassRunner::class)
@SpringBootTest
class UserControllerTest {

    @Autowired
    lateinit var context: WebApplicationContext
    lateinit var mvc: MockMvc
    @Before
    fun setUp() {
        mvc = MockMvcBuilders.webAppContextSetup(context).build()
    }

    @Test
    fun testFetchUser1() {
        mvc.perform(MockMvcRequestBuilders.get("/user/1")
                .contentType(MediaType.APPLICATION_JSON_UTF8)
                .accept(MediaType.APPLICATION_JSON))
                .andExpect(MockMvcResultMatchers.status().isOk)
                .andDo(MockMvcResultHandlers.print())
                .andExpect(MockMvcResultMatchers.content().string(Matchers.containsString("""
                    "username":"user"
                """.trimIndent())))
                .andDo {
                    println("it.request.method=${it.request.method}")
                    println("it.response.contentAsString=${it.response.contentAsString}")
                }
                .andExpect(MockMvcResultMatchers.jsonPath("$.id", Matchers.equalTo(1)))
                .andExpect(MockMvcResultMatchers.jsonPath("$.roles[0].role", Matchers.equalTo("ROLE_USER")))

    }
}

其中, MockMvc是一个被final修饰的类型,该类无法被继承使用。这个类在包org.springframework.test.web.servlet下面,是Spring提供的模拟SpringMVC请求的实例类,该类由MockMvcBuilders通过WebApplicationContext实例进行创建。MockMvcBuilder接口签名如下

package org.springframework.test.web.servlet;
public interface MockMvcBuilder {
  MockMvc build();
}

上面的代码简单说明如下表15-1。
表15-1
方法名
功能说明
Perform()
方法其实只是为了构建一个请求,并且返回ResultActions实例,使用该实例可以获取到请求的返回内容。

MockMvcRequestBuilders
支持构建多种请求方法对象,如:Post、Get、Put、Delete等常用的请求方式,其中的参数"/user/1"则是我们需要请求的本项目的相对路径,/ 则是项目请求的根路径。另外,还可以调用param() 方法用于在发送请求时携带参数。

andExpect()
是ResultActions中成员,入参是ResultMatcher类型:
ResultActions andExpect(ResultMatcher matcher)
在发送请求后对响应结果进行匹配校验时调用。其中MockMvcResultMatchers 抽象类是一个静态工厂,用于生产ResultMatcher对象。MockMvcResultMatchers中提供了丰富的匹配器。

1.2.5 JSON接口测试
使用 JsonPath 我们可以像 JavaScript 语法一样方便地进行 JSON 数据返回的访问操作。例如下面的这两行代码
.andExpect(MockMvcResultMatchers.jsonPath("$.id", Matchers.equalTo(1)))
.andExpect(MockMvcResultMatchers.jsonPath("$.roles[0].role", Matchers.equalTo("ROLE_USER")))
这里的Matchers类是org.hamcrest包下面的类。org.hamcrest.Matchers 类中提供了丰富的断言方法,这些方法的具体使用可以阅读Matchers 类的源码深入了解。
其中,"$.id" 和 "$.roles[0].role" 就是 JsonPath的表达式语法。
提示:更多关于 JsonPath 的内容可以参考: https://github.com/json-path/JsonPath

运行上面的测试代码,测试成功:

图15-3 UserControllerTest测试成功
使用命令 $ gradle test 可以一次性全部执行 src/test 目录下面的测试类。在 IDEA 中可以直接邮寄 src/test 目录,选择 Run > All Tests执行所有测试类,如下图所示

图15-4 选择 Run > All Tests执行 所有测试类
另外,Gradle Test 生成的测试报告在 build/reports/tests/test/index.html 中,如下图

图15-5 Gradle Test 生成的测试报告在 build/reports/tests/test/index.html 中
测试报告的部分内容截图如下

图15-6 测试报告Summary

图15-7 UserControllerTest测试报告

图15-8 MockUserServiceTest测试报告

1.3 本章小结

本章介绍了Spring Boot项目如何测试。Spring Boot 应用对Web层测试提供强大的支持:采用MockMvc方式测试Web请求,根据传递的不用参数以及请求返回对象反馈信息进行验证测试。另外,针对 JSON 数据接口,使用 JsonPath 可以方便地进行 JSON 数据结果的校验。
提示:本章项目工程源代码:
https://github.com/KotlinSpringBoot/demo_testing_and_deploy

相关文章
|
7天前
|
敏捷开发 测试技术 持续交付
探索自动化测试在敏捷开发中的应用与挑战
本文深入探讨了自动化测试在现代软件开发流程,特别是敏捷开发环境中的重要作用和面临的挑战。通过分析自动化测试的基本原理、实施策略以及在实际项目中的应用案例,揭示了其在提高软件质量和加速产品交付方面的巨大潜力。同时,文章也指出了自动化测试实施过程中可能遇到的技术难题、成本考量及团队协作问题,并提出了相应的解决策略,为软件开发团队提供了有价值的参考和指导。
|
12天前
|
编解码 测试技术 开发工具
测试 iPhone 应用在不同屏幕尺寸和分辨率下的响应式效果
【10月更文挑战第23天】测试 iPhone 应用在不同屏幕尺寸和分辨率下的响应式效果是确保应用质量和用户体验的重要环节。通过手动测试、自动化测试、视觉效果评估、性能测试、用户体验测试等多种方法的综合运用,能够全面地发现应用在响应式效果方面存在的问题,并及时进行解决和优化。同时,持续的测试和优化也是不断提升应用质量和用户满意度的关键。
|
11天前
|
JavaScript 安全 Java
如何使用 Spring Boot 和 Ant Design Pro Vue 构建一个具有动态路由和菜单功能的前后端分离应用。
本文介绍了如何使用 Spring Boot 和 Ant Design Pro Vue 构建一个具有动态路由和菜单功能的前后端分离应用。首先,创建并配置 Spring Boot 项目,实现后端 API;然后,使用 Ant Design Pro Vue 创建前端项目,配置动态路由和菜单。通过具体案例,展示了如何快速搭建高效、易维护的项目框架。
92 62
|
9天前
|
人工智能 前端开发 Java
基于开源框架Spring AI Alibaba快速构建Java应用
本文旨在帮助开发者快速掌握并应用 Spring AI Alibaba,提升基于 Java 的大模型应用开发效率和安全性。
基于开源框架Spring AI Alibaba快速构建Java应用
|
9天前
|
前端开发 数据管理 测试技术
前端自动化测试:Jest与Cypress的实战应用与最佳实践
【10月更文挑战第27天】本文介绍了前端自动化测试中Jest和Cypress的实战应用与最佳实践。Jest适合React应用的单元测试和快照测试,Cypress则擅长端到端测试,模拟用户交互。通过结合使用这两种工具,可以有效提升代码质量和开发效率。最佳实践包括单元测试与集成测试结合、快照测试、并行执行、代码覆盖率分析、测试环境管理和测试数据管理。
22 2
|
9天前
|
JavaScript 安全 Java
如何使用 Spring Boot 和 Ant Design Pro Vue 构建一个前后端分离的应用框架,实现动态路由和菜单功能
本文介绍了如何使用 Spring Boot 和 Ant Design Pro Vue 构建一个前后端分离的应用框架,实现动态路由和菜单功能。首先,确保开发环境已安装必要的工具,然后创建并配置 Spring Boot 项目,包括添加依赖和配置 Spring Security。接着,创建后端 API 和前端项目,配置动态路由和菜单。最后,运行项目并分享实践心得,帮助开发者提高开发效率和应用的可维护性。
24 2
|
9天前
|
Web App开发 定位技术 iOS开发
Playwright 是一个强大的工具,用于在各种浏览器上测试应用,并模拟真实设备如手机和平板。通过配置 `playwright.devices`,可以轻松模拟不同设备的用户代理、屏幕尺寸、视口等特性。此外,Playwright 还支持模拟地理位置、区域设置、时区、权限(如通知)和配色方案,使测试更加全面和真实。例如,可以在配置文件中设置全局的区域设置和时区,然后在特定测试中进行覆盖。同时,还可以动态更改地理位置和媒体类型,以适应不同的测试需求。
Playwright 是一个强大的工具,用于在各种浏览器上测试应用,并模拟真实设备如手机和平板。通过配置 `playwright.devices`,可以轻松模拟不同设备的用户代理、屏幕尺寸、视口等特性。此外,Playwright 还支持模拟地理位置、区域设置、时区、权限(如通知)和配色方案,使测试更加全面和真实。例如,可以在配置文件中设置全局的区域设置和时区,然后在特定测试中进行覆盖。同时,还可以动态更改地理位置和媒体类型,以适应不同的测试需求。
16 1
|
10天前
|
前端开发 JavaScript 数据可视化
前端自动化测试:Jest与Cypress的实战应用与最佳实践
【10月更文挑战第26天】前端自动化测试在现代软件开发中至关重要,Jest和Cypress分别是单元测试和端到端测试的流行工具。本文通过解答一系列问题,介绍Jest与Cypress的实战应用与最佳实践,帮助开发者提高测试效率和代码质量。
24 2
|
7天前
|
NoSQL 测试技术 Go
自动化测试在 Go 开源库中的应用与实践
本文介绍了 Go 语言的自动化测试及其在 `go mongox` 库中的实践。Go 语言通过 `testing` 库和 `go test` 命令提供了简洁高效的测试框架,支持单元测试、集成测试和基准测试。`go mongox` 库通过单元测试和集成测试确保与 MongoDB 交互的正确性和稳定性,使用 Docker Compose 快速搭建测试环境。文章还探讨了表驱动测试、覆盖率检查和 Mock 工具的使用,强调了自动化测试在开源库中的重要性。
|
2月前
|
SQL 监控 druid
springboot-druid数据源的配置方式及配置后台监控-自定义和导入stater(推荐-简单方便使用)两种方式配置druid数据源
这篇文章介绍了如何在Spring Boot项目中配置和监控Druid数据源,包括自定义配置和使用Spring Boot Starter两种方法。
下一篇
无影云桌面