jmockit學習


下圖為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 }}
View Code

 在我們了解記錄,重放和驗證調用之間的差異后,我們將更多地了解期望。

record-replay-verify模型

任何開發人員測試都可以分為至少三個單獨的執行階段。 這些階段按順序執行,一次一個,如下所示。

 1 @Test
 2     public void someTestMethod(){
 3     //1.准備:在被測試代碼可以執行之前的任何需要。
 4     ... ...    
 5     // 2.通過調用public方法來執行測試下的代碼。    
 6     ... ...    
 7     // 3.驗證:無論需要檢查以確保代碼行使    
 8     //     測試完成了它的工作。    
 9     ... ...
10 }}
View Code
首先,我們有一個准備階段,其中測試所需的對象和數據項從其他地方創建或獲取。然后,執行測試中的代碼。最后,將運行測試代碼的結果與預期結果進行比較。
這個三階段模型也稱為Arrange,Act,Assert語法,或簡稱為“AAA”。不同的話,但意思是一樣的。
在使用模擬類型(及其模擬實例)的基於行為的測試的上下文中,我們可以識別以下替代階段,這與前面描述的三個常規測試階段直接相關:
記錄階段,在此階段可以記錄調用。這發生在測試准備期間,在我們要測試的調用被執行之前。
在重放階段期間,當執行測試代碼時,感興趣的模擬調用有機會被執行。之前記錄的對模擬方法/構造函數的調用現在將被重放。通常在記錄和重放的調用之間沒有一對一的映射。
驗證階段,在此期間,可以驗證調用已按預期發生。這發生在測試驗證期間,在測試下的調用有機會被執行之后。
使用JMockit編寫的基於行為的測試通常適合以下模板:
 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 }}
View Code

上述模板還有其他變化,但本質是,期望塊屬於記錄階段,並且在被測試代碼執行之前,而驗證塊屬於驗證階段。 測試方法可以包含任何數量的期望塊,也可以沒有。 驗證塊也是如此。事實上,匿名內部類用於區分代碼塊,這使我們可以利用現代Java IDE中提供的“代碼折疊”功能。 下圖顯示了IntelliJ IDEA中的內容。

 

定期與嚴格的期望

“new Expectations(){...}”塊中記錄的期望是常規期望。這意味着期在重放它們指定的調用預階段期間至少發生一次;它們可能出現不止一次,但是相對於其他記錄的期望以不同的順序出現;另外,允許不以任何記錄的期望匹配的調用以任何數量和以任何順序發生。如果沒有調用匹配給定的記錄期望,則在測試結束時拋出“丟失的調用”錯誤,導致它失敗(這只是默認行為,因為它可以被覆蓋)。
 API還支持嚴格期望的概念:在記錄時,僅允許在重放期間完全匹配記錄(在明確指定的允許(在需要時)中)的調用,在匹配調用的數量(默認為一個)和它們發生的順序。在重放期間發生但無法匹配記錄的嚴格期望的調用被視為意外的,導致立即的“意外調用”錯誤,因此測試失敗。這是通過使用StrictExpectations子類實現的。
 注意,在嚴格期望的情況下,在重放期間發生的匹配記錄的期望的所有調用被隱式地驗證。任何與預期不匹配的剩余調用都被視為意外,導致測試失敗。如果錯過任何記錄的嚴格期望,即如果在重放期間沒有發生匹配的調用,則測試也將失敗。
 我們可以通過編寫多個期望塊,一些規則(使用期望),其他嚴格(使用StrictExpectations)在同一測試中混合不同嚴格程度的期望。通常,給定的模擬字段或模擬參數將出現在單一類型的期望塊中。
 大多數測試將簡單地利用“常規”期望。使用嚴格的期望可能更多是個人喜好的問題。

嚴格和非嚴格的模擬

注意,我們不指定給定的模擬類型/實例應該是嚴格的或不。相反,給定模擬場/參數的嚴格性由如何在測試中使用來確定。一旦在“新的StrictExpectations(){...}”塊中記錄了第一個嚴格的期望,相關的模擬類型/實例被認為對於整個測試是嚴格的;否則,就不會嚴格。

記錄期望的結果

對於具有非void返回類型的給定方法,可以通過對結果字段賦值來記錄返回值。當在重放階段調用該方法時,指定的返回值將返回給調用者。對結果的賦值應該出現在調用之后,該調用標識期望塊內的記錄期望。
 
如果測試改為需要在調用方法時拋出異常或錯誤,那么仍然可以使用結果字段: 只需為其分配所需的可拋出的實例。注意,要拋出的異常/錯誤的記錄適用於模擬方法(任何返回類型)以及模擬構造函數。
 
通過在一行中多次分配結果字段,可以針對相同的期望記錄多個連續結果(要返回的值和/或可拋出的throwable)。對於相同的期望,可以自由地混合要拋出的多個返回值和/或異常/錯誤的記錄。在為給定期望記錄多個連續返回值的情況下,可以對返回(Object ...)方法進行單個調用。此外,如果為其分配的值是包含連續值的列表或數組,則對結果字段的單個分配將實現相同的效果。
 
以下示例測試記錄了模擬的DependencyAbc類的方法的兩種類型的結果,當從UnitUnderTest類調用時,將使用它們。讓我們說測試類的實現如下:
 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     }}
View Code
該測試記錄了三種不同的期望。第一個,由對DependencyAbc()構造函數的調用表示,僅僅說明這個依賴關系恰好在被測代碼中通過no-args構造函數實例化的事實;除非偶爾拋出的異常/錯誤(構造函數具有void返回類型,因此對它們記錄返回值沒有意義),因此不需要為這樣的調用指定結果。第二個期望指定當調用時intReturningMethod()將返回3。第三個字符串指定了stringReturningMethod()的三個連續結果的序列,其中最后一個結果恰好是所需異常的一個實例,允許測試實現其目標(注意,如果異常沒有傳播出去,它才會通過)。

匹配調用特定實例(@Injectable)

上面,我們解釋了在模擬實例上記錄的期望,例如“abc.someMethod();”實際上匹配調用DependencyAbc#someMethod()在模擬的DependencyAbc類的任何實例上。在大多數情況下,測試代碼使用給定依賴關系的單個實例,因此這不會真正重要,可以安全地忽略,無論模擬的實例是傳遞到測試中的代碼還是在其中創建。但是,如果我們需要驗證調用發生在特定實例上,如果在測試中的代碼中使用幾個調用之間呢?此外,如果只有一個或幾個模擬類的實例實際上應該被模擬,同一個類的其他實例保持未被捕獲怎么辦? (這種第二種情況往往發生在來自標准Java庫或其他第三方庫的類被模擬時)。API提供了一個模擬注釋@Injectable,它只會模擬mock類型的一個實例,使其他人不受影響。此外,我們有幾種方法來約束期望與特定@Mocked實例的匹配,同時仍然mock所有模擬類的實例。

可注入的模擬實例

 
假設我們需要測試與給定類的多個實例一起工作的代碼,其中一些我們想要模擬。如果要被模擬的實例可以傳遞或注入到測試中的代碼,那么我們可以為它聲明一個@Injectable模擬字段或模擬參數。這個@Injectable實例將是一個“獨占”模擬實例;除非從單獨的模擬字段/參數獲得,否則相同模擬類型的任何其他實例將保持為常規的非模擬實例。
 
當使用@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 返回頂部
View Code
注意,@Injectable的使用在這里確實是必要的,因為被測試的類擴展了模擬類,並且調用練習ConcatenatingInputStream的方法實際上是在基於InputStream類中定義的。如果InputStream被“正常”mock,則read( byte []) 方法總是被模擬,而不管它被調用的實例。

聲明多個mock實例

當在同一mock字段/參數上使用@Mocked或@Capturing(而不是@Injectable)時,我們仍然可以將重放調用與記錄在特定模擬實例上的期望進行匹配。為此,我們簡單地聲明多個模擬字段或同一模擬類型的參數,如下例所示。
 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 }}
View Code
上面的測試只有在測試代碼(為了簡潔嵌入在測試方法本身中)在對記錄調用進行完全相同的實例上調用getValue()時才會通過。當被測試代碼調用同一類型的兩個或多個不同實例,並且測試想要驗證特定調用是否發生在預期實例上時,這通常很有用。

使用給定構造函數創建的實例

特別是對於以后被測試的代碼創建的未來實例,JMockit提供了一些機制,通過它們我們可以匹配對它們的調用。這兩種機制都需要記錄對模擬類的特定構造函數調用(“新”表達式)的期望。
第一種機制涉及在記錄對實例方法的期望時簡單地使用從記錄的構造函數期望獲得的新實例。讓我們看一個例子。
 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 }}
View Code
在上面的測試中,我們使用@Mocked聲明所需類的單個模擬字段或模擬參數。然而,這個模擬場/參數在記錄期望時不使用;相反,我們使用在實例化記錄上創建的實例來記錄對實例方法的進一步期望。使用匹配構造函數調用創建的未來實例將映射到這些記錄的實例。另外,請注意,它不一定是一對一映射,而是一個多對一映射,從潛在的許多未來實例到用於記錄期望的單個實例。
第二種機制允許我們記錄與記錄的構造函數調用匹配的那些未來實例的替換實例。有了這個替代機制,我們可以重寫測試如下。
 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 }}
View Code
這兩個版本的測試是等效的。第二個也允許,當與部分模擬組合時,用於實際(非模擬)實例用作替換。

參數值的靈活匹配

在記錄和驗證階段,對模擬方法或構造函數的調用標識期望。如果方法/構造函數有一個或多個參數,那么記錄/驗證期望如doSomething(1,“s”,true);將只匹配重放階段中的調用(如果它具有相等的參數值)。對於作為常規對象(非基元或數組)的參數,equals(Object)方法用於相等性檢查。對於數組類型的參數,等式檢查擴展到單個元素;因此,在每個維度中具有相同長度的兩個不同的陣列實例和等同的對應元素被認為是相等的。
在給定的測試中,我們經常不知道這些參數值將是什么,或者它們對於被測試的東西不是必需的。因此,為了允許記錄或驗證的調用來匹配具有不同參數值的整個重放調用的集合,我們可以指定靈活的參數匹配約束而不是實際的參數值。這是通過使用anyXyz字段和/或withXyz(...)方法。 “任何”字段和“with”方法都在mockit.Invocations中定義,它是測試中使用的所有期望/驗證類的基類;因此,它們可以用於期望以及驗證塊。

使用“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     }}
View Code
“any”字段的使用必須出現在調用語句中的實際參數位置,從不在之前。你仍然可以在同一調用中的其他參數的常規參數值。

 使用“with”方法進行參數匹配

當記錄或驗證期望時,對調用中傳遞的參數的任何子集可以調用withXyz(...)方法。它們可以與常規參數傳遞(使用文字值,局部變量等)自由混合。唯一的要求是這樣的調用出現在記錄/驗證的調用語句內,而不是在它之前。例如,不可能首先將調用的結果分配給局部變量withNotEqual(val),然后在調用語句中使用該變量。使用一些“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     }}
View Code
還有更多的“與”方法比上面所示。有關更多詳細信息,請參閱API文檔。
除了在API中可用的幾個預定義參數匹配約束之外,JMockit允許用戶通過with(Delegate)和withArgThat(Matcher)方法提供自定義約束。

使用空值匹配任何對象引用

當對給定期望使用至少一個參數匹配方法或字段時,我們可以使用“快捷方式”來指定應接受任何對象引用(對於引用類型的參數)。簡單地傳遞null值,而不是一個withAny(x)或任何參數匹配器。特別是,這避免了將值轉換為聲明的參數類型的需要。但是,請記住,此行為僅適用於使用至少一個顯式參數匹配器(“with”方法或“任何”字段)。當在不使用匹配器的調用中傳遞時,null值將僅匹配null引用。在之前的測試中,我們可以寫成:
1     @Test
2     public void someTestMethod(@Mocked final DependencyAbc abc){   
3         ... ...   
4             new Expectations(){{      
5             abc.voidMethod(anyString,null);   
6         }};   
7         ... ...
8 }}
View Code
要具體驗證給定參數接收到空引用,可以使用withNull()匹配器。

通過varargs參數傳遞的匹配值

偶爾,我們可能需要處理“varargs”方法或構造函數的期望。它有效的傳遞常規值作為varargs參數,也有效使用“with”/“任何”匹配器的這樣的值。但是,當有一個varargs參數時,對於相同的期望結合兩種類型的值傳遞是無效的。我們需要僅使用常規值或僅使用通過參數匹配器獲取的值。
如果我們想要匹配調用,其中varargs參數接收任意數量的值(包括零),我們可以為最終varargs參數指定一個期望值為“(Object [])any”約束。

指定調用計數約束

到目前為止,我們看到除了相關聯的方法或構造函數之外,期望可以具有調用結果和參數匹配器。假設被測試的代碼可以用不同或相同的參數多次調用相同的方法或構造函數,我們有時需要一種方法來解釋所有這些單獨的調用。
 
可以通過調用計數約束來指定期望和/或允許匹配給定期望的調用的數量。mockAPI只提供三個特殊字段:times,minTimes和maxTimes。這些字段可以在記錄時或在驗證期望時使用。在任一情況下,與期望相關聯的方法或構造器將被約束以接收落入指定范圍內的多個調用。任何調用分別小於或大於預期的下限或上限,並且測試執行將自動失敗。讓我們看看一些示例測試。
 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     }}
View Code
與結果字段不同,對於給定的期望,這三個字段中的每一個最多可以指定一次。任何非負整數值對於任何調用計數約束都有效。如果指定times = 0或maxTimes = 0,則與重放期間發生的期望匹配的第一次調用(如果有)將導致測試失敗。

顯示驗證

除了對記錄的期望指定調用計數約束之外,我們還可以在調用被測代碼之后在驗證塊中顯式地驗證匹配調用。這對於常規期望是有效的,但對於嚴格的期望是無效的,因為它們總是被隱式地驗證;在顯式驗證塊中重新驗證它們沒有意義。
 
在“new Verifications(){...}”塊中,我們可以使用“new Expectations(){...}”塊中可用的相同API,但用於記錄返回值的方法和字段除外,拋出異常/錯誤。也就是說,我們可以自由地使用anyXyz字段,withXyz(...)參數匹配方法,以及時間,minTimes和maxTimes調用計數約束字段。下面是一個示例測試。
 
 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     }}
View Code
請注意,默認情況下,驗證檢查在重放期間至少發生了一個匹配調用。當我們需要驗證調用的確切數量(包括1)時,必須指定times = n約束。

 驗證調用從未發生

要在驗證塊中執行此操作,請在調用后添加一個“times = 0”分配,該分配在重放階段期間不會發生。如果發生一個或多個匹配調用,測試將失敗。

驗證按順序

使用驗證類創建的常規驗證塊是無序的。在重放階段期間調用aMethod()和anotherMethod()的實際相對順序未經驗證,但只有每個方法至少執行一次。如果要驗證調用的相對順序,則必須使用“new VerificationsInOrder() {...}" 塊。在這個塊中,只需按照它們預期發生的順序向一個或多個模擬類型寫入調用。
 
 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     }}
View Code

 

注意調用abc.doSomething(...)在測試中沒有驗證,所以它可能在任何時候發生(或根本不發生)。

部分有序驗證

假設您要驗證特定方法(或構造函數)在其他調用之前/之后被調用,但您不關心這些其他調用發生的順序。在有序的驗證塊中,這可以通過在適當的地方簡單地調用unverifiedInvocations()方法來實現。以下測試演示它。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM