1. 介紹
1.1 為什么需要測試
測試是軟件開發(fā)不可或缺的一部分缎讼。 在實(shí)際應(yīng)用程序中坑匠,服務(wù)通常依賴于訪問外部系統(tǒng),因此提供適當(dāng)?shù)母綦x測試非常重要夹纫,這樣我們就可以專注于測試給定單元的功能舰讹,而無需涉及整個(gè)類層次結(jié)構(gòu)。
1.2 Spring解決方案
Spring提供了spring-test包用于測試匈睁,在Spring Boot中通過引入spring-boot-starter-test依賴以啟用桶错。Spring提供了三種測試方案來針對(duì)不同場景下的測試。
1.2.1 MockMvc
針對(duì)Controller的接口測試糯钙,我們可以使用MockMvc進(jìn)行退腥,本節(jié)也將介紹此種框架的使用。
1.2.2 HtmlUnit
HtmlUnit與MockMvc結(jié)合享潜,實(shí)現(xiàn)了Html頁面中表單的測試剑按±绞酰可以通過獲取某個(gè)網(wǎng)頁的內(nèi)容鸟废,操作其中的元素,模擬事件盒延。實(shí)現(xiàn)針對(duì)網(wǎng)頁的測試兰英。
1.2.3 RestTemplate
適用于完全模擬客戶端的請(qǐng)求測試畦贸,無須關(guān)心服務(wù)器端的運(yùn)行(即服務(wù)器端在其他服務(wù)器已經(jīng)運(yùn)行,本地?zé)o須啟動(dòng))趋厉。此種方式一般不適用于后端開發(fā)人員的單元測試(因?yàn)楹蠖碎_發(fā)的單元測試其實(shí)是在開發(fā)過程中的君账,必然需要在本地運(yùn)行服務(wù)端)沈善。
2. MockMvc的使用
2.1 基本組成與流程
MockMvc的幾個(gè)重要組件如下:
2.1.1 MockHttpServletRequestBuilder
創(chuàng)建請(qǐng)求闻牡,可以設(shè)置參數(shù)、頭信息玖翅、編碼割以、Cookies等基本http請(qǐng)求所含的所有信息。
2.1.2 MockMvc
客戶端猜极,主要入口魔吐,執(zhí)行請(qǐng)求莱找。
2.1.3 ResultActions
結(jié)果與動(dòng)作,MockMvc將MockHttpServletRequestBuilder構(gòu)造的請(qǐng)求發(fā)出后奥溺,返回的結(jié)果浮定。并且可以在此基礎(chǔ)上,針對(duì)結(jié)果添加一些動(dòng)作立美。包含:
- andExpect:預(yù)期結(jié)果是否與真實(shí)結(jié)果一致
- andDo:針對(duì)結(jié)果執(zhí)行腳本建蹄,常用的為print(),打印結(jié)果
- andReturn:獲取結(jié)果
其中andExpect與andDo返回的類型扔為ResultActions痛单,故可以使用鏈?zhǔn)降姆绞教砑佣鄠€(gè)動(dòng)作劲腿。
2.1.4 基本流程圖
2.2 SpringBoot中的MockMvc使用
SpringBoot中的MockMvc使用相對(duì)簡單
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 創(chuàng)建BaseTest
為了簡化代碼挥吵,我們創(chuàng)建一個(gè)BaseTest來封裝基礎(chǔ)的測試流程蔫劣。
@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請(qǐng)求測試
*/
@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請(qǐng)求測試
*/
@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是一個(gè)用于讀取JSON文檔的Java DSL。嫌松,
MockMvc引入它用于andExpect中來讀取奕污、比對(duì)json結(jié)果。通過表達(dá)式讀取文檔贾陷,并傳入比對(duì)的方法以此進(jìn)行判斷髓废。
寫法可以是
$.store.book[0].title
或
$['store']['book'][0]['title']
JsonPath的表達(dá)式的語法類似于jquery:
操作符 | 描述 |
---|---|
$ | 要查詢的根元素慌洪。所有表達(dá)式開始元素凑保。 |
@ | 根據(jù)過濾表達(dá)式查詢節(jié)點(diǎn) |
* | 通配符欧引。可在任何名稱或數(shù)字需要的地方使用憋肖。 |
.. | 深層掃描。可在任何需要名稱的地方使用租副。 |
.<name> | 獲取節(jié)點(diǎn) |
['<name>' (, '<name>')] | 根據(jù)名稱獲取子節(jié)點(diǎn)$['store']['book'] |
[<number> (, <number>)] | 根據(jù)索引獲取子節(jié)點(diǎn)$['store'][0] |
[start:end] | 獲取從start到end的節(jié)點(diǎn)列表 |
[?(<expression>)] | 過濾表達(dá)式用僧。表達(dá)式必須求值為布爾值责循。 |