面向開發(fā)的測試技術(shù)(一):Mock

引子:自上世紀末Kent Beck提出TDD(Test-Driven Development)開發(fā)理念以來,開發(fā)和測試的邊界變的越來越模糊础废,從原本上下游的依賴關(guān)系饥伊,逐步演變成你中有我、我中有你的互賴關(guān)系智亮,甚至很多公司設(shè)立了新的QE(Quality Engineer)職位俄删。和傳統(tǒng)的QA(Quality Assurance)不同宏怔,QE的主要職責是通過工程化的手段保證項目質(zhì)量,這些手段包括但不僅限于編寫單元測試畴椰、集成測試臊诊,搭建自動化測試流程,設(shè)計性能測試等迅矛》列桑可以說,QE身上兼具了QA的質(zhì)量意識和開發(fā)的工程能力秽褒。從這篇開始壶硅,我會從開發(fā)的角度分三期聊聊QE這個亦測試亦開發(fā)的角色所需的基本技能。

1 什么是Mock销斟?

在軟件測試領(lǐng)域庐椒,Mock的意思是模擬,簡單來說蚂踊,就是通過某種技術(shù)手段模擬測試對象的行為约谈,返回預(yù)先設(shè)計的結(jié)果。這里的關(guān)鍵詞是預(yù)先設(shè)計犁钟,也就是說對于任意被測試的對象棱诱,可以根據(jù)具體測試場景的需要,返回特定的結(jié)果涝动。打個比方迈勋,就像BBC紀錄片里面的假企鵝,可以根據(jù)拍攝需要作出不同的反應(yīng)醋粟。

2 Mock有什么用靡菇?

理解了什么是Mock,再來看Mock有哪些用途米愿。首先厦凤,Mock可以用來解除測試對象對外部服務(wù)的依賴(比如數(shù)據(jù)庫,第三方接口等)育苟,使得測試用例可以獨立運行较鼓。不管是傳統(tǒng)的單體應(yīng)用,還是現(xiàn)在流行的微服務(wù)违柏,這點都特別重要博烂,因為任何外部依賴的存在都會極大的限制測試用例的可遷移性和穩(wěn)定性拓哺。可遷移性是指脖母,如果要在一個新的測試環(huán)境中運行相同的測試用例,那么除了要保證測試對象自身能夠正常運行闲孤,還要保證所有依賴的外部服務(wù)也能夠被正常調(diào)用谆级。穩(wěn)定性是指,如果外部服務(wù)不可用讼积,那么測試用例也可能會失敗肥照。通過Mock去除外部依賴之后,不管是測試用例的可遷移性還是穩(wěn)定性勤众,都能夠上一個臺階舆绎。

Mock的第二個好處是替換外部服務(wù)調(diào)用,提升測試用例的運行速度们颜。任何外部服務(wù)調(diào)用至少是跨進程級別的消耗吕朵,甚至是跨系統(tǒng)、跨網(wǎng)絡(luò)的消耗窥突,而Mock可以把消耗降低到進程內(nèi)努溃。比如原來一次秒級的網(wǎng)絡(luò)請求,通過Mock可以降至毫秒級阻问,整整3個數(shù)量級的差別梧税。

Mock的第三個好處是提升測試效率。這里說的測試效率有兩層含義称近。第一層含義是單位時間運行的測試用例數(shù)第队,這是運行速度提升帶來的直接好處。而第二層含義是一個QE單位時間創(chuàng)建的測試用例數(shù)刨秆。如何理解這第二層含義呢凳谦?以單體應(yīng)用為例,隨著業(yè)務(wù)復(fù)雜度的上升坛善,為了運行一個測試用例可能需要準備很多測試數(shù)據(jù)晾蜘,與此同時還要盡量保證多個測試用例之間的測試數(shù)據(jù)互不干擾。為了做到這一點眠屎,QE往往需要花費大量的時間來維護一套可運行的測試數(shù)據(jù)剔交。有了Mock之后,由于去除了測試用例之間共享的數(shù)據(jù)庫依賴改衩,QE就可以針對每一個或者每一組測試用例設(shè)計一套獨立的測試數(shù)據(jù)岖常,從而很容易的做到不同測試用例之間的數(shù)據(jù)隔離性。而對于微服務(wù)葫督,由于一個微服務(wù)可能級聯(lián)依賴很多其他的微服務(wù)竭鞍,運行一個測試用例甚至需要跨系統(tǒng)準備一套測試數(shù)據(jù)板惑,如果沒有Mock,基本上可以說是不可能的偎快。因此冯乘,不管是單體應(yīng)用還是微服務(wù),有了Mock之后晒夹,QE就可以省去大量的準備測試數(shù)據(jù)的時間裆馒,專注于測試用例本身,自然也就提升了單人的測試效率丐怯。

3 如何Mock喷好?

說了這么多Mock的好處,那么究竟如何在測試中使用Mock呢读跷?針對不同的測試場景梗搅,可以選擇不同的Mock框架。

3.1 Mockito

如果測試對象是一個方法效览,尤其是涉及數(shù)據(jù)庫操作的方法无切,那么Mockito可能是最好的選擇。作為使用最廣泛的Mock框架钦铺,Mockito出于EasyMock而勝于EasyMock订雾,乃至被默認集成進Spring Testing。其實現(xiàn)原理是矛洞,通過CGLib在運行時為每一個被Mock的類或者對象動態(tài)生成一個代理對象洼哎,返回預(yù)先設(shè)計的結(jié)果。集成Mockito的基本步驟是:

  1. 標記被Mock的類或者對象沼本,生成代理對象
  2. 通過Mockito API定制代理對象的行為
  3. 調(diào)用代理對象的方法噩峦,獲得預(yù)先設(shè)計的結(jié)果

下面是我GitHub上的示例工程里的一個例子,

@RunWith(SpringRunner.class)
@SpringBootTest
public class SignonServiceTests {

    // 測試對象抽兆,一個服務(wù)類
    @Autowired
    private SignonService signonService;

    // 被Mock的類识补,被服務(wù)類所依賴的一個DAO類
    @MockBean
    private SignonDao dao;

    @Test
    public void testFindAll() {
        // SignonService#findAll()內(nèi)部會調(diào)用SignonDao#findAll()
        // 如果不做定制,所有被Mock的類默認返回空
        List<Signon> signons = signonService.findAll();
        assertTrue(CollectionUtils.isEmpty(signons));

        // 定制返回結(jié)果
        Signon signon = new Signon();
        signon.setUsername("foo");
        when(dao.findAll()).thenReturn(Lists.newArrayList(signon));

        signons = signonService.findAll();
        // 驗證返回結(jié)果和預(yù)先設(shè)計的結(jié)果一致
        assertEquals(1, signons.size());
        assertEquals("foo", signons.get(0).getUsername());
    }
}

從上面的測試用例可以看到辫红,通過Mock服務(wù)類所依賴的DAO類凭涂,我們可以跳過所有的數(shù)據(jù)庫操作,任意定制返回結(jié)果贴妻,從而專注于測試服務(wù)類內(nèi)部的業(yè)務(wù)邏輯切油。這是傳統(tǒng)的非Mock測試所難以實現(xiàn)的。

注意:Mockito不支持Mock私有方法或者靜態(tài)方法名惩,如果要Mock這類方法澎胡,可以使用PowerMock

3.2 WireMock

如果說Mocketo是瑞士軍刀,可以Mock Everything攻谁,那么WireMock就是為微服務(wù)而生的倚天劍稚伍。和處在對象層的Mockito不同,WireMock針對的是API戚宦。假設(shè)有兩個微服務(wù)个曙,Service-A和Service-B,Service-A里的一個API(姑且稱為API-1)受楼,依賴于Service-B困檩,那么使用傳統(tǒng)的測試方法,測試API-1時必然需要同時啟動Service-B那槽。如果使用WireMock,那么就可以在Service-A端Mock所有依賴的Service-B的API等舔,從而去掉Service-B這個外部依賴骚灸。

同樣看一個我GitHub上的示例工程里的一個例子,

@RunWith(SpringRunner.class)
@WebMvcTest(VacationController.class)
public class VacationControllerTests {

    // Mock被依賴的另一個微服務(wù)
    @Rule
    public WireMockRule wireMockRule = new WireMockRule(3001);
    
    @Autowired
    private MockMvc mockMvc;

    @Autowired
    private ObjectMapper objectMapper;

    @Before
    public void before() throws JsonProcessingException {
        // 定制返回結(jié)果
        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
    public void testIsWeekendProxy() throws Exception {
        // 構(gòu)造請求參數(shù)
        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)
                // 驗證返回結(jié)果和預(yù)先設(shè)計的結(jié)果一致
                .andExpect(status().isOk())
                .andExpect(content().contentType(APPLICATION_JSON_UTF8))
                .andExpect(content().string(objectMapper.writeValueAsString(expected)));
    }
}

和Mockito類似慌植,在測試用例中集成WireMock的基本步驟是:

  1. 聲明代理服務(wù)甚牲,以替代被Mock的微服務(wù)
  2. 通過WireMock API定制代理服務(wù)的返回結(jié)果
  3. 調(diào)用代理服務(wù),獲得預(yù)先設(shè)計的結(jié)果

值得一提的是蝶柿,除了API方式的集成丈钙,WireMock還支持以Jar包的形式獨立運行,從配置文件中加載預(yù)先設(shè)計的響應(yīng)結(jié)果交汤,以替代被Mock的微服務(wù)雏赦。更多信息可以參閱官方文檔。

其他類似的Mock API的框架還有OkHttp的mockwebserver芙扎,mocomockserver星岗。mockwebserver也屬于嵌入式Mock框架的范疇,但功能過于簡單戒洼。moco俏橘,mockserver雖然功能完善,但需要獨立部署圈浇,和WireMock相比不具有優(yōu)勢寥掐。

4 小結(jié)

以上就是我對Mock技術(shù)的一些見解,歡迎你到我的留言板分享磷蜀,和大家一起過過招召耘。最后還要說一句,Mock技術(shù)雖然強大蠕搜,但主要還是適用于單元測試怎茫,在集成測試,性能測試,自動化測試等其他測試領(lǐng)域使用并不多轨蛤。

5 參考

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末蜜宪,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子祥山,更是在濱河造成了極大的恐慌圃验,老刑警劉巖,帶你破解...
    沈念sama閱讀 218,525評論 6 507
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件缝呕,死亡現(xiàn)場離奇詭異澳窑,居然都是意外死亡,警方通過查閱死者的電腦和手機供常,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,203評論 3 395
  • 文/潘曉璐 我一進店門摊聋,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人栈暇,你說我怎么就攤上這事麻裁。” “怎么了源祈?”我有些...
    開封第一講書人閱讀 164,862評論 0 354
  • 文/不壞的土叔 我叫張陵煎源,是天一觀的道長。 經(jīng)常有香客問我香缺,道長手销,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,728評論 1 294
  • 正文 為了忘掉前任图张,我火速辦了婚禮锋拖,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘祸轮。我一直安慰自己姑隅,他們只是感情好,可當我...
    茶點故事閱讀 67,743評論 6 392
  • 文/花漫 我一把揭開白布倔撞。 她就那樣靜靜地躺著讲仰,像睡著了一般。 火紅的嫁衣襯著肌膚如雪痪蝇。 梳的紋絲不亂的頭發(fā)上鄙陡,一...
    開封第一講書人閱讀 51,590評論 1 305
  • 那天,我揣著相機與錄音躏啰,去河邊找鬼趁矾。 笑死,一個胖子當著我的面吹牛给僵,可吹牛的內(nèi)容都是我干的毫捣。 我是一名探鬼主播详拙,決...
    沈念sama閱讀 40,330評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼蔓同!你這毒婦竟也來了饶辙?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 39,244評論 0 276
  • 序言:老撾萬榮一對情侶失蹤斑粱,失蹤者是張志新(化名)和其女友劉穎弃揽,沒想到半個月后,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體则北,經(jīng)...
    沈念sama閱讀 45,693評論 1 314
  • 正文 獨居荒郊野嶺守林人離奇死亡矿微,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,885評論 3 336
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了尚揣。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片丙挽。...
    茶點故事閱讀 40,001評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡谆沃,死狀恐怖濒旦,靈堂內(nèi)的尸體忽然破棺而出懊蒸,到底是詐尸還是另有隱情,我是刑警寧澤滨巴,帶...
    沈念sama閱讀 35,723評論 5 346
  • 正文 年R本政府宣布,位于F島的核電站俺叭,受9級特大地震影響恭取,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜熄守,卻給世界環(huán)境...
    茶點故事閱讀 41,343評論 3 330
  • 文/蒙蒙 一蜈垮、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧裕照,春花似錦攒发、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,919評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至负间,卻和暖如春偶妖,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背政溃。 一陣腳步聲響...
    開封第一講書人閱讀 33,042評論 1 270
  • 我被黑心中介騙來泰國打工趾访, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人董虱。 一個月前我還...
    沈念sama閱讀 48,191評論 3 370
  • 正文 我出身青樓扼鞋,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子云头,可洞房花燭夜當晚...
    茶點故事閱讀 44,955評論 2 355

推薦閱讀更多精彩內(nèi)容