1. 介紹
1.1 為什么需要測試
測試是軟件開發(fā)不可或缺的一部分。 在實際應(yīng)用程序中,服務(wù)通常依賴于訪問外部系統(tǒng),因此提供適當(dāng)?shù)母綦x測試非常重要,這樣我們就可以專注于測試給定單元的功能,而無需涉及整個類層次結(jié)構(gòu)。
1.2 Spring解決方案
Spring提供了spring-test包用于測試,在Spring Boot中通過引入spring-boot-starter-test依賴以啟用。Spring提供了三種測試方案來針對不同場景下的測試。
1.2.1 MockMvc
針對Controller的接口測試,我們可以使用MockMvc進行,本節(jié)也將介紹此種框架的使用。
1.2.2 HtmlUnit
HtmlUnit與MockMvc結(jié)合,實現(xiàn)了Html頁面中表單的測試??梢酝ㄟ^獲取某個網(wǎng)頁的內(nèi)容,操作其中的元素,模擬事件。實現(xiàn)針對網(wǎng)頁的測試。
1.2.3 RestTemplate
適用于完全模擬客戶端的請求測試,無須關(guān)心服務(wù)器端的運行(即服務(wù)器端在其他服務(wù)器已經(jīng)運行,本地?zé)o須啟動)。此種方式一般不適用于后端開發(fā)人員的單元測試(因為后端開發(fā)的單元測試其實是在開發(fā)過程中的,必然需要在本地運行服務(wù)端)。
2. MockMvc的使用
2.1 基本組成與流程
MockMvc的幾個重要組件如下:
2.1.1 MockHttpServletRequestBuilder
創(chuàng)建請求,可以設(shè)置參數(shù)、頭信息、編碼、Cookies等基本http請求所含的所有信息。
2.1.2 MockMvc
客戶端,主要入口,執(zhí)行請求。
2.1.3 ResultActions
結(jié)果與動作,MockMvc將MockHttpServletRequestBuilder構(gòu)造的請求發(fā)出后,返回的結(jié)果。并且可以在此基礎(chǔ)上,針對結(jié)果添加一些動作。包含:
- andExpect:預(yù)期結(jié)果是否與真實結(jié)果一致
- andDo:針對結(jié)果執(zhí)行腳本,常用的為print(),打印結(jié)果
- andReturn:獲取結(jié)果
其中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 創(chuàng)建BaseTest
為了簡化代碼,我們創(chuàng)建一個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請求測試
*/
@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結(jié)果。通過表達式讀取文檔,并傳入比對的方法以此進行判斷。
寫法可以是
$.store.book[0].title
或
$['store']['book'][0]['title']
JsonPath的表達式的語法類似于jquery:
| 操作符 | 描述 |
|---|---|
| $ | 要查詢的根元素。所有表達式開始元素。 |
| @ | 根據(jù)過濾表達式查詢節(jié)點 |
| * | 通配符。可在任何名稱或數(shù)字需要的地方使用。 |
| .. | 深層掃描??稍谌魏涡枰Q的地方使用。 |
| .<name> | 獲取節(jié)點 |
| ['<name>' (, '<name>')] | 根據(jù)名稱獲取子節(jié)點$['store']['book'] |
| [<number> (, <number>)] | 根據(jù)索引獲取子節(jié)點$['store'][0] |
| [start:end] | 獲取從start到end的節(jié)點列表 |
| [?(<expression>)] | 過濾表達式。表達式必須求值為布爾值。 |