下圖為jmockit 類圖。在我們編寫代碼時幾乎都會用到Expectations(期望)和Verifications(校驗),二者均繼承自Invacations.
常會用到的注解有:@Mocked @Tested @Injectable(@Tested和@Injectable經常配對使用),@Capturing(用於接口)
mock類型和實例
從依賴的測試代碼調用的方法和構造函數是mock(模擬)的目標。 Mocking提供了我們需要的機制,以便將被測試的代碼與(一些)依賴關系隔離開來。我們通過聲明適當的模擬字段和/或模擬參數來指定要為給定測試(或多個測試)模擬哪些特定依賴性; mock字段聲明為測試類的注釋實例字段,而mock參數聲明為測試方法的注釋參數。 要模擬的依賴關系的類型將是模擬字段或參數的類型。這種類型可以是任何類型的引用類型:接口,類(包括抽象和final類型),注釋或枚舉。
默認情況下,mock類型的所有非私有方法(包括靜態,final或native的任何方法)將在測試期間被模擬。如果聲明的mocked類型是一個類,那么所有的超類直到但不包括java.lang.Object也將被遞歸地mock。同時,繼承的方法也會自動被mock。在一個類的情況下,它的所有非私有構造函數將被mock。
當一個方法或構造器被mock時,它的原始實現代碼將不會被執行在測試期間發生的調用。相反,調用將被重定向到JMockit,所以它可以以顯式或隱式指定測試方式處理。
以下示例測試框架用作對mock字段和mock參數的聲明以及它們在測試代碼中通常使用的方式的基本說明。
對於在測試方法中聲明的mock參數,聲明類型的實例將由JMockit自動創建,並在JUnit / TestNG測試運行器執行測試方法時傳遞; 因此,參數值永遠不會為null。對於mock屬性,聲明類型的實例將由JMockit自動創建並分配給字段,前提是它不是final(final不允許繼承)。
有一些不同的注解可用於聲明模擬屬性和參數,以及默認模擬行為可以修改以適應特定測試的需要的方式。本章的其他部分詳細介紹,但基本是:@Mocked是中央模擬注解,有一個可選的屬性,在某些情況下是有用的; @Injectable是另一個模擬注釋,它限制mock單個模擬實例的實例方法;@Capturing是另一個模擬注釋,它擴展mock到實現模擬接口的類,或擴展mock類的子類。當@Injectable或@Capturing應用於模擬字段或模擬參數時,隱含了@Mocked,所以它不需要(但可以)應用。由JMockit創建的模擬實例可以在測試代碼(用於期望的記錄和驗證)中正常使用,和/或傳遞到測試中的代碼。或者他們可能只是閑置。與其他模擬API不同,這些模擬對象不一定是被測試代碼在其依賴項上調用實例方法時使用的對象。默認情況下(即,當不使用@Injectable時),JMockit不關心調用模擬實例方法的對象。這允許直接在測試下的代碼中創建的實例的透明模擬,當所述代碼使用新運算符調用全新實例上的構造函數時;實例化的類必須由測試代碼中聲明的模擬類型覆蓋,這就是全部。
Expectations
期望表示對與給定測試相關的特定模擬方法/構造函數的調用的集合。期望可以覆蓋對同一方法或構造器的多個不同調用,但是它不必覆蓋在測試的執行期間發生的所有這樣的調用。特定調用是否與給定期望匹配將不僅取決於方法/構造函數簽名,而且還取決於諸如調用該方法的實例,參數值和/或已經匹配的調用的數量的運行時方面。因此,可以(可選地)為給定期望指定幾種類型的匹配約束。
當我們具有一個或多個調用參數時,可以為每個參數指定確切的參數值。例如,可以為String參數指定值“test string”,從而導致期望僅在相應參數中將這些調用與此精確值進行匹配。正如我們將在后面看到的,我們可以指定更寬松的約束,而不是指定精確的參數值,它將匹配整組不同的參數值。
下面的示例顯示了Dependency#someMethod(int,String)的期望,它將使用指定的確切參數值匹配此方法的調用。注意,期望本身是通過對模擬方法的單獨調用來指定的。沒有涉及特殊的API方法,這在其他mocking API中是常見的。然而,這種調用不算作我們對測試感興趣的“真正的”調用之一。它只在那里,以便可以指定期望。

1 @Test 2 public void doBusinessOperationXyz(@Mocked final Dependency mockInstanc){ 3 ... ... 4 new Expectations(){{ 5 ... ... 6 //實例方法的期望: 7 //mockInstance.someMethod(1,“test”); 8 result =“mocked”; 9 ... ... 10 }}; 11 //這里調用被測代碼,導致模擬調用可能或可能不匹配指定的期望。 12 }}
在我們了解記錄,重放和驗證調用之間的差異后,我們將更多地了解期望。
record-replay-verify模型
任何開發人員測試都可以分為至少三個單獨的執行階段。 這些階段按順序執行,一次一個,如下所示。

1 @Test 2 public void someTestMethod(){ 3 //1.准備:在被測試代碼可以執行之前的任何需要。 4 ... ... 5 // 2.通過調用public方法來執行測試下的代碼。 6 ... ... 7 // 3.驗證:無論需要檢查以確保代碼行使 8 // 測試完成了它的工作。 9 ... ... 10 }}

1 import mockit。*; 2 ...other import... 3 public class SomeTest{ 4 //零或多個“模擬字段”對類中的所有測試方法是通用的: 5 @Mocked Collaborator mockCollaborator; 6 @Mocked AnotherDependency anotherDependency; 7 ... ... 8 // 第一種情況: 9 @Test 10 public void testWithRecordAndReplayOnly(mock parameters) { 11 //准備代碼不特定於JMockit,可以使任意的。 12 new Expectations(){{ 13 //“expectation block” 14 //對模擬類型的一個或多個調用,導致期望被記錄。 15 //在這個塊內的任何地方也允許調用非模擬類型 16 //(雖然不推薦)。 17 }}; 18 //執行被測單元。 19 //驗證代碼碼(JUnit/TestNG assertions),如果有的話。 20 }} 21 // 第二種情況: 22 @Test 23 public void testWithReplayAndVerifyOnly(mock parameters) { 24 准備代碼不特定於JMockit,可以使任意的。 25 執行被測單元。 26 new Verifications() {{ 27 //“驗證塊” 28 //對模擬類型的一個或多個調用,導致期望被驗證。 29 //在這個塊內的任何地方也允許調用非模擬類型 30 //(雖然不推薦)。 31 }}; 32 //附加驗證碼(如果有的話),位於驗證區塊之前或之前。 33 }} 34 // 第三種情況: 35 @Test 36 public void testWithBothRecordAndVerify(mock parameters) { 37 //准備代碼不特定於JMockit,如果有的話。 38 //new Expectations(){{ 39 ////對模擬類型的一個或多個調用,導致期望被記錄。 40 }}; 41 //執行被測單元。 42 new VerificationsInOrder(){{ 43 //有序的驗證塊 44 //對模擬類型的一個或多個調用,導致期望被驗證 45 //以指定的順序。 46 }}; 47 //附加驗證碼(如果有的話),位於驗證區塊之前或之前。 48 }} 49 }}
上述模板還有其他變化,但本質是,期望塊屬於記錄階段,並且在被測試代碼執行之前,而驗證塊屬於驗證階段。 測試方法可以包含任何數量的期望塊,也可以沒有。 驗證塊也是如此。事實上,匿名內部類用於區分代碼塊,這使我們可以利用現代Java IDE中提供的“代碼折疊”功能。 下圖顯示了IntelliJ IDEA中的內容。
定期與嚴格的期望
嚴格和非嚴格的模擬
注意,我們不指定給定的模擬類型/實例應該是嚴格的或不。相反,給定模擬場/參數的嚴格性由如何在測試中使用來確定。一旦在“新的StrictExpectations(){...}”塊中記錄了第一個嚴格的期望,相關的模擬類型/實例被認為對於整個測試是嚴格的;否則,就不會嚴格。
記錄期望的結果

1 public class UnitUnderTest{ 2 (1)private final DependencyAbc abc = new DependencyAbc(); 3 public void doSomething(){ 4 (2)int n = abc.intReturningMethod(); 5 for(int i = 0; i <n; i ++){ 6 String s; 7 catch{ 8 (3)s = abc.stringReturningMethod(); 9 }} 10 catch(SomeCheckedException e){ 11 //以某種方式處理異常 12 }} 13 //做一些其他的東西 14 }} 15 }} 16 }} 17 // doSomething()方法的一個可能的測試可以運行在任意數量的成功迭代之后拋出SomeCheckedException的情況。假設我們想要(為了什么原因)記錄這兩個類之間的交互的一整套期望,我們可以寫下面的測試。 (通常,指定對模擬方法的所有調用和特定的在給定測試中的模擬構造函數是不可取的或重要的,我們稍后將解決這個問題。 18 @Test 19 public void doSomethingHandlesSomeCheckedException(@Mocked final DependencyAbc abc)throws Exception{ 20 new Expectations(){{ 21 (1)new DependencyAbc(); 22 (2)abc.intReturningMethod(); 23 result = 3; 24 (3)abc.stringReturningMethod(); 25 returns(“str1”,“str2”); 26 result = new SomeCheckedException(); 27 }}; 28 new UnitUnderTest().doSomething(); 29 }}
匹配調用特定實例(@Injectable)
可注入的模擬實例

1 在線工具 2 3 4 5 6 7 8 9 保存(Save) 嵌入博客(Embed) 執行(Run) 10 11 12 1 13 14 public final class ConcatenatingInputStream extends InputStream{ 15 2 16 private final Queue <InputStream> sequentialInputs; 17 3 18 private InputStream currentInput; 19 4 20 21 public ConcatenatingInputStream(InputStream ... sequentialInputs){ 22 5 23 this.sequentialInputs = new LinkedList <InputStream>(Arrays.asList(sequentialInputs)); 24 6 25 currentInput = this.sequentialInputs.poll(); 26 7 27 }} 28 8 29 @Override 30 9 31 32 public int read()throws IOException{ 33 10 34 if(currentInput == null)return -1; 35 11 36 int nextByte = currentInput.read(); 37 12 38 39 if(nextByte> = 0){ 40 13 41 return nextByte; 42 14 43 }} 44 15 45 currentInput = sequentialInputs.poll(); 46 16 47 return read(); 48 17 49 }} 50 18 51 }} 52 19 53 // 這個類可以通過使用ByteArrayInputStream對象進行輸入而輕松地進行測試,但是我們希望確保InputStream#read()方法在構造函數中傳遞的每個輸入流上被正確調用。以下測試將實現這一點。 54 20 55 @Test 56 21 57 58 public void concatenateInputStreams(@Injectable final InputStream input1,@Injectable final InputStream input2)throws Exception{ 59 22 60 61 new Expectations(){{ 62 23 63 input1.read();returns(1,2,-1); 64 24 65 input2.read();returns(3,-1); }}; 66 25 67 InputStream concatenatedInput = new ConcatenatingInputStream(input1,input2); 68 26 69 byte [] buf = new byte [3]; 70 27 71 concatenatedInput.read(buf); 72 28 73 assertArrayEquals(new byte [] {1,2,3},buf); 74 29 75 }} 76 77 78 縮進 減少縮進 注釋 格式化 79 80 227310278 81 小子欠扁 82 83 返回頂部
聲明多個mock實例

1 @Test 2 public void matchOnMockInstance(@Mocked final Collaborator mock,@Mocked Collaborator otherInstance){ 3 new Expectations(){{ 4 mock.getValue(); result = 12; 5 }}; 6 //使用從測試傳遞的模擬實例練習測試中的代碼: 7 int result = mock.getValue(); 8 assertEquals(12,result); 9 //如果另一個實例在測試下的代碼中創建... 10 //Collaborator another = new Collaborator(); 11 // ...我們不會得到記錄的結果,但默認的一個: 12 assertEquals(0,another.getValue()); 13 }}
使用給定構造函數創建的實例

1 @Test 2 public void newCollaboratorsWithDifferentBehaviors(@Mocked Collaborator anyCollaborator){ 3 //記錄每組實例的不同行為: 4 new Expectations(){{ 5 //一組,使用“a value”創建的實例: 6 Collaborator col1 = new Collaborator(“a value”); 7 col1.doSomething(anyInt); result = 123; 8 //另一個集合,使用“另一個值”創建的實例: 9 Collaborator col2 = new Collaborator("another value"); 10 col2.doSomething(anyInt); result = new InvalidStateException(); 11 }}; 12 //測試代碼: 13 new Collaborator(“a value”).doSomething(5); 14 //將返回123 15 ... ... 16 new Collaborator(“another value”).doSomething(0); 17 //會拋出異常 18 ... ... 19 }}

1 @Test 2 public void newCollaboratorsWithDifferentBehaviors(@Mocked final Collaborator col1, @Mocked final Collaborator col2){ 3 new Expectations(){{ 4 將單獨的未來實例集映射到單獨的模擬參數: 5 new Collaborator("a value"); result = col1; 6 new Collaborator("another value"); result = col2; 7 //記錄每組實例的不同行為: 8 col1.doSomething(anyInt); result = 123; 9 col2.doSomething(anyInt); result = new InvalidStateException(); 10 }}; 11 //測試代碼: 12 new Collaborator("a value").doSomething(5); 13 //將返回123 14 ... ... 15 new Collaborator("another value").doSomething(0); 16 //會拋出異常 17 ... ... 18 }}
參數值的靈活匹配
使用“any”字段進行參數匹配

1 @Test 2 public void someTestMethod(@Mocked final DependencyAbc abc){ 3 final DataItem item = new DataItem(...); 4 new Expectations(){{ 5 將匹配第一個參數為“voidMethod(String,List)”調用 6 //任何字符串和第二個任何列表。 7 abc.voidMethod(anyString,(List <?>)any); 8 }}; 9 new UnitUnderTest().doSomething(item); 10 new Verifications() {{ 11 //匹配具有long或Long類型的任何值的指定方法的調用。 12 abc.anotherVoidMethod(anyLong); 13 }}; 14 }}
使用“with”方法進行參數匹配

1 @Test 2 public void someTestMethod(@Mocked final DependencyAbc abc){ 3 final DataItem item = new DataItem(...); 4 new Expectations(){{ 5 將匹配“voidMethod(String,List)”調用第一個參數 6 //等於“str”,第二個不為null。 7 abc.voidMethod(“str”,(List <?>)withNotNull()); 8 //將匹配調用到DependencyAbc#stringReturningMethod(DataItem,String) 9 //第一個參數指向“item”,第二個參數指向“xyz”。 10 abc.stringReturningMethod(withSameInstance(item),withSubstring(“xyz”)); 11 }}; 12 new UnitUnderTest().doSomething(item); 13 new Verifications() {{ 14 //使用任何長整數參數匹配指定方法的調用。 15 abc.anotherVoidMethod(withAny(1L)); 16 }}; 17 }}
使用空值匹配任何對象引用

1 @Test 2 public void someTestMethod(@Mocked final DependencyAbc abc){ 3 ... ... 4 new Expectations(){{ 5 abc.voidMethod(anyString,null); 6 }}; 7 ... ... 8 }}
通過varargs參數傳遞的匹配值
指定調用計數約束

1 @Test 2 public void someTestMethod(@Mocked final DependencyAbc abc){ 3 new Expectations(){{ 4 默認情況下,至少需要一次調用,即“minTimes = 1”: 5 new DependencyAbc(); 6 //至少需要兩次調用: 7 abc.voidMethod(); minTimes = 2; 8 //需要1到5次調用: 9 abc.stringReturningMethod(); 10 minTimes = 1; 11 maxTimes = 5; 12 }}; 13 new UnitUnderTest().doSomething(); 14 }} 15 @Test 16 public void someOtherTestMethod(@Mocked final DependencyAbc abc){ 17 new UnitUnderTest().doSomething(); 18 new Verifications() {{ 19 驗證發生了零個或一個調用,並指定了參數值: 20 abc.anotherVoidMethod(3); 21 maxTimes = 1; 22 //使用指定的參數驗證至少一次調用的發生: 23 DependencyAbc.someStaticMethod(“test”,false); 24 //“minTimes = 1” 25 }}; 26 }}
顯示驗證

1 @Test 2 public void verifyInvocationsExplicitlyAtEndOfTest(@Mocked final Dependency mock){ 3 //這里沒有記錄,雖然它可以。 4 // Inside tested code: 5 Dependency dependency = new Dependency(); 6 dependency.doSomething(123, true, "abc-xyz"); 7 //驗證Dependency#doSomething(int,boolean,String)被調用至少一次, 8 //具有遵守指定約束的參數: 9 new Verifications(){{ 10 mock.doSomething(anyInt,true,withPrefix(“abc”)); 11 }}; 12 }}
驗證調用從未發生
驗證按順序

1 @Test 2 public void validationExpectationsInOrder(@Mocked final DependencyAbc abc){ 3 //里面的測試代碼: 4 abc.aMethod(); 5 abc.doSomething(“blah”,123); 6 abc.anotherMethod(5); 7 ... ... 8 new VerificationsInOrder(){{ 9 //這些調用的順序必須與順序相同 10 //重放匹配調用期間的發生。 11 abc.aMethod(); 12 abc.anotherMethod(anyInt); 13 }}; 14 }}