看到一篇十分好的对于 Springboot 单元测试的文章,特此转过来,原文地址:Spring Boot 干货系列:(十二)Spring Boot 应用单元测试
一、前言
这次来介绍下 Spring Boot 中对单元测试的整合应用,本篇会通过以下 4 点来介绍,根本满足日常需要
- Service 层单元测试
- Controller 层单元测试
- 新断言 assertThat 应用
- 单元测试的回滚
Spring Boot 中引入单元测试很简略,依赖如下:
<dependency>
<groupId>org.springframework.boot<groupId>
<artifactId>spring-boot-starter-test<artifactId>
<scope>test<scope>
<dependency>
在生成的测试类中就能够写单元测试了。用 spring 自带 spring-boot-test 的测试工具类即可,spring-boot-starter-test 启动器能引入这些 Spring Boot 测试模块:
- JUnit:Java 应用程序单元测试规范类库。
- Spring Test & Spring Boot Test:Spring Boot 应用程序性能集成化测试反对。
- Mockito:一个 Java Mock 测试框架。
- AssertJ:一个轻量级的断言类库。
- Hamcrest:一个对象匹配器类库。
- JSONassert:一个用于 JSON 的断言库。
- JsonPath:一个 JSON 操作类库。
二、Service 单元测试
Spring Boot 中单元测试类写在在 srctestjava 目录下,你能够手动创立具体测试类,如果是 IDEA,则能够通过 IDEA 主动创立测试类,如下图,也能够通过快捷键
⇧⌘T
(MAC) 或者
Ctrl+Shift+T
(Window) 来创立,如下:
主动生成测试类如下:
而后再编写创立好的测试类,具体代码如下:
package com.dudu.service;
import com.dudu.domain.LearnResource;
import org.junit.Assert;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.context.junit4.SpringRunner;
import static org.hamcrest.CoreMatchers.*;@RunWith(SpringRunner.class)
@SpringBootTest
public class LearnServiceTest {@Autowired
private LearnService learnService;@Test
public void getLearn(){
LearnResource learnResource=learnService.selectByKey(1001L);
Assert.assertThat(learnResource.getAuthor(),is("嘟嘟 MD 独立博客"));
}
}下面就是最简略的单元测试写法,顶部只有
@RunWith(SpringRunner.class)
和
SpringBootTest
即可,想要执行的时候,鼠标放在对应的办法,右键抉择 run 该办法即可。
RunWith 解释
@RunWith 就是一个运行器,如 @RunWith(SpringJUnit4ClassRunner.class), 让测试运行于 Spring 测试环境,@RunWith(SpringJUnit4ClassRunner.class)应用了 Spring 的 SpringJUnit4ClassRunner,以便在测试开始的时候主动创立 Spring 的利用上下文。其余的想创立 spring 容器的话,就得子啊 web.xml 配置 classloder。注解了 @RunWith 就能够间接应用 spring 容器,间接应用 @Test 注解,不必启动 spring 容器
测试用例中我应用了 assertThat 断言,下文中会介绍,也举荐大家应用该断言。
三、Controller 单元测试
下面只是针对 Service 层做测试,然而有时候须要对 Controller 层(API)做测试,这时候就得用到 MockMvc 了,你能够不用启动工程就能测试这些接口。
MockMvc 实现了对 Http 申请的模仿,可能间接应用网络的模式,转换到 Controller 的调用,这样能够使得测试速度快、不依赖网络环境,而且提供了一套验证的工具,这样能够使得申请的验证对立而且很不便。
Controller 类:
package com.dudu.controller;
** 教程页面
* Created by tengj on 2017313.
*
@Controller
@RequestMapping("learn")
public class LearnController extends AbstractController{
@Autowired
private LearnService learnService;
private Logger logger = LoggerFactory.getLogger(this.getClass());@RequestMapping("")
public String learn(Model model){
model.addAttribute("ctx", getContextPath()+"");
return "learn-resource";
}**
* 查问教程列表
* @param page
* @return
*
@RequestMapping(value = "queryLeanList",method = RequestMethod.POST)
@ResponseBody
public AjaxObject queryLearnList(Page<LeanQueryLeanListReq> page){
List<LearnResource> learnList=learnService.queryLearnResouceList(page);
PageInfo<LearnResource> pageInfo =new PageInfo<LearnResource>(learnList);
return AjaxObject.ok().put("page", pageInfo);
}**
* 新添教程
* @param learn
*
@RequestMapping(value = "add",method = RequestMethod.POST)
@ResponseBody
public AjaxObject addLearn(@RequestBody LearnResource learn){
learnService.save(learn);
return AjaxObject.ok();
}**
* 批改教程
* @param learn
*
@RequestMapping(value = "update",method = RequestMethod.POST)
@ResponseBody
public AjaxObject updateLearn(@RequestBody LearnResource learn){
learnService.updateNotNull(learn);
return AjaxObject.ok();
}**
* 删除教程
* @param ids
*
@RequestMapping(value="delete",method = RequestMethod.POST)
@ResponseBody
public AjaxObject deleteLearn(@RequestBody Long[] ids){
learnService.deleteBatch(ids);
return AjaxObject.ok();
}**
* 获取教程
* @param id
*
@RequestMapping(value="resource{id}",method = RequestMethod.GET)
@ResponseBody
public LearnResource qryLearn(@PathVariable(value = "id") Long id){
LearnResource lean= learnService.selectByKey(id);
return lean;
}
}这里咱们也主动创立一个 Controller 的测试类,具体代码如下:
package com.dudu.controller;
import com.dudu.domain.User;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.http.MediaType;
import org.springframework.mock.web.MockHttpSession;
import org.springframework.test.context.junit4.SpringRunner;
import org.springframework.test.web.servlet.MockMvc;
import org.springframework.test.web.servlet.request.MockMvcRequestBuilders;
import org.springframework.test.web.servlet.result.MockMvcResultHandlers;
import org.springframework.test.web.servlet.result.MockMvcResultMatchers;
import org.springframework.test.web.servlet.setup.MockMvcBuilders;
import org.springframework.web.context.WebApplicationContext;@RunWith(SpringRunner.class)
@SpringBootTestpublic class LearnControllerTest {
@Autowired
private WebApplicationContext wac;private MockMvc mvc;
private MockHttpSession session;@Before
public void setupMockMvc(){
mvc = MockMvcBuilders.webAppContextSetup(wac).build(); 初始化 MockMvc 对象
session = new MockHttpSession();
User user =new User("root","root");
session.setAttribute("user",user); 拦截器那边会判断用户是否登录,所以这里注入一个用户
}**
* 新增教程测试用例
* @throws Exception
*
@Test
public void addLearn() throws Exception{
String json="{\"author\":\"HAHAHAA\",\"title\":\"Spring\",\"url\":\"http:tengj.top\"}";
mvc.perform(MockMvcRequestBuilders.post("learnadd")
.accept(MediaType.APPLICATION_JSON_UTF8)
.content(json.getBytes()) 传 json 参数
.session(session)
)
.andExpect(MockMvcResultMatchers.status().isOk())
.andDo(MockMvcResultHandlers.print());
}**
* 获取教程测试用例
* @throws Exception
*
@Test
public void qryLearn() throws Exception {
mvc.perform(MockMvcRequestBuilders.get("learnresource1001")
.contentType(MediaType.APPLICATION_JSON_UTF8)
.accept(MediaType.APPLICATION_JSON_UTF8)
.session(session)
)
.andExpect(MockMvcResultMatchers.status().isOk())
.andExpect(MockMvcResultMatchers.jsonPath("$.author").value("嘟嘟 MD 独立博客"))
.andExpect(MockMvcResultMatchers.jsonPath("$.title").value("Spring Boot 干货系列"))
.andDo(MockMvcResultHandlers.print());
}**
* 批改教程测试用例
* @throws Exception
*
@Test
public void updateLearn() throws Exception{
String json="{\"author\":\" 测试批改 \",\"id\":1031,\"title\":\"Spring Boot 干货系列 \",\"url\":\"http:tengj.top\"}";
mvc.perform(MockMvcRequestBuilders.post("learnupdate")
.accept(MediaType.APPLICATION_JSON_UTF8)
.content(json.getBytes())传 json 参数
.session(session)
)
.andExpect(MockMvcResultMatchers.status().isOk())
.andDo(MockMvcResultHandlers.print());
}**
* 删除教程测试用例
* @throws Exception
*
@Test
public void deleteLearn() throws Exception{
String json="[1031]";
mvc.perform(MockMvcRequestBuilders.post("learndelete")
.accept(MediaType.APPLICATION_JSON_UTF8)
.content(json.getBytes())传 json 参数
.session(session)
)
.andExpect(MockMvcResultMatchers.status().isOk())
.andDo(MockMvcResultHandlers.print());
}}
下面实现了根本的增删改查的测试用例,应用 MockMvc 的时候须要先用 MockMvcBuilders 应用构建 MockMvc 对象,如下
@Before
public void setupMockMvc(){
mvc = MockMvcBuilders.webAppContextSetup(wac).build(); 初始化 MockMvc 对象
session = new MockHttpSession();
User user =new User("root","root");
session.setAttribute("user",user); 拦截器那边会判断用户是否登录,所以这里注入一个用户
}
因为拦截器那边会判断是否登录,所以这里我注入了一个用户,你也能够间接批改拦截器勾销验证用户登录,先测试完再开启。
这里拿一个例子来介绍一下 MockMvc 简略的办法
**
* 获取教程测试用例
* @throws Exception
*
@Test
public void qryLearn() throws Exception {
mvc.perform(MockMvcRequestBuilders.get("learnresource1001")
.contentType(MediaType.APPLICATION_JSON_UTF8)
.accept(MediaType.APPLICATION_JSON_UTF8)
.session(session)
)
.andExpect(MockMvcResultMatchers.status().isOk())
.andExpect(MockMvcResultMatchers.jsonPath("$.author").value("嘟嘟 MD 独立博客"))
.andExpect(MockMvcResultMatchers.jsonPath("$.title").value("Spring Boot 干货系列"))
.andDo(MockMvcResultHandlers.print());
}
- mockMvc.perform 执行一个申请
- MockMvcRequestBuilders.get(“user1”)结构一个申请,Post 申请就用.post 办法
- contentType(MediaType.APPLICATION_JSON_UTF8)代表发送端发送的数据格式是
applicationjson;charset=UTF-8
- accept(MediaType.APPLICATION_JSON_UTF8)代表客户端心愿承受的数据类型为
applicationjson;charset=UTF-8
- session(session)注入一个 session,这样拦截器才能够通过
- ResultActions.andExpect 增加执行实现后的断言
- ResultActions.andExpect(MockMvcResultMatchers.status().isOk())办法看申请的状态响应码是否为 200 如果不是则抛异样,测试不通过
- andExpect(MockMvcResultMatchers.jsonPath(“$.author”).value(“嘟嘟 MD 独立博客”))这里 jsonPath 用来获取 author 字段比对是否为
嘟嘟 MD 独立博客
, 不是就测试不通过
- ResultActions.andDo 增加一个后果处理器,示意要对后果做点什么事件,比方此处应用 MockMvcResultHandlers.print()输入整个响应后果信息
本例子测试如下:
四、新断言 assertThat 应用
JUnit 4.4 联合 Hamcrest 提供了一个全新的断言语法——assertThat。程序员能够只应用 assertThat 一个断言语句,联合 Hamcrest 提供的匹配符,就能够表白全副的测试思维,咱们引入的版本是 Junit4.12 所以反对 assertThat。
清单 1 assertThat 根本语法
assertThat(\[value\], \[matcher statement\] );
- value 是接下来想要测试的变量值;
- matcher statement 是应用 Hamcrest 匹配符来表白的对后面变量所冀望的值的申明,如果 value 值与 matcher statement 所表白的期望值相符,则测试胜利,否则测试失败。
assertThat 的长处
- 长处 1:以前 JUnit 提供了很多的 assertion 语句,如:assertEquals,assertNotSame,assertFalse,assertTrue,assertNotNull,assertNull 等,当初有了 JUnit 4.4,一条 assertThat 即能够代替所有的 assertion 语句,这样能够在所有的单元测试中只应用一个断言办法,使得编写测试用例变得简略,代码格调变得对立,测试代码也更容易保护。
- 长处 2:assertThat 应用了 Hamcrest 的 Matcher 匹配符,用户能够应用匹配符规定的匹配准则准确的指定一些想设定满足的条件,具备很强的易读性,而且应用起来更加灵便。如清单 2 所示:
清单 2 应用匹配符 Matcher 和不应用之间的比拟
想判断某个字符串 s 是否含有子字符串 "developer" 或 "Works" 两头的一个
JUnit 4.4 以前的版本:assertTrue(s.indexOf("developer")>-1||s.indexOf("Works")>-1 );
JUnit 4.4:
assertThat(s, anyOf(containsString("developer"), containsString("Works")));
匹配符 anyOf 示意任何一个条件满足则成立,相似于逻辑或 "||",匹配符 containsString 示意是否含有参数子
字符串,文章接下来会对匹配符进行具体介绍
长处 3:assertThat 不再像 assertEquals 那样,应用比拟难懂的“谓宾主”语法模式(如:assertEquals(3, x);),相同,assertThat 应用了相似于“主谓宾”的易读语法模式(如:assertThat(x,is(3));),使得代码更加直观、易读。
注:本篇博文为转载,原问地址:Spring Boot 干货系列:(十二)Spring Boot 应用单元测试