<kbd id="afajh"><form id="afajh"></form></kbd>
<strong id="afajh"><dl id="afajh"></dl></strong>
    <del id="afajh"><form id="afajh"></form></del>
        1. <th id="afajh"><progress id="afajh"></progress></th>
          <b id="afajh"><abbr id="afajh"></abbr></b>
          <th id="afajh"><progress id="afajh"></progress></th>

          阿里開(kāi)源新一代單元測(cè)試 Mock 工具!

          共 5774字,需瀏覽 12分鐘

           ·

          2020-12-30 17:04

          TestableMock是基于源碼和字節(jié)碼增強(qiáng)的Java單元測(cè)試輔助工具,包含以下功能:
          • 訪問(wèn)被測(cè)類私有成員:使單元測(cè)試能直接調(diào)用和訪問(wèn)被測(cè)類的私有成員,解決私有成員初始化和私有方法測(cè)試的問(wèn)題

          • 快速M(fèi)ock任意調(diào)用:使被測(cè)類的任意方法調(diào)用快速替換為Mock方法,實(shí)現(xiàn)"指哪換哪",解決傳統(tǒng)Mock工具使用繁瑣的問(wèn)題

          • 輔助測(cè)試void方法:利用Mock校驗(yàn)器對(duì)方法的內(nèi)部邏輯進(jìn)行檢查,解決無(wú)返回值方法難以實(shí)施單元測(cè)試的問(wèn)題

          訪問(wèn)私有成員字段和方法

          如今關(guān)于私有方法是否應(yīng)該做單元測(cè)試的爭(zhēng)論正逐漸消停,開(kāi)發(fā)者的普遍實(shí)踐已經(jīng)給出事實(shí)答案。通過(guò)公有方法間接測(cè)私有方法在很多情況下難以進(jìn)行,開(kāi)發(fā)者們更愿意通過(guò)修改方法可見(jiàn)性的辦法來(lái)讓原本私有的方法在測(cè)試用例中變得可測(cè)。

          此外,在單元測(cè)試中時(shí)常會(huì)需要對(duì)被測(cè)對(duì)象進(jìn)行特定的成員字段初始化,但有時(shí)由于被測(cè)類的構(gòu)造方法限制,使得無(wú)法便捷的對(duì)這些字段進(jìn)行賦值。那么,能否在不破壞被測(cè)類型封裝的情況下,允許單元測(cè)試用例內(nèi)的代碼直接訪問(wèn)被測(cè)類的私有方法和成員字段呢?TestableMock提供了兩種簡(jiǎn)單的解決方案。

          方法一:使用`@EnablePrivateAccess`注解

          只需為測(cè)試類添加@EnablePrivateAccess注解,即可在測(cè)試用例中獲得以下增強(qiáng)能力:

          • 調(diào)用被測(cè)類的私有方法(包括靜態(tài)方法)

          • 讀取被測(cè)類的私有字段(包括靜態(tài)字段)

          • 修改被測(cè)類的私有字段(包括靜態(tài)字段)

          • 修改被測(cè)類的常量字段(使用final修飾的字段,包括靜態(tài)字段)

          訪問(wèn)和修改私有、常量成員時(shí),IDE可能會(huì)提示語(yǔ)法有誤,但編譯器將能夠正常運(yùn)行測(cè)試。(使用編譯期代碼增強(qiáng),目前僅實(shí)現(xiàn)了Java語(yǔ)言的適配)

          效果見(jiàn)java-demo示例項(xiàng)目DemoPrivateAccessTest測(cè)試類中的用例。

          方法二:使用`PrivateAccessor`工具類

          若不希望看到IDE的語(yǔ)法錯(cuò)誤提醒,或是在非Java語(yǔ)言的JVM工程(譬如Kotlin語(yǔ)言)里,也可以借助PrivateAccessor工具類來(lái)直接訪問(wèn)私有成員。

          這個(gè)類提供了6個(gè)靜態(tài)方法:

          • PrivateAccessor.get(被測(cè)對(duì)象, "私有字段名") ? 讀取被測(cè)類的私有字段

          • PrivateAccessor.set(被測(cè)對(duì)象, "私有字段名", 新的值) ? 修改被測(cè)類的私有字段(或常量字段)

          • PrivateAccessor.invoke(被測(cè)對(duì)象, "私有方法名", 調(diào)用參數(shù)..) ? 調(diào)用被測(cè)類的私有方法

          • PrivateAccessor.getStatic(被測(cè)類型, "私有靜態(tài)字段名") ? 讀取被測(cè)類的靜態(tài)私有字段

          • PrivateAccessor.setStatic(被測(cè)類型, "私有靜態(tài)字段名", 新的值) ? 修改被測(cè)類的靜態(tài)私有字段(或靜態(tài)常量字段)

          • PrivateAccessor.invokeStatic(被測(cè)類型, "私有靜態(tài)方法名", 調(diào)用參數(shù)..) ? 調(diào)用被測(cè)類的靜態(tài)私有方法

          快速M(fèi)ock被測(cè)類的任意方法調(diào)用

          相比以往Mock工具以類為粒度的Mock方式,TestableMock允許用戶直接定義需要Mock的單個(gè)方法,并遵循約定優(yōu)于配置的原則,按照規(guī)則自動(dòng)在測(cè)試運(yùn)行時(shí)替換被測(cè)方法中的指定方法調(diào)用。

          歸納起來(lái)就兩條:

          • Mock非構(gòu)造方法,拷貝原方法定義到測(cè)試類,增加一個(gè)與調(diào)用者類型相同的參數(shù),加@MockMethod注解

          • Mock構(gòu)造方法,拷貝原方法定義到測(cè)試類,返回值換成構(gòu)造的類型,方法名隨意,加@MockContructor注解

          具體的Mock方法定義約定如下:

          1. 覆寫(xiě)任意類的方法調(diào)用

          在測(cè)試類里定義一個(gè)有@MockMethod注解的普通方法,使它與需覆寫(xiě)的方法名稱、參數(shù)、返回值類型完全一致,然后在其參數(shù)列表首位再增加一個(gè)類型為該方法原本所屬對(duì)象類型的參數(shù)。

          此時(shí)被測(cè)類中所有對(duì)該需覆寫(xiě)方法的調(diào)用,將在單元測(cè)試運(yùn)行時(shí),將自動(dòng)被替換為對(duì)上述自定義Mock方法的調(diào)用。

          注意:當(dāng)遇到待覆寫(xiě)方法有重名時(shí),可以將需覆寫(xiě)的方法名寫(xiě)到@MockMethod注解的targetMethod參數(shù)里,這樣Mock方法自身就可以隨意命名了。

          例如,被測(cè)類中有一處"anything".substring(1, 2)調(diào)用,我們希望在運(yùn)行測(cè)試的時(shí)候?qū)⑺鼡Q成一個(gè)固定字符串,則只需在測(cè)試類定義如下方法:

          //?原方法簽名為`String?substring(int,?int)`
          //?調(diào)用此方法的對(duì)象`"anything"`類型為`String`
          //?則Mock方法簽名在其參數(shù)列表首位增加一個(gè)類型為`String`的參數(shù)(名字隨意)
          //?此參數(shù)可用于獲得當(dāng)時(shí)的實(shí)際調(diào)用者的值和上下文
          @MockMethod
          private?String?substring(String?self,?int?i,?int?j)?{
          ????return?"sub_string";
          }

          下面這個(gè)例子展示了targetMethod參數(shù)的用法,其效果與上述示例相同:

          //?使用`targetMethod`指定需Mock的方法名
          //?此方法本身現(xiàn)在可以隨意命名,但方法參數(shù)依然需要遵循相同的匹配規(guī)則
          @MockMethod(targetMethod?=?"substring")
          private?String?use_any_mock_method_name(String?self,?int?i,?int?j)?{
          ????return?"sub_string";
          }

          完整代碼示例見(jiàn)java-demokotlin-demo示例項(xiàng)目中的should_able_to_mock_common_method()測(cè)試用例。(由于Kotlin對(duì)String類型進(jìn)行了魔改,故Kotlin示例中將被測(cè)方法在BlackBox類里加了一層封裝)

          2. 覆寫(xiě)被測(cè)類自身的成員方法

          有時(shí)候,在對(duì)某些方法進(jìn)行測(cè)試時(shí),希望將被測(cè)類自身的另外一些成員方法Mock掉。

          操作方法與前一種情況相同,Mock方法的第一個(gè)參數(shù)類型需與被測(cè)類相同,即可實(shí)現(xiàn)對(duì)被測(cè)類自身(不論是公有或私有)成員方法的覆寫(xiě)。

          例如,被測(cè)類中有一個(gè)簽名為String innerFunc(String)的私有方法,我們希望在測(cè)試的時(shí)候?qū)⑺鎿Q掉,則只需在測(cè)試類定義如下方法:

          //?被測(cè)類型是`DemoMock`
          //?因此在定義Mock方法時(shí),在目標(biāo)方法參數(shù)首位加一個(gè)類型為`DemoMock`的參數(shù)(名字隨意)
          @MockMethod
          private?String?innerFunc(DemoMock?self,?String?text)?{
          ????return?"mock_"?+?text;
          }

          3. 覆寫(xiě)任意類的靜態(tài)方法

          對(duì)于靜態(tài)方法的Mock與普通方法相同。但需要注意的是,靜態(tài)方法的Mock方法被調(diào)用時(shí),傳入的第一個(gè)參數(shù)實(shí)際值始終是null

          例如,在被測(cè)類中調(diào)用了BlackBox類型中的靜態(tài)方法secretBox(),改方法簽名為BlackBox secretBox(),則Mock方法如下:

          //?目標(biāo)靜態(tài)方法定義在`BlackBox`類型中
          //?在定義Mock方法時(shí),在目標(biāo)方法參數(shù)首位加一個(gè)類型為`BlackBox`的參數(shù)(名字隨意)
          //?此參數(shù)僅用于標(biāo)識(shí)目標(biāo)類型,實(shí)際傳入值將始終為`null`
          @MockMethod
          private?BlackBox?secretBox(BlackBox?ignore)?{
          ????return?new?BlackBox("not_secret_box");
          }

          完整代碼示例見(jiàn)java-demokotlin-demo示例項(xiàng)目中的should_able_to_mock_static_method()測(cè)試用例。

          測(cè)試無(wú)返回值的方法

          如何對(duì)void類型的方法進(jìn)行測(cè)試一直是許多單元測(cè)試框架在悄悄回避的話題,由于以往的單元測(cè)試手段主要是對(duì)被測(cè)單元的返回結(jié)果進(jìn)行校驗(yàn),當(dāng)遇到方法沒(méi)有返回值時(shí)就會(huì)變得無(wú)從下手。

          從功能的角度來(lái)說(shuō),雖然void方法不返回任何值,但它的執(zhí)行一定會(huì)對(duì)外界產(chǎn)生某些潛在影響,我們將其稱為方法的"副作用",比如:

          1. 初始化某些外部變量(私有成員變量或者全局靜態(tài)變量)

          2. 在方法體內(nèi)對(duì)外部對(duì)象實(shí)例進(jìn)行賦值

          3. 輸出了日志

          4. 調(diào)用了其他外部方法

          5. … …

          不返回任何值也不產(chǎn)生任何"副作用"的方法沒(méi)有存在的意義。

          這些"副作用"的本質(zhì)歸納來(lái)說(shuō)可分為兩類:修改外部變量調(diào)用外部方法。

          通過(guò)TestableMock的私有字段訪問(wèn)和Mock校驗(yàn)器可以很方便的實(shí)現(xiàn)對(duì)"副作用"的結(jié)果檢查。

          1. 修改外部變量的void方法

          例如,下面這個(gè)方法會(huì)根據(jù)輸入修改私有成員變量hashCache

          class?Demo?{
          ????private?Map<String,?Integer>?hashCache?=?mapOf();

          ????public?void?updateCache(String?domain,?String?key)?{
          ????????String?cacheKey?=?domain?+?"::"?+?key;
          ????????Integer?num?=?hashCache.get(cacheKey);
          ????????hashCache.put(cacheKey,?count?==?null???initHash(key)?:?nextHash(num,?key));
          ????}

          ????...?//?其他方法省略
          }

          若要測(cè)試此方法,可以利用TestableMock直接讀取私有成員變量的值,對(duì)結(jié)果進(jìn)行校驗(yàn):

          @EnablePrivateAccess??//?啟用TestableMock的私有成員訪問(wèn)功能
          class?DemoTest?{
          ????private?Demo?demo?=?new?Demo();

          ????@Test
          ????public?void?testSaveToCache()?
          {
          ????????Integer?firstVal?=?demo.initHash("hello");?//?訪問(wèn)私有方法
          ????????Integer?nextVal?=?demo.nextHash(firstVal,?"hello");?//?訪問(wèn)私有方法
          ????????demo.saveToCache("demo",?"hello");
          ????????assertEquals(firstVal,?demo.hashCache.get("demo::hello"));?//?讀取私有變量
          ????????demo.saveToCache("demo",?"hello");
          ????????assertEquals(nextVal,?demo.hashCache.get("demo::hello"));?//?讀取私有變量
          ????}
          }

          2. 調(diào)用外部方法的void方法

          例如,下面這個(gè)方法會(huì)根據(jù)輸入打印信息到控制臺(tái):

          class?Demo?{
          ????public?void?recordAction(Action?action)?{
          ????????SimpleDateFormat?df?=?new?SimpleDateFormat("yyyy-MM-dd?hh:mm:ss?");
          ????????String?timeStamp?=?df.format(new?Date());
          ????????System.out.println(timeStamp?+?"["?+?action.getType()?+?"]?"?+?action.getTarget());
          ????}
          }

          若要測(cè)試此方法,可以利用TestableMock快速M(fèi)ock掉System.out.println方法。在Mock方法體里可以繼續(xù)執(zhí)行原調(diào)用(相當(dāng)于并不影響本來(lái)方法功能,僅用于做調(diào)用記錄),也可以直接留空(相當(dāng)于去除了原方法的副作用)。

          在執(zhí)行完被測(cè)的void類型方法以后,用InvokeVerifier.verify()校驗(yàn)傳入的打印內(nèi)容是否符合預(yù)期:

          class?DemoTest?{
          ????private?Demo?demo?=?new?Demo();

          ????//?攔截`System.out.println`調(diào)用
          ????@MockMethod
          ????public?void?println(PrintStream?ps,?String?msg)?{
          ????????//?執(zhí)行原調(diào)用
          ????????ps.println(msg);
          ????}

          ????@Test
          ????public?void?testRecordAction()?{
          ????????Action?action?=?new?Action("click",?":download");
          ????????demo.recordAction();
          ????????//?驗(yàn)證Mock方法`println`被調(diào)用,且傳入?yún)?shù)符合預(yù)期
          ????????verify("println").with(matches("\\d{4}-\\d{2}-\\d{2}?\\d{2}:\\d{2}:\\d{2}?\\[click\\]?:download"));
          ????}
          }

          項(xiàng)目地址

          開(kāi)源地址:https://gitee.com/mirrors/TestableMock

          推薦閱讀:



          最近面試BAT,整理一份面試資料Java面試BAT通關(guān)手冊(cè),覆蓋了Java核心技術(shù)、JVM、Java并發(fā)、SSM、微服務(wù)、數(shù)據(jù)庫(kù)、數(shù)據(jù)結(jié)構(gòu)等等。

          獲取方式:點(diǎn)“在看”,關(guān)注公眾號(hào)并回復(fù)?666?領(lǐng)取,更多內(nèi)容陸續(xù)奉上。

          明天見(jiàn)(??ω??)??
          瀏覽 26
          點(diǎn)贊
          評(píng)論
          收藏
          分享

          手機(jī)掃一掃分享

          分享
          舉報(bào)
          評(píng)論
          圖片
          表情
          推薦
          點(diǎn)贊
          評(píng)論
          收藏
          分享

          手機(jī)掃一掃分享

          分享
          舉報(bào)
          <kbd id="afajh"><form id="afajh"></form></kbd>
          <strong id="afajh"><dl id="afajh"></dl></strong>
            <del id="afajh"><form id="afajh"></form></del>
                1. <th id="afajh"><progress id="afajh"></progress></th>
                  <b id="afajh"><abbr id="afajh"></abbr></b>
                  <th id="afajh"><progress id="afajh"></progress></th>
                  大香蕉av伊人 | 亚洲私人影院日韩 | 国产精品久久久久久亚洲色 | 蜜桃视频色五月婷婷 | 性爱视频网站 |