单元测试
对于开发人员来讲是很是熟悉的,咱们天天的工做也都是围绕着开发与测试进行的,在最先的时候测试都是采用工具Debug
模式进行调试程序,后来Junit
的诞生也让程序测试发生了很大的变化。咱们今天来说解下基于SpringBoot
结合Junit
怎么来完成单元测试
。java
基于SpringBoot
平台整合Junit
分别完成客户端
、服务端
的单元测试
。mysql
咱们首先使用idea工具建立一个SpringBoot
项目,而且添加相关Web、MySQL、JPA依赖,具体pom.xml配置依赖内容以下所示:git
.../省略其余配置 <dependencies> <!--web依赖--> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <!--data jpa依赖--> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-data-jpa</artifactId> </dependency> <!--druid数据源依赖--> <dependency> <groupId>com.alibaba</groupId> <artifactId>druid</artifactId> <version>1.0.31</version> </dependency> <!--lombok依赖--> <dependency> <groupId>org.projectlombok</groupId> <artifactId>lombok</artifactId> </dependency> <!--MySQL依赖--> <dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> </dependency> <!--springboot程序测试依赖,建立项目默认添加--> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-test</artifactId> <scope>test</scope> </dependency> </dependencies> .../省略其余配置
咱们本章的内容须要访问数据库,咱们先在src/main/resources
下添加application.yml
配置文件,对应添加数据库配置信息以下所示:web
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学习目录
文章内找寻对应的章节。spring
对应数据库内的数据表来建立一个商品基本信息实体,实体内容以下所示:sql
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接口,该接口继承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<GoodInfoEntity,Integer> { }
下面咱们开始为单元测试
来作准备工做,先来建立一个SpringMVC
控制器来处理请求,代码以下所示:json
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<GoodInfoEntity> selectAll() { return goodInfoJPA.findAll(); } /** * 查询商品详情 * @param goodId * @return */ @RequestMapping(value = "/detail",method = RequestMethod.GET) public GoodInfoEntity selectOne(Integer goodId) { return goodInfoJPA.findOne(goodId); } }
咱们在测试控制内注入了GoodInfoJPA
,得到了操做商品基本信息的数据接口代理实例,咱们能够经过该代理实例去作一些数据库操做,如上代码selectAll
、detail
方法所示。
在测试控制器内添加了三个测试MVC
方法,咱们接下来开始编写单元测试
代码。数组
在咱们使用idea开发工具构建完成SpringBoot
项目后,会自动为咱们添加spring-boot-starter-test
依赖到pom.xml
配置文件内,固然也为咱们自动建立了一个测试类,该类内一开始是没有过多的代码的。
下面咱们开始基于该测试类进行添加逻辑,代码以下所示:springboot
....//省略依赖导包 /** * 单元测试 */ @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
,该注解才是配置了测试类的启动方式,以及启动时使用实现类的类型。
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 }
我在上面代码中进行了标记,咱们按照标记进行讲解,这样会更明白一些:1
perform
方法其实只是为了构建一个请求,而且返回ResultActions
实例,该实例则是能够获取到请求的返回内容。2
MockMvcRequestBuilders
该抽象类则是能够构建多种请求方式,如:Post
、Get
、Put
、Delete
等经常使用的请求方式,其中参数则是咱们须要请求的本项目的相对路径,/
则是项目请求的根路径。3
param
方法用于在发送请求时携带参数,固然除了该方法还有不少其余的方法,你们能够根据实际请求状况选择调用。4
andReturn
方法则是在发送请求后须要获取放回时调用,该方法返回MvcResult
对象,该对象能够获取到返回的视图名称、返回的Response状态、获取拦截请求的拦截器集合等。5
咱们在这里就是使用到了第4
步内的MvcResult
对象实例获取的MockHttpServletResponse
对象从而才获得的Status
状态码。6
一样也是使用MvcResult
实例获取的MockHttpServletResponse
对象从而获得的请求返回的字符串内容。【能够查看rest返回的json数据】7
使用Junit
内部验证类Assert
判断返回的状态码是否正常为2008
判断返回的字符串是否与咱们预计的同样。
直接上代码吧,跟上面的代码几乎一致,以下所示:
/** * 测试查询详情 * @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
方式进行的,咱们也能够直接调用JPA
、Service
进行直接测试。下面咱们来测试下商品基本信息的添加,代码以下所示:
/** * 测试添加商品基本信息 */ @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技术交流群,共同进步。