<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>

          阿里開源新一代單元測試 Mock 工具!

          共 5828字,需瀏覽 12分鐘

           ·

          2021-01-05 02:08

          點擊上方藍色“小哈學(xué)Java”,選擇“設(shè)為星標

          回復(fù)“資源”獲取獨家整理的學(xué)習(xí)資料!


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

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

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

          訪問私有成員字段和方法

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

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

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

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

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

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

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

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

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

          效果見java-demo示例項目DemoPrivateAccessTest測試類中的用例。

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

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

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

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

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

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

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

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

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

          快速Mock被測類的任意方法調(diào)用

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

          歸納起來就兩條:

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

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

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

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

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

          此時被測類中所有對該需覆寫方法的調(diào)用,將在單元測試運行時,將自動被替換為對上述自定義Mock方法的調(diào)用。

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

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

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

          下面這個例子展示了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";
          }

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

          2. 覆寫被測類自身的成員方法

          有時候,在對某些方法進行測試時,希望將被測類自身的另外一些成員方法Mock掉。

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

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

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

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

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

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

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

          完整代碼示例見java-demokotlin-demo示例項目中的should_able_to_mock_static_method()測試用例。

          測試無返回值的方法

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

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

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

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

          3. 輸出了日志

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

          5. … …

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

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

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

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

          例如,下面這個方法會根據(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));
          ????}

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

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

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

          ????@Test
          ????public?void?testSaveToCache()?
          {
          ????????Integer?firstVal?=?demo.initHash("hello");?//?訪問私有方法
          ????????Integer?nextVal?=?demo.nextHash(firstVal,?"hello");?//?訪問私有方法
          ????????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方法

          例如,下面這個方法會根據(jù)輸入打印信息到控制臺:

          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());
          ????}
          }

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

          在執(zhí)行完被測的void類型方法以后,用InvokeVerifier.verify()校驗傳入的打印內(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();
          ????????//?驗證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"));
          ????}
          }

          項目地址

          開源地址:https://gitee.com/mirrors/TestableMock


          END


          有熱門推薦?

          1.?加強版 Redis,又一款國產(chǎn)高性能 KV 存儲數(shù)據(jù)庫開源了!

          2.?高贊回答:為什么高級程序員不必擔(dān)心自己的技術(shù)過時?

          3.?介紹一款基于 SpringBoot 開發(fā) OA 開源產(chǎn)品 !

          4.?微服務(wù)架構(gòu)最強講解,那叫一個通俗易懂!

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

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

          文章有幫助的話,在看,轉(zhuǎn)發(fā)吧。

          謝謝支持喲 (*^__^*)

          瀏覽 19
          點贊
          評論
          收藏
          分享

          手機掃一掃分享

          分享
          舉報
          評論
          圖片
          表情
          推薦
          點贊
          評論
          收藏
          分享

          手機掃一掃分享

          分享
          舉報
          <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>
                  亚洲一级片免费看 | 亚洲精品视频在线观看免费 | 国内精品久久久久久久星 | 国产免费一区二区三区在线 | 无码欧美成人AAAA三区在线 |