资讯专栏INFORMATION COLUMN

第三十五章:SpringBoot与单元测试的小秘密

hikui / 2649人阅读

摘要:本章目的基于平台整合分别完成客户端服务端的单元测试。在测试控制器内添加了三个测试方法,我们接下来开始编写单元测试代码。总结本章主要介绍了基于平台的两种单元测试方式,一种是在服务端采用注入方式将需要测试的或者注入到测试类中,然后调用方法即可。

单元测试对于开发人员来说是非常熟悉的,我们每天的工作也都是围绕着开发与测试进行的,在最早的时候测试都是采用工具Debug模式进行调试程序,后来Junit的诞生也让程序测试发生了很大的变化。我们今天来讲解下基于SpringBoot结合Junit怎么来完成单元测试

本章目的

基于SpringBoot平台整合Junit分别完成客户端服务端单元测试

构建项目

我们首先使用idea工具创建一个SpringBoot项目,并且添加相关Web、MySQL、JPA依赖,具体pom.xml配置依赖内容如下所示:

.../省略其他配置

        
        
            org.springframework.boot
            spring-boot-starter-web
        
        
        
            org.springframework.boot
            spring-boot-starter-data-jpa
        
        
        
            com.alibaba
            druid
            1.0.31
        
        
        
            org.projectlombok
            lombok
        
        
        
            mysql
            mysql-connector-java
        

        
        
            org.springframework.boot
            spring-boot-starter-test
            test
        

    
.../省略其他配置
配置数据库

我们本章的内容需要访问数据库,我们先在src/main/resources下添加application.yml配置文件,对应添加数据库配置信息如下所示:

spring:
  datasource:
    type: com.alibaba.druid.pool.DruidDataSource
    driver-class-name: com.mysql.jdbc.Driver
    url: jdbc:mysql://127.0.0.1:3306/test?characterEncoding=utf8
    username: root
    password: 123456
    #最大活跃数
    maxActive: 20
    #初始化数量
    initialSize: 1
    #最大连接等待超时时间
    maxWait: 60000
    #打开PSCache,并且指定每个连接PSCache的大小
    poolPreparedStatements: true
    maxPoolPreparedStatementPerConnectionSize: 20
    #通过connectionProperties属性来打开mergeSql功能;慢SQL记录
    #connectionProperties: druid.stat.mergeSql=true;druid.stat.slowSqlMillis=5000
    minIdle: 1
    timeBetweenEvictionRunsMillis: 60000
    minEvictableIdleTimeMillis: 300000
    validationQuery: select 1 from dual
    testWhileIdle: true
    testOnBorrow: false
    testOnReturn: false
    #配置监控统计拦截的filters,去掉后监控界面sql将无法统计,"wall"用于防火墙
    filters: stat, wall, log4j
  jpa:
    properties:
      hibernate:
        show_sql: true
        format_sql: true

以上配置都是比较常用到,这里不做多解释了,如果不明白可以去本文底部SpringBoot学习目录文章内找寻对应的章节。

构建实体

对应数据库内的数据表来创建一个商品基本信息实体,实体内容如下所示:

package com.yuqiyu.chapter35.bean;

import lombok.Data;

import javax.persistence.*;
import java.io.Serializable;

/**
 * 商品基本信息实体
 * ========================
 * Created with IntelliJ IDEA.
 * User:恒宇少年
 * Date:2017/9/13
 * Time:22:20
 * 码云:http://git.oschina.net/jnyqy
 * ========================
 */
@Data
@Entity
@Table(name = "good_infos")
public class GoodInfoEntity implements Serializable
{
    //商品编号
    @Id
    @Column(name = "tg_id")
    @GeneratedValue
    private Integer tgId;

    //商品类型编号
    @Column(name = "tg_type_id")
    private Integer typeId;

    //商品标题
    @Column(name = "tg_title")
    private String title;

    //商品价格
    @Column(name = "tg_price")
    private double price;

    //商品排序
    @Column(name = "tg_order")
    private int order;
}
构建JPA

基于商品基本信息实体类创建一个JPA接口,该接口继承JpaRepository接口完成框架通过反向代理模式进行生成实现类,自定义JPA接口内容如下所示:

package com.yuqiyu.chapter35.jpa;

import com.yuqiyu.chapter35.bean.GoodInfoEntity;
import org.springframework.data.jpa.repository.JpaRepository;

/**
 * 商品jpa
 * ========================
 * Created with IntelliJ IDEA.
 * User:恒宇少年
 * Date:2017/9/13
 * Time:22:23
 * 码云:http://git.oschina.net/jnyqy
 * ========================
 */
public interface GoodInfoJPA
    extends JpaRepository
{
}
构建测试控制器

下面我们开始为单元测试来做准备工作,先来创建一个SpringMVC控制器来处理请求,代码如下所示:

package com.yuqiyu.chapter35.controller;

import com.yuqiyu.chapter35.bean.GoodInfoEntity;
import com.yuqiyu.chapter35.jpa.GoodInfoJPA;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RestController;

import java.util.List;

/**
 * ===============================
 * Created with Eclipse.
 * User:于起宇
 * Date:2017/9/13
 * Time:18:37
 * 简书:http://www.jianshu.com/u/092df3f77bca
 * ================================
 */
@RestController
public class TestController
{
    //商品基本信息数据接口
    @Autowired
    private GoodInfoJPA goodInfoJPA;

    /**
     * 查询首页内容
     * @return
     */
    @RequestMapping(value = "/index")
    public String index(String name)
    {
        return "this is index page" + name;
    }

    /**
     * 查询全部商品
     * @return
     */
    @RequestMapping(value = "/all")
    public List selectAll()
    {
        return goodInfoJPA.findAll();
    }

    /**
     * 查询商品详情
     * @param goodId
     * @return
     */
    @RequestMapping(value = "/detail",method = RequestMethod.GET)
    public GoodInfoEntity selectOne(Integer goodId)
    {
        return goodInfoJPA.findOne(goodId);
    }
}

我们在测试控制内注入了GoodInfoJPA,获得了操作商品基本信息的数据接口代理实例,我们可以通过该代理实例去做一些数据库操作,如上代码selectAlldetail方法所示。
在测试控制器内添加了三个测试MVC方法,我们接下来开始编写单元测试代码。

编写单元测试

在我们使用idea开发工具构建完成SpringBoot项目后,会自动为我们添加spring-boot-starter-test依赖到pom.xml配置文件内,当然也为我们自动创建了一个测试类,该类内一开始是没有过多的代码的。
下面我们开始基于该测试类进行添加逻辑,代码如下所示:

....//省略依赖导包
/**
 * 单元测试
 */
@RunWith(SpringRunner.class)
@SpringBootTest
public class Chapter35ApplicationTests {
    /**
     * 模拟mvc测试对象
     */
    private MockMvc mockMvc;

    /**
     * web项目上下文
     */
    @Autowired
    private WebApplicationContext webApplicationContext;

    /**
     * 商品业务数据接口
     */
    @Autowired
    private GoodInfoJPA goodInfoJPA;

    /**
     * 所有测试方法执行之前执行该方法
     */
    @Before
    public void before() {
        //获取mockmvc对象实例
        mockMvc = MockMvcBuilders.webAppContextSetup(webApplicationContext).build();
    }
}

在上面测试代码中我们从上面开始讲解下,其中@RunWith这里就不多做解释了,我们最比较常用到的就是这个注解。

@SpringBootTest这个注解这里要强调下,这是SpringBoot项目测试的核心注解,标识该测试类以SpringBoot方式运行,该注解的源码如下所示:

...//省略导包
@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@BootstrapWith(SpringBootTestContextBootstrapper.class)
public @interface SpringBootTest {
    @AliasFor("properties")
    String[] value() default {};

    @AliasFor("value")
    String[] properties() default {};

    Class[] classes() default {};

    SpringBootTest.WebEnvironment webEnvironment() default SpringBootTest.WebEnvironment.MOCK;

    public static enum WebEnvironment {
        MOCK(false),
        RANDOM_PORT(true),
        DEFINED_PORT(true),
        NONE(false);

        private final boolean embedded;

        private WebEnvironment(boolean embedded) {
            this.embedded = embedded;
        }

        public boolean isEmbedded() {
            return this.embedded;
        }
    }
}

我们可以看到在@SpringBootTest注解源码中最为重要的就是@BootstrapWith,该注解才是配置了测试类的启动方式,以及启动时使用实现类的类型。

测试index请求

MockMvc这个类是一个被final修饰的类型,该类无法被继承使用。这个类是Spring为我们提供模拟SpringMVC请求的实例类,该类则是由MockMvcBuilders通过WebApplicationContext实例进行创建的,初始化MockMvc实例我们可以看下before方法逻辑。到现在为止我们才是万事俱备就差编写单元测试逻辑了,我们首先来编写访问/index请求路径的测试,具体测试代码如下所示:

    /**
     * 测试访问/index地址
     * @throws Exception
     */
    @Test
    public void testIndex() throws Exception {
        MvcResult mvcResult = mockMvc
                .perform(// 1
                        MockMvcRequestBuilders.get("/index") // 2
                        .param("name","admin") // 3
                )
                .andReturn();// 4

        int status = mvcResult.getResponse().getStatus(); // 5
        String responseString = mvcResult.getResponse().getContentAsString(); // 6

        Assert.assertEquals("请求错误", 200, status); // 7
        Assert.assertEquals("返回结果不一致", "this is index pageadmin", responseString); // 8
    }
MockMvc解析

我在上面代码中进行了标记,我们按照标记进行讲解,这样会更明白一些:
1 perform方法其实只是为了构建一个请求,并且返回ResultActions实例,该实例则是可以获取到请求的返回内容。
2 MockMvcRequestBuilders该抽象类则是可以构建多种请求方式,如:PostGetPutDelete等常用的请求方式,其中参数则是我们需要请求的本项目的相对路径,/则是项目请求的根路径。
3 param方法用于在发送请求时携带参数,当然除了该方法还有很多其他的方法,大家可以根据实际请求情况选择调用。
4 andReturn方法则是在发送请求后需要获取放回时调用,该方法返回MvcResult 对象,该对象可以获取到返回的视图名称、返回的Response状态、获取拦截请求的拦截器集合等。
5 我们在这里就是使用到了第4步内的MvcResult对象实例获取的MockHttpServletResponse对象从而才得到的Status状态码。
6 同样也是使用MvcResult实例获取的MockHttpServletResponse对象从而得到的请求返回的字符串内容。【可以查看rest返回的json数据】
7 使用Junit内部验证类Assert判断返回的状态码是否正常为200
8 判断返回的字符串是否与我们预计的一样。

测试商品详情

直接上代码吧,跟上面的代码几乎一致,如下所示:

/**
     * 测试查询详情
     * @throws Exception
     */
    @Test
    public void testDetail() throws Exception
    {
        MvcResult mvcResult = mockMvc
                .perform(
                        MockMvcRequestBuilders.get("/detail")
                        .param("goodId","2")
                )
                .andReturn(); // 5

        //输出经历的拦截器
        HandlerInterceptor[] interceptors = mvcResult.getInterceptors();
        System.out.println(interceptors[0].getClass().getName());

        int status = mvcResult.getResponse().getStatus(); // 6
        String responseString = mvcResult.getResponse().getContentAsString(); // 7
        System.out.println("返回内容:"+responseString);
        Assert.assertEquals("return status not equals 200", 200, status); // 8
    }

上面唯一一个部分需要解释下,在上面测试方法内输出了请求经历的拦截器,如果我们配置了多个拦截器这里会根据先后顺序写入到拦截器数组内,其他的MockMvc测试方法以及参数跟上面测试方法一致。

测试添加

在测试类声明定义全局字段时,我们注入了GoodInfoJPA实例,当然单元测试也不仅仅是客户端也就是使用MockMvc方式进行的,我们也可以直接调用JPAService进行直接测试。下面我们来测试下商品基本信息的添加,代码如下所示:

/**
     * 测试添加商品基本信息
     */
    @Test
    public void testInsert()
    {
        /**
         * 商品基本信息实体
         */
        GoodInfoEntity goodInfoEntity = new GoodInfoEntity();
        goodInfoEntity.setTitle("西红柿");
        goodInfoEntity.setOrder(2);
        goodInfoEntity.setPrice(5.82);
        goodInfoEntity.setTypeId(1);
        goodInfoJPA.save(goodInfoEntity);
        /**
         * 测试是否添加成功
         * 验证主键是否存在
         */
        Assert.assertNotNull(goodInfoEntity.getTgId());
    }

在上面代码中并没有什么特殊的部分,是我们在使用Data JPA时用到的save方法用于执行添加,在添加完成后验证主键的值是否存在,NotNull时证明添加成功。

测试删除

与添加差别不大,代码如下所示:

    /**
     * 测试删除商品基本信息
     */
    @Test
    public void testDelete()
    {
        //根据主键删除
        goodInfoJPA.delete(3);
        
        //验证数据库是否已经删除
        Assert.assertNull(goodInfoJPA.findOne(3));
    }

在上面代码中,我们根据主键的值进行删除商品的基本信息,执行删除完成后调用selectOne方法查看数据库内是否已经不存在该条数据了。

总结

本章主要介绍了基于SpringBoot平台的两种单元测试方式,一种是在服务端采用Spring注入方式将需要测试的JPA或者Service注入到测试类中,然后调用方法即可。另外一种则是在客户端采用MockMvc方式测试Web请求,根据传递的不用参数以及请求返回对象反馈信息进行验证测试。

本章代码已经上传到码云:
网页地址:http://git.oschina.net/jnyqy/lessons
Git地址:https://git.oschina.net/jnyqy/lessons.git
SpringBoot相关系列文章请访问:目录:SpringBoot学习目录
QueryDSL相关系列文章请访问:QueryDSL通用查询框架学习目录
SpringDataJPA相关系列文章请访问:目录:SpringDataJPA学习目录
感谢阅读!
欢迎加入QQ技术交流群,共同进步。

文章版权归作者所有,未经允许请勿转载,若此文章存在违规行为,您可以联系管理员删除。

转载请注明本文地址:https://www.ucloud.cn/yun/67635.html

相关文章

  • 三十四章:SpringBoot配置类WebMvcConfigurerAdapter

    摘要:本章目标继承采用形式实现个性化配置定制。本章代码已经上传到码云网页地址地址相关系列文章请访问目录学习目录相关系列文章请访问通用查询框架学习目录相关系列文章请访问目录学习目录感谢阅读欢迎加入技术交流群,共同进步。 WebMvcConfigurerAdapter配置类其实是Spring内部的一种配置方式,采用JavaBean的形式来代替传统的xml配置文件形式进行针对框架个性化定制,下面我...

    springDevBird 评论0 收藏0
  • #yyds干货盘点#安全管理制度管理办法范文

    摘要:第四章安全管理制度发布第十条安全管理制度必须以正式文件的形式发布施行。第十一条安全管理制度由信息安全管理小组制订,信息安全领导小组审批发布。第二十条安全管理制度的修改与废止须经信息安全领导组织审批确认,信息安全管理部门备案。 字数 3610阅读 760评论 0赞 3《xxxx安全管理制度汇编》****制度管理办法****文...

    duan199226 评论0 收藏0
  • 第二五章SpringBoot添加支持CORS跨域访问

    摘要:本章目标基于项目搭建可以站外请求访问的跨域资源服务器。允许所有的请求域名访问我们的跨域资源,可以固定单条或者多条内容,如,只有百度可以访问我们的跨域资源。 CORS(Cross-Origin Resource Sharing)跨域资源共享,是一个W3C标准,它允许浏览器向跨域服务器发送Ajax请求,打破了Ajax只能访问本站内的资源限制,CORS在很多地方都有被使用,微信支付的JS支付...

    simpleapples 评论0 收藏0
  • [系统安全] 三十五.Procmon工具基本用法及文件进程、注册表查看

    摘要:本文将分享软件基本用法及文件进程注册表查看,这是一款微软推荐的系统监视工具,功能非常强大可用来检测恶意软件。可以帮助使用者对系统中的任何文件注册表操作进行监视和记录,通过注册表和文件读写的变化,有效帮助诊断系统故障或发现恶意软件病毒及木马。 ...

    kk_miles 评论0 收藏0
  • 三十六章:基于SpringBoot架构重写SpringMVC请求参数装载

    摘要:本章目标根据项目定制参数状态并了解的装载过程以及实现方式。创建测试控制器创建名为的控制器并添加数据提交的方法,具体代码如下所示表单提交控制器恒宇少年码云装载参数测试教师名称, 在国内企业开发项目中大多数都已经偏向Spring家族式的开发风格,在前几年国内项目都是以Structs2作为Web开发的主导,不过由于近几年发生的事情确实让开发者对它失去了以往的信心。与此同时Spring家族发布...

    Ali_ 评论0 收藏0

发表评论

0条评论

最新活动
阅读需要支付1元查看
<