1. 介绍
1.1 为什么需要测试
测试是软件开发不可或缺的一部分。 在实际应用程序中,服务通常依赖于访问外部系统,因此提供适当的隔离测试非常重要,这样我们就可以专注于测试给定单元的功能,而无需涉及整个类层次结构。
1.2 Spring解决方案
Spring提供了spring-test包用于测试,在Spring Boot中通过引入spring-boot-starter-test依赖以启用。Spring提供了三种测试方案来针对不同场景下的测试。
1.2.1 MockMvc
针对Controller的接口测试,我们可以使用MockMvc进行,本节也将介绍此种框架的使用。
1.2.2 HtmlUnit
HtmlUnit与MockMvc结合,实现了Html页面中表单的测试。可以通过获取某个网页的内容,操作其中的元素,模拟事件。实现针对网页的测试。
1.2.3 RestTemplate
适用于完全模拟客户端的请求测试,无须关心服务器端的运行(即服务器端在其他服务器已经运行,本地无须启动)。此种方式一般不适用于后端开发人员的单元测试(因为后端开发的单元测试其实是在开发过程中的,必然需要在本地运行服务端)。
2. MockMvc的使用
2.1 基本组成与流程
MockMvc的几个重要组件如下:
2.1.1 MockHttpServletRequestBuilder
创建请求,可以设置参数、头信息、编码、Cookies等基本http请求所含的所有信息。
2.1.2 MockMvc
客户端,主要入口,执行请求。
2.1.3 ResultActions
结果与动作,MockMvc将MockHttpServletRequestBuilder构造的请求发出后,返回的结果。并且可以在此基础上,针对结果添加一些动作。包含:
- andExpect:预期结果是否与真实结果一致
- andDo:针对结果执行脚本,常用的为print(),打印结果
- andReturn:获取结果
其中andExpect与andDo返回的类型扔为ResultActions,故可以使用链式的方式添加多个动作。
2.1.4 基本流程图
基本流程2.2 SpringBoot中的MockMvc使用
SpringBoot中的MockMvc使用相对简单
2.2.1 编写Controller接口
@RestController
@RequestMapping("/demo")
public class DemoController {
@PostMapping("testPost")
public String testPost(@RequestBody String request) {
System.out.println(request);
return "{\"code\":\"0000\"}";
}
@GetMapping("/testGet/{id}")
public String testGet(@PathVariable String id, @RequestParam("name") String name) {
System.out.println(id);
System.out.println(name);
return "{\"code\":\"0000\"}";
}
}
2.2.2 创建BaseTest
为了简化代码,我们创建一个BaseTest来封装基础的测试流程。
@RunWith(SpringJUnit4ClassRunner.class)
public abstract class BaseTest {
@Autowired
protected WebApplicationContext wac;
protected MockMvc mockMvc;
@Before
public void setup() {
this.mockMvc = MockMvcBuilders.webAppContextSetup(wac).build();
}
public ResultActions buildRequest(Supplier<MockHttpServletRequestBuilder> method) throws Exception {
String header = getBaseHeader();
header = header == null ? "" : header;
return this.mockMvc.perform(method.get().characterEncoding(StandardCharsets.UTF_8.name())
.contentType(MediaType.APPLICATION_JSON)
.header(HttpHeaders.AUTHORIZATION, header)).andDo(print()).andExpect(status().is2xxSuccessful());
}
public abstract String getBaseHeader();
}
2.2.3 单元测试示例
@SpringBootTest(classes = TestApplication.class)
public class ApiTest extends BaseTest {
@Override
public String getBaseHeader() {
return null;
}
/**
* get请求测试
*/
@Test
public void testGet() throws Exception {
MockHttpServletRequestBuilder mockHttpServletRequestBuilder = MockMvcRequestBuilders
.get("/demo/testGet/{id}", 1111);
mockHttpServletRequestBuilder.param("name", "张三");
super.buildRequest(() -> mockHttpServletRequestBuilder)
.andExpect(jsonPath("$.code", is("0000")));
}
/**
* post请求测试
*/
@Test
public void testPost() throws Exception {
MockHttpServletRequestBuilder mockHttpServletRequestBuilder = MockMvcRequestBuilders
.post("/demo/testPost");
Map<String,Object> requestMap = new HashMap<>();
requestMap.put("name","张三");
mockHttpServletRequestBuilder.content(JSON.toJSONString(requestMap));
super.buildRequest(() -> mockHttpServletRequestBuilder)
.andExpect(jsonPath("$.code", is("0000")));
}
}
2.2.4 JsonPath
JsonPath是一个用于读取JSON文档的Java DSL。,
MockMvc引入它用于andExpect中来读取、比对json结果。通过表达式读取文档,并传入比对的方法以此进行判断。
写法可以是
$.store.book[0].title
或
$['store']['book'][0]['title']
JsonPath的表达式的语法类似于jquery:
操作符 | 描述 |
---|---|
$ | 要查询的根元素。所有表达式开始元素。 |
@ | 根据过滤表达式查询节点 |
* | 通配符。可在任何名称或数字需要的地方使用。 |
.. | 深层扫描。可在任何需要名称的地方使用。 |
.<name> | 获取节点 |
['<name>' (, '<name>')] | 根据名称获取子节点$['store']['book'] |
[<number> (, <number>)] | 根据索引获取子节点$['store'][0] |
[start:end] | 获取从start到end的节点列表 |
[?(<expression>)] | 过滤表达式。表达式必须求值为布尔值。 |
网友评论