测试私有方法_史上最轻量!阿里开源了新型单元测试Mock工具
點擊上方藍色字體,選擇“設為星標”
回復”666“獲取面試寶典TestableMock是基于源碼和字節碼增強的Java單元測試輔助工具,包含以下功能:
- 訪問被測類私有成員:使單元測試能直接調用和訪問被測類的私有成員,解決私有成員初始化和私有方法測試的問題
- 快速Mock任意調用:使被測類的任意方法調用快速替換為Mock方法,實現"指哪換哪",解決傳統Mock工具使用繁瑣的問題
- 輔助測試void方法:利用Mock校驗器對方法的內部邏輯進行檢查,解決無返回值方法難以實施單元測試的問題
訪問私有成員字段和方法
如今關于私有方法是否應該做單元測試的爭論正逐漸消停,開發者的普遍實踐已經給出事實答案。通過公有方法間接測私有方法在很多情況下難以進行,開發者們更愿意通過修改方法可見性的辦法來讓原本私有的方法在測試用例中變得可測。
此外,在單元測試中時常會需要對被測對象進行特定的成員字段初始化,但有時由于被測類的構造方法限制,使得無法便捷的對這些字段進行賦值。那么,能否在不破壞被測類型封裝的情況下,允許單元測試用例內的代碼直接訪問被測類的私有方法和成員字段呢?TestableMock提供了兩種簡單的解決方案。
方法一:使用@EnablePrivateAccess注解
只需為測試類添加@EnablePrivateAccess注解,即可在測試用例中獲得以下增強能力:
- 調用被測類的私有方法(包括靜態方法)
- 讀取被測類的私有字段(包括靜態字段)
- 修改被測類的私有字段(包括靜態字段)
- 修改被測類的常量字段(使用final修飾的字段,包括靜態字段)
訪問和修改私有、常量成員時,IDE可能會提示語法有誤,但編譯器將能夠正常運行測試。(使用編譯期代碼增強,目前僅實現了Java語言的適配)
效果見java-demo示例項目DemoPrivateAccessTest測試類中的用例。
方法二:使用PrivateAccessor工具類
若不希望看到IDE的語法錯誤提醒,或是在非Java語言的JVM工程(譬如Kotlin語言)里,也可以借助PrivateAccessor工具類來直接訪問私有成員。
這個類提供了6個靜態方法:
- PrivateAccessor.get(被測對象, "私有字段名")?? 讀取被測類的私有字段
- PrivateAccessor.set(被測對象, "私有字段名", 新的值)?? 修改被測類的私有字段(或常量字段)
- PrivateAccessor.invoke(被測對象, "私有方法名", 調用參數..)?? 調用被測類的私有方法
- PrivateAccessor.getStatic(被測類型, "私有靜態字段名")?? 讀取被測類的靜態?私有字段
- PrivateAccessor.setStatic(被測類型, "私有靜態字段名", 新的值)? 修改被測類的靜態?私有字段(或靜態?常量字段)
- PrivateAccessor.invokeStatic(被測類型, "私有靜態方法名", 調用參數..)?? 調用被測類的靜態?私有方法
快速Mock被測類的任意方法調用
相比以往Mock工具以類為粒度的Mock方式,TestableMock允許用戶直接定義需要Mock的單個方法,并遵循約定優于配置的原則,按照規則自動在測試運行時替換被測方法中的指定方法調用。
歸納起來就兩條:
- Mock非構造方法,拷貝原方法定義到測試類,增加一個與調用者類型相同的參數,加@MockMethod注解
- Mock構造方法,拷貝原方法定義到測試類,返回值換成構造的類型,方法名隨意,加@MockContructor注解
具體的Mock方法定義約定如下:
1. 覆寫任意類的方法調用
在測試類里定義一個有@MockMethod注解的普通方法,使它與需覆寫的方法名稱、參數、返回值類型完全一致,然后在其參數列表首位再增加一個類型為該方法原本所屬對象類型的參數。
此時被測類中所有對該需覆寫方法的調用,將在單元測試運行時,將自動被替換為對上述自定義Mock方法的調用。
注意?:當遇到待覆寫方法有重名時,可以將需覆寫的方法名寫到@MockMethod注解的targetMethod參數里,這樣Mock方法自身就可以隨意命名了。
例如,被測類中有一處"anything".substring(1, 2)調用,我們希望在運行測試的時候將它換成一個固定字符串,則只需在測試類定義如下方法:
//?原方法簽名為`String?substring(int,?int)`//?調用此方法的對象`"anything"`類型為`String`
//?則Mock方法簽名在其參數列表首位增加一個類型為`String`的參數(名字隨意)
//?此參數可用于獲得當時的實際調用者的值和上下文
@MockMethod
private?String?substring(String?self,?int?i,?int?j)?{
????return?"sub_string";
}
下面這個例子展示了targetMethod參數的用法,其效果與上述示例相同:
//?使用`targetMethod`指定需Mock的方法名//?此方法本身現在可以隨意命名,但方法參數依然需要遵循相同的匹配規則
@MockMethod(targetMethod?=?"substring")
private?String?use_any_mock_method_name(String?self,?int?i,?int?j)?{
????return?"sub_string";
}
完整代碼示例見java-demo和kotlin-demo示例項目中的should_able_to_mock_common_method()測試用例。(由于Kotlin對String類型進行了魔改,故Kotlin示例中將被測方法在BlackBox類里加了一層封裝)
2. 覆寫被測類自身的成員方法
有時候,在對某些方法進行測試時,希望將被測類自身的另外一些成員方法Mock掉。
操作方法與前一種情況相同,Mock方法的第一個參數類型需與被測類相同,即可實現對被測類自身(不論是公有或私有)成員方法的覆寫。
例如,被測類中有一個簽名為String innerFunc(String)的私有方法,我們希望在測試的時候將它替換掉,則只需在測試類定義如下方法:
//?被測類型是`DemoMock`//?因此在定義Mock方法時,在目標方法參數首位加一個類型為`DemoMock`的參數(名字隨意)
@MockMethod
private?String?innerFunc(DemoMock?self,?String?text)?{
????return?"mock_"?+?text;
}
3. 覆寫任意類的靜態方法
對于靜態方法的Mock與普通方法相同。但需要注意的是,靜態方法的Mock方法被調用時,傳入的第一個參數實際值始終是null。
例如,在被測類中調用了BlackBox類型中的靜態方法secretBox(),改方法簽名為BlackBox secretBox(),則Mock方法如下:
//?目標靜態方法定義在`BlackBox`類型中//?在定義Mock方法時,在目標方法參數首位加一個類型為`BlackBox`的參數(名字隨意)
//?此參數僅用于標識目標類型,實際傳入值將始終為`null`
@MockMethod
private?BlackBox?secretBox(BlackBox?ignore)?{
????return?new?BlackBox("not_secret_box");
}
完整代碼示例見java-demo和kotlin-demo示例項目中的should_able_to_mock_static_method()測試用例。
測試無返回值的方法
如何對void類型的方法進行測試一直是許多單元測試框架在悄悄回避的話題,由于以往的單元測試手段主要是對被測單元的返回結果進行校驗,當遇到方法沒有返回值時就會變得無從下手。
從功能的角度來說,雖然void方法不返回任何值,但它的執行一定會對外界產生某些潛在影響,我們將其稱為方法的"副作用",比如:
不返回任何值也不產生任何"副作用"的方法沒有存在的意義。
這些"副作用"的本質歸納來說可分為兩類:修改外部變量?和調用外部方法?。
通過TestableMock的私有字段訪問和Mock校驗器可以很方便的實現對"副作用"的結果檢查。
1. 修改外部變量的void方法
例如,下面這個方法會根據輸入修改私有成員變量hashCache:
class?Demo?{????private?Map?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直接讀取私有成員變量的值,對結果進行校驗:
@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. 調用外部方法的void方法
例如,下面這個方法會根據輸入打印信息到控制臺:
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方法體里可以繼續執行原調用(相當于并不影響本來方法功能,僅用于做調用記錄),也可以直接留空(相當于去除了原方法的副作用)。
在執行完被測的void類型方法以后,用InvokeVerifier.verify()校驗傳入的打印內容是否符合預期:
class?DemoTest?{????private?Demo?demo?=?new?Demo();
????//?攔截`System.out.println`調用
????@MockMethod
????public?void?println(PrintStream?ps,?String?msg)?{
????????//?執行原調用
????????ps.println(msg);
????}
????@Test
????public?void?testRecordAction()?{
????????Action?action?=?new?Action("click",?":download");
????????demo.recordAction();
????????//?驗證Mock方法`println`被調用,且傳入參數符合預期
????????verify("println").with(matches("\\d{4}-\\d{2}-\\d{2}?\\d{2}:\\d{2}:\\d{2}?\\[click\\]?:download"));
????}
}
項目地址
開源地址:https://gitee.com/mirrors/TestableMock
來源 |?gitee.com/mirrors/TestableMock面試官問:MySQL的自增ID用完了,怎么辦?
ArrayList插入1000w條數據之后,我懷疑了jvm...
螞蟻二面,面試官問我零拷貝的實現原理,當場懵了…
獲取方式:點“在看”,關注公眾號并回復?666?領取,更多內容陸續奉上。
明天見(。・ω・。)ノ?總結
以上是生活随笔為你收集整理的测试私有方法_史上最轻量!阿里开源了新型单元测试Mock工具的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: Java-Scanner进阶使用
- 下一篇: 计算机专业直接工作简历,2017计算机专