看到一篇十分好的对于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)@SpringBootTestpublic 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对象,如下
@Beforepublic void setupMockMvc(){ mvc = MockMvcBuilders.webAppContextSetup(wac).build(); 初始化MockMvc对象 session = new MockHttpSession(); User user =new User("root","root"); session.setAttribute("user",user); 拦截器那边会判断用户是否登录,所以这里注入一个用户}
因为拦截器那边会判断是否登录,所以这里我注入了一个用户,你也能够间接批改拦截器勾销验证用户登录,先测试完再开启。
这里拿一个例子来介绍一下MockMvc简略的办法
** * 获取教程测试用例 * @throws Exception *@Testpublic 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应用单元测试