1 什么是Mock?
截取一段stackflow中的解釋:
Mocking isprimarily used in unit testing. An object under test may have dependencies onother (complex) objects. To isolate the behaviour of the object you want totest you replace the other objects by mocks that simulate the behavior of thereal objects. This is useful if the real objects are impractical to incorporateinto the unit test.
MOCK主要被用於在單測中,某個對象在測試過程中有可能依賴於其他的復雜對象,通過mocks去模擬真實的其他對象(模塊)去代替你想要去測試的其他的對象(模塊),如果其他對象(模塊)是很難從單元測試中剝離開來的話,這是非常有用的…..人工翻譯,大概意思ok。准確翻譯請使用采用人工智能翻譯的fanyi.sogou.com :) 實力硬廣。
2 Mock有什么用?
上面其實已經解釋了,mock又什么用,為了加強讀者理解,再來看Mock有哪些用途。首先,Mock可以用來解除測試對象對外部服務的依賴(比如數據庫,第三方接口等),使得測試用例可以獨立運行。不管是傳統的單體應用,還是現在流行的微服務,這點都特別重要,因為任何外部依賴的存在都會極大的限制測試用例的可遷移性和穩定性。可遷移性是指,如果要在一個新的測試環境中運行相同的測試用例,那么除了要保證測試對象自身能夠正常運行,還要保證所有依賴的外部服務也能夠被正常調用。穩定性是指,如果外部服務不可用,那么測試用例也可能會失敗。通過Mock去除外部依賴之后,不管是測試用例的可遷移性還是穩定性,都能夠上一個台階。
Mock的第二個好處是替換外部服務調用,提升測試用例的運行速度。任何外部服務調用至少是跨進程級別的消耗,甚至是跨系統、跨網絡的消耗,而Mock可以把消耗降低到進程內。比如原來一次秒級的網絡請求,通過Mock可以降至毫秒級,整整3個數量級的差別。
Mock的第三個好處是提升測試效率。這里說的測試效率有兩層含義。第一層含義是單位時間運行的測試用例數,這是運行速度提升帶來的直接好處。而第二層含義是一個測試人員單位時間創建的測試用例數。如何理解這第二層含義呢?以單體應用為例,隨着業務復雜度的上升,為了運行一個測試用例可能需要准備很多測試數據,與此同時還要盡量保證多個測試用例之間的測試數據互不干擾。為了做到這一點,測試人員往往需要花費大量的時間來維護一套可運行的測試數據。有了Mock之后,由於去除了測試用例之間共享的數據庫依賴,測試人員就可以針對每一個或者每一組測試用例設計一套獨立的測試數據,從而很容易的做到不同測試用例之間的數據隔離性。而對於微服務,由於一個微服務可能級聯依賴很多其他的微服務,運行一個測試用例甚至需要跨系統准備一套測試數據,如果沒有Mock,基本上可以說是不可能的。因此,不管是單體應用還是微服務,有了Mock之后,QE就可以省去大量的准備測試數據的時間,專注於測試用例本身,自然也就提升了單人的測試效率。
3 如何Mock?
說了這么多Mock的好處,那么究竟如何在測試中使用Mock呢?針對不同的測試場景,可以選擇不同的Mock框架。
3.1 Mockito
如果測試對象是一個方法,尤其是涉及數據庫操作的方法,那么Mockito可能是最好的選擇。作為使用最廣泛的Mock框架,Mockito出於EasyMock而勝於EasyMock,乃至被默認集成進Spring Testing。其實現原理是,通過CGLib在運行時為每一個被Mock的類或者對象動態生成一個代理對象,返回預先設計的結果。集成Mockito的基本步驟是:
1. 標記被Mock的類或者對象,生成代理對象
2. 通過Mockito API定制代理對象的行為
3. 調用代理對象的方法,獲得預先設計的結果
下面是我網上隨手找的一個例子,
@RunWith(SpringRunner.class)
@SpringBootTest
publicclassSignonServiceTests {
// 測試對象,一個服務類
@Autowired
private SignonService signonService;
// 被Mock的類,被服務類所依賴的一個DAO類
@MockBean
private SignonDao dao;
@Test
publicvoidtestFindAll() {
// SignonService#findAll()內部會調用SignonDao#findAll()
// 如果不做定制,所有被Mock的類默認返回空
List<Signon> signons = signonService.findAll();
assertTrue(CollectionUtils.isEmpty(signons));
// 定制返回結果
Signonsignon = new Signon();
signon.setUsername("foo");
when(dao.findAll()).thenReturn(Lists.newArrayList(signon));
signons =signonService.findAll();
// 驗證返回結果和預先設計的結果一致
assertEquals(1, signons.size());
assertEquals("foo", signons.get(0).getUsername());
}
}
從上面的測試用例可以看到,通過Mock服務類所依賴的DAO類,我們可以跳過所有的數據庫操作,任意定制返回結果,從而專注於測試服務類內部的業務邏輯。這是傳統的非Mock測試所難以實現的。
注意:Mockito不支持Mock私有方法或者靜態方法,如果要Mock這類方法,可以使用PowerMock。
3.2 WireMock
如果說Mocketo是瑞士軍刀,可以Mock Everything,那么WireMock就是為微服務而生的倚天劍。和處在對象層的Mockito不同,WireMock針對的是API。假設有兩個微服務,Service-A和Service-B,Service-A里的一個API(姑且稱為API-1),依賴於Service-B,那么使用傳統的測試方法,測試API-1時必然需要同時啟動Service-B。如果使用WireMock,那么就可以在Service-A端Mock所有依賴的Service-B的API,從而去掉Service-B這個外部依賴。
同樣看一個別人的一個例子:
@RunWith(SpringRunner.class)
@WebMvcTest(VacationController.class)
publicclassVacationControllerTests {
// Mock被依賴的另一個微服務
@Rule
public WireMockRule wireMockRule = new WireMockRule(3001);
@Autowired
private MockMvc mockMvc;
@Autowired
private ObjectMapper objectMapper;
@Before
publicvoidbefore() throwsJsonProcessingException {
// 定制返回結果
JsonResult<Boolean> expected = JsonResult.ok(true);
stubFor(get(urlPathEqualTo("/api/vacation/isWeekend"))
.willReturn(aResponse()
.withStatus(OK.value())
.withHeader(CONTENT_TYPE, APPLICATION_JSON_UTF8_VALUE)
.withBody(objectMapper.writeValueAsString(expected))));
}
@Test
publicvoidtestIsWeekendProxy() throws Exception{
// 構造請求參數
VacationRequest request = new VacationRequest();
request.setType(PERSONAL);
OffsetDateTime lastSunday =OffsetDateTime.now().with(TemporalAdjusters.previous(SUNDAY));
request.setStart(lastSunday);
request.setEnd(lastSunday.plusDays(1));
MockHttpServletRequestBuilder builder = MockMvcRequestBuilders.get("/vacation/isWeekend");
request.toMap().forEach((k, v) -> builder.param(k, v));
JsonResult<Boolean> expected = JsonResult.ok(true);
mockMvc.perform(builder)
// 驗證返回結果和預先設計的結果一致
.andExpect(status().isOk())
.andExpect(content().contentType(APPLICATION_JSON_UTF8))
.andExpect(content().string(objectMapper.writeValueAsString(expected)));
}
}
和Mockito類似,在測試用例中集成WireMock的基本步驟是:
1. 聲明代理服務,以替代被Mock的微服務
2. 通過WireMock API定制代理服務的返回結果
3. 調用代理服務,獲得預先設計的結果
值得一提的是,除了API方式的集成,WireMock還支持以Jar包的形式獨立運行,從配置文件中加載預先設計的響應結果,以替代被Mock的微服務。更多信息可以參閱官方文檔。
其他類似的Mock API的框架還有OkHttp的mockwebserver,moco和mockserver。mockwebserver也屬於嵌入式Mock框架的范疇,但功能過於簡單。moco,mockserver雖然功能完善,但需要獨立部署,和WireMock相比不具有優勢。
參考資料:
