JMockit is a Java toolkit for automated developer testing.It contains APIs for the creation of the objects to be tested, for mocking dependencies, and for faking externalAPIs; JUnit (4 and 5) and TestNG test runners are supported.It also contains an advanced code coverage tool.
Jmockit是一個Java工具,用於開發者做自動測試。Jmockit包含了被創建對象的創建API,主要是為了模擬外部依賴和外部API,支持Junit4、5和TestNG,並且Jmockit還包含了優秀的代碼覆蓋工具。
JMockit簡介
JMockit是一個用於在測試階段模擬Java對象的Java框架,支持Junit4和TestNG。JMockit使用Java的instrumentation API在運行時修改類class文件的字節碼來動態的改變類的行為。JMockit比較強的地方在於它的可表達性和開箱即用的能力,它支持模擬靜態方法和私有方法(不推薦mock私有方法)。
Jmockit具有超強的表達能力。我們只需要創建mocks並定義其行為,我們不需要進行各種鏈式調用,只要直接定義它們就好。也就是說你不會有如下這樣的操作。
而是直接定義mock的行為即可。
可能看起來代碼變多了,但是你完全可以將它們寫在一行。重點在於,我們不需要在寫冗長的鏈式調用代碼了。你只需要定義你想mock對象方法調用時有什么行為即可。另外result=value部分,你可以完全返回任何你想要的值(固定值、動態生成的值、異常等等),可以看到JMockit有極強的表達力。
JMockit的Record-Replay-Verify模型
使用JMockit進行測試分為三個不同的階段:錄制、重放和核實。
1. 錄制階段,我們為下一步所需要用到的測試都定義默認的行為。
2. 重放階段,也就是真正執行測試代碼的階段,之前定義的模擬方法,構造器等等都會在這個階段播放。
3. 最后,在核實階段,我們將對測試的結果進行斷言,來確認和我們的預期是一致的。
我們通過一個簡單的例子來了解下JMockit的精髓吧。
在上面的例子中,通過參數Mock了一個對象,定義了兩個Expectations, 一個Verification。整個流程讀下來就是,我希望我Mock的對象的sum(1,2)返回3,sum(2,3)返回5,然后開始使用Mock的對象來執行代碼;接下來確認Mock對象的sum(int,int)方法被調用了2次;最后使用兩個斷言來看程序執行結果是否符合自己的預期。
Mock對象的創建
當我們使用JMockit時,最簡單的創建mocks的方法就是使用注解了。這里有3個注解來創建mocks, @Mocked、@Injectable、@Capturing。
當我們使用@Mocked注解來標注一個成員變量,它將會為每個該類的實例對象創建一個mocked實例,即使你new出來的實例也會受到影響。但是,使用@Injectable注解來標注一個成員變量,只會對標注的成員變量實例進行模擬,其他實例不受影響,你new出來的對象仍然保持原有行為。
我們通過一個簡單的例子來理解下。
但是如果我們將@Mocked換成@Injectable, 我們得到的結果將會是下面這樣。
@Capturing注解行為和@Mocked一樣,但是會為被標注變量所屬類的子類或實現類進行模擬,也就是上述TestCalculator的子類的sum(1,2)也會返回4。
@Tested注解常常和@Injectable注解一起使用,@Tested用來表示要測試的對象,@Injectable一般用來模擬主業務中其他的依賴的對象。
比如現在有一個業務,用戶上傳頭像的業務,涉及FileService和UserService兩個類的操作,但是我們現在無法搭建文件服務,所以我們決定模擬文件服務,我們通過代碼來看一下。
上述代碼中的主要測試點是UserService實例的saveLogoKey()方法,因此我們用@Tested來標注UserService,但是我們又缺少FileService的環境,所以我們用@Injectable來標注FileService,並使用Expectations來模擬其行為,使其返回一個fake file unique key, 那么userService就可以走通了。
Expectations & Verifications
接下來的方法將用到Expectations和Verifications。
any通配參數
Jmockit提供了一系列的通用字段:anyInt、anyString等等。以此來讓參數的匹配更加通用,這些通用字段都是以any開頭的。比如我們想給一個mock對象的f(String s)方法接受任何參數都返回true,我們就可以在Expectations中使用通用字段。
注:在使用Expectations時,里面定義的方法期望必須在Expectations下面的代碼段有調用,要不然會報錯Missing Invocation。
注:當使用Any工具字段的時候,我們必須要將參數強轉到方法所接受的類型,如上面代碼所示。
with通配方法
JMockit還提供了很多通用方法去處理通用參數匹配問題,這些方法都以With開頭(withSubString(subString)、withNotEqual(1)),這些方法比any開頭的字段更加高級,我們可以看下面這個例。
上面的例子中使用了withEqual(1)和withNotEqual(2),表示calculator在sum()的第一個參數為1時、第二個參數不為2時,返回結果是4。
null is NOT null
null是一個定義任意引用對象的語法糖,如果你想驗證當前方法接受的參數是null的引用,我們可以使用withNull()。
下面的例子,我們將定義mock對象的行為,這個行為將會被觸發,如果傳入的參數是String、List和null的引用。
可以注意到區別:null意味着任何list,withNull()意味着null的list的引用。特別是,為了避免將值強轉到聲明的參數類型時。
這個的唯一使用場景就是,至少有一個詳細的參數匹配器被用到Expections中(要么是Any要么是With)。
times限制模擬對象調用次數
有時候,我們需要限制mock方法的調用次數。JMockit使用times、minTimes和maxTimes來達成目的。我們通過一個小例子來體驗一下。
上面的例子在Expectations使用了times=2來期望方法會被調用2次,如果不夠2次或超過2次都將產生錯誤。
自定義通用類型
JMockit還允許自定義通用類型,使用withArgThat和BaseMatcher來實現。
Results和Returns
JMockit使用Result和Returns來模擬mock對象的返回值,Result和Returns可以覆蓋90%的返回值類型,我們通過一個例子來了解下。
注: Returning只能用在Expectations中。
JMockit還支持第三種方式來返回值。
上述例子使用代理來返回值,如果傳遞的參數<3, 則返回5, 否則拋出異常。
JMockit高級方案
JMockit除了提供了上述的Expectations和Verifications等特性之外,還提供了其他的高級特性。
• Faking(MockUp API)
• Deencapsulation功能類
• 如何使用一個mock來模擬多個接口
• 如何重用Expectations和Verifications
在原先JMockit版本中,支持對私有變量對的mock, 但是在我測試的1.43版本中,不再支持對private字段和方法進行模擬,因為對於私有方法和內部類的測試往往是不被推薦的。
MockUpAPI
除了可以使用Expectations和Verifications的特性,我們還可以使用MockUp類來進行字段、方法的模擬(靜態方法和靜態變量不推薦使用MockUp),並且MockUp貌似只能模擬類,不能模擬接口。
可以看到,上述方法沒有對max方法進行模擬,則會導致調用方法時出現actual=0的情況。
Deencapsulation
接下來,我們使用Deencapsulation來模擬公有字段。
如何在一個測試類中模擬多個接口
假設我們現在想測試一個類,但是還沒有實現,但是我們很清楚的知道它將會實現多個接口。我們可以使用泛型和定義一個類來實現那多個接口。
我們將通過下面的例子來看看如何在一個測試類下模擬多個接口。
總結
JMockit為我們單元測試引入了模擬對象方案,有了JMockit我們基本上可以對任何類、接口、方法、字段進行模擬,來隔離外部依賴,從而寫出清晰易讀的測試代碼。