首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >Spring Boot 应用的测试Spring Boot 应用的测试

Spring Boot 应用的测试Spring Boot 应用的测试

作者头像
一个会写诗的程序员
发布2018-08-17 11:00:37
1.5K0
发布2018-08-17 11:00:37
举报

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

本文参与 腾讯云自媒体分享计划,分享自作者个人站点/博客。
原始发表:2018.04.17 ,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

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

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

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