使用JUnit4與JMockit進行打樁測試

1. 何爲Mockphp

項目中各個模塊,各個類之間會有互相依賴的關係,在單元測試中,咱們只關心被測試的單元,對於其依賴的單元並不關心(會有另外針對該單元的測試)。html

好比,邏輯層A類依賴了數據訪問層B類的取數方法,而後進行邏輯處理。在對A的單元測試中,咱們關注的是在B返回不一樣的查詢結果的時候,A是怎麼處理的,而不是B究竟是怎麼取的數,如何封裝成一個模型等等。java

所以,要屏蔽掉這些外部依賴,而Mock讓咱們有了一套仿真的環境。數據庫

目前業界有幾種Mock,這裏選用最全面的JMockit進行總結。app

2. JMockit簡介ide

JMockit的工做原理是經過asm修改原有class的字節碼,再利用jdk的instrument機制替換現有class的內容,從而達到mock的目的。單元測試

這裏使用的JMockit是1.21版本,具體使用方法可能與其餘版本的不同,但思想是相通的。Maven 配置以下:測試

<dependency>
  <groupId>org.jmockit</groupId>
  <artifactId>jmockit</artifactId>
  <version>1.21</version>
  <scope>test</scope>
</dependency>ui

JMockit有兩種測試方式,一種是基於行爲的,一種是基於狀態的測試。this

1) Behavior-oriented(Expectations & Verifications)  

2)State-oriented(MockUp<GenericType>)   

通俗點講,Behavior-oriented是基於行爲的mock,對mock目標代碼的行爲進行模仿,更像黑盒測試。State-oriented 是基於狀態的mock,是站在目標測試代碼內部的。能夠對傳入的參數進行檢查、匹配,才返回某些結果,相似白盒。而State-oriented的 new MockUp基本上能夠mock任何代碼或邏輯。

假設如今有兩個類,Service和DAO.  Service經過數據庫查詢出不一樣分組貨物的數量,獲得貨物是否暢銷。

 1 package com.khlin.test.junit.jmockit.demo;
 2 
 3 public class Service {
 4     
 5     private DAO dao;
 6     
 7     public void setDao(DAO dao) {
 8         this.dao = dao;
 9     }
10     
11     /**
12      * 根據存貨量判斷貨物是否暢銷
13      * @param group
14      * @return
15      */
16     public Status checkStatus(String group) {
17         int count = this.dao.getStoreCount(group);
18 
19         if (count <= 0) {
20             return Status.UNKOWN;
21         } else if (count <= 800) {
22             return Status.UNSALABLE;
23         } else if (count <= 1000) {
24             return Status.NORMAL;
25         } else {
26             return Status.SELLINGWELL;
27         }
28     }
29 }
 1 package com.khlin.test.junit.jmockit.demo;
 2 
 3 import java.util.HashMap;
 4 import java.util.Map;
 5 
 6 public class DAO {
 7 
 8     private Map<String, Integer> groupCounts = new HashMap<String, Integer>();
 9 
10     /**
11      * 假數據
12      */
13     {
14         this.groupCounts.put("A", 500);
15         this.groupCounts.put("B", 1000);
16         this.groupCounts.put("C", 1200);
17     }
18 
19     public int getStoreCount(String group) {
20         Integer count = this.groupCounts.get(group);
21 
22         return null == count ? -1 : count.intValue();
23     }
24 }
 1 package com.khlin.test.junit.jmockit.demo;
 2 
 3 public enum Status {
 4 
 5     /**
 6      * 暢銷
 7      */
 8     SELLINGWELL,
 9     /**
10      * 通常
11      */
12     NORMAL,
13     /**
14      * 滯銷
15      */
16     UNSALABLE,
17     
18     /**
19      * 狀態未知
20      */
21     UNKOWN
22 }

 

基於行爲的Mock 測試,一共三個階段:record、replay、verify。

1)record:在這個階段,各類在實際執行中指望被調用的方法都會被錄製。

2)repaly:在這個階段,執行單元測試Case,原先在record 階段被錄製的調用均可能有機會被執行到。這裏有「有可能」強調了並非錄製了就必定會嚴格執行。

3)verify:在這個階段,斷言測試的執行結果或者其餘是不是原來指望的那樣。

假設如今我只想測試Service,在存貨量900件的狀況下,是否能正確返回NORMAL的狀態。那麼,我並不關心傳入DAO的究竟是哪一個分組,也不關心DAO怎麼去數據庫取數,我只想讓DAO返回900,這樣就能夠測試Service了。

示例代碼:

 1 @RunWith(JMockit.class)
 2 public class ServiceBehavier {
 3 
 4     @Mocked
 5     DAO dao = new DAO();
 6 
 7     private Service service = new Service();
 8 
 9     @Test
10     public void test() {
11 
12         // 1. record 錄製指望值
13         new NonStrictExpectations() {
14             {
15                 /**
16                  * 錄製的方法
17                  */
18                 dao.getStoreCount(anyString);// mock這個方法,不管傳入任何String類型的值,都返回一樣的值,達到黑盒的效果
19                 /**
20                  * 預期結果,返回900
21                  */
22                 result = 900;
23                 /**
24                 times必須調用兩次。在Expectations中,必須調用,不然會報錯,所以不須要做校驗。
25                 在NonStrictExpectations中不強制要求,但要進行verify驗證.但彷佛已經強制要求了
26                 此外還有maxTimes,minTimes
27                 */
28                 times = 1;
29             }
30         };
31         service.setDao(dao);
32 
33         // 2. replay 調用
34         Assert.assertEquals(Status.NORMAL, service.checkStatus("D"));
35 
36 //        Assert.assertEquals(Status.NORMAL, service.checkStatus("D"));
37 
38          //3.校驗是否只調用了一次。若是上面註釋的語句再調一次,且把錄製的times改成2,那麼在驗證階段將會報錯。
39         new Verifications() {
40             {
41                 dao.getStoreCount(anyString);
42                 times = 1;
43             }
44         };
45 
46     }
47 }

基於狀態的Mock測試

經過MockUp類,直接改寫了mock類的代碼邏輯,有點相似白盒測試。

 1 public class ServiceState {
 2 
 3     private DAO dao;
 4     
 5     private Service service;
 6 
 7     @Test
 8     public void test() {
 9         
10         //1. mock對象
11         MockUp<DAO> mockUp = new MockUp<DAO>() {
12 
13             @Mock
14             public int getStoreCount(String group) {
15                 return 2000;
16             }
17         };
18         
19         //2. 獲取實例
20         dao = mockUp.getMockInstance();
21         service = new Service();
22         service.setDao(dao);
23         
24         //3.調用
25         Assert.assertEquals(Status.SELLINGWELL, service.checkStatus("FFF"));
26         
27         //4. 還原對象,避免測試方法之間互相影響。其實對一個實例來講沒什麼影響,對靜態方法影響較大。舊版本的tearDown()方法是Mockit類的靜態方法
28         mockUp.tearDown();
29     }
30 }

3. JMockit mock各類類型或方法的示例代碼

抽象類 

 1 package com.khlin.test.junit.jmockit.demo.jmockit;
 2 
 3 public abstract class AbstractA {
 4     
 5     public abstract int getAbstractAnything();
 6 
 7     public int getAnything() {
 8         return 1;
 9     }
10 }
View Code

接口類

1 package com.khlin.test.junit.jmockit.demo.jmockit;
2 
3 public interface InterfaceB {
4     
5     public int getAnything();
6 }
View Code

普通類

 1 package com.khlin.test.junit.jmockit.demo.jmockit;
 2 
 3 public class ClassA {
 4     
 5     InterfaceB interfaceB;
 6     
 7     private int number;
 8     
 9     public void setInterfaceB(InterfaceB interfaceB) {
10         this.interfaceB = interfaceB;
11     }
12     
13     public int getAnything() {
14         return getAnythingPrivate();
15     }
16     
17     private int getAnythingPrivate() {
18         return 1;
19     }
20     
21     public int getNumber() {
22         return number;
23     }
24     
25     
26     
27     public static int getStaticAnything(){
28         return getStaticAnythingPrivate();
29     }
30     
31     private static int getStaticAnythingPrivate() {
32         return 1;
33     }
34     
35     public int getClassBAnything() {
36         return this.interfaceB.getAnything();
37     }
38 }
View Code

接口實現類

1 package com.khlin.test.junit.jmockit.demo.jmockit;
2 
3 public class ClassB implements InterfaceB {
4 
5     public int getAnything() {
6         return 10;
7     }
8     
9 }
View Code

終極測試代碼

  1 package com.khlin.test.junit.jmockit.demo;
  2 
  3 import mockit.Deencapsulation;
  4 import mockit.Expectations;
  5 import mockit.Injectable;
  6 import mockit.Mock;
  7 import mockit.MockUp;
  8 import mockit.Mocked;
  9 import mockit.NonStrictExpectations;
 10 import mockit.Tested;
 11 import mockit.Verifications;
 12 import mockit.integration.junit4.JMockit;
 13 
 14 import org.junit.Assert;
 15 import org.junit.Test;
 16 import org.junit.runner.RunWith;
 17 
 18 import com.khlin.test.junit.jmockit.demo.jmockit.AbstractA;
 19 import com.khlin.test.junit.jmockit.demo.jmockit.ClassA;
 20 import com.khlin.test.junit.jmockit.demo.jmockit.ClassB;
 21 import com.khlin.test.junit.jmockit.demo.jmockit.InterfaceB;
 22 
 23 @RunWith(JMockit.class)
 24 public class JMockitTest {
 25 
 26     /**
 27      * mock私有方法
 28      */
 29     @Test
 30     public void testPrivateMethod() {
 31 
 32         final ClassA a = new ClassA();
 33         // 局部參數,把a傳進去
 34         new NonStrictExpectations(a) {
 35             {
 36                 Deencapsulation.invoke(a, "getAnythingPrivate");
 37                 result = 100;
 38                 times = 1;
 39             }
 40         };
 41 
 42         Assert.assertEquals(100, a.getAnything());
 43 
 44         new Verifications() {
 45             {
 46                 Deencapsulation.invoke(a, "getAnythingPrivate");
 47                 times = 1;
 48             }
 49         };
 50     }
 51 
 52     /**
 53      * mock私有靜態方法
 54      */
 55     @Test
 56     public void testPrivateStaticMethod() {
 57 
 58         new NonStrictExpectations(ClassA.class) {
 59             {
 60                 Deencapsulation
 61                         .invoke(ClassA.class, "getStaticAnythingPrivate");
 62                 result = 100;
 63                 times = 1;
 64             }
 65         };
 66 
 67         Assert.assertEquals(100, ClassA.getStaticAnything());
 68 
 69         new Verifications() {
 70             {
 71                 Deencapsulation
 72                         .invoke(ClassA.class, "getStaticAnythingPrivate");
 73                 times = 1;
 74             }
 75         };
 76 
 77     }
 78 
 79     /**
 80      * mock公有方法
 81      */
 82     @Test
 83     public void testPublicMethod() {
 84         final ClassA classA = new ClassA();
 85         new NonStrictExpectations(classA) {
 86             {
 87                 classA.getAnything();
 88                 result = 100;
 89                 times = 1;
 90             }
 91         };
 92 
 93         Assert.assertEquals(100, classA.getAnything());
 94 
 95         new Verifications() {
 96             {
 97                 classA.getAnything();
 98                 times = 1;
 99             }
100         };
101     }
102 
103     /**
104      * mock公有靜態方法--基於行爲
105      */
106     @Test
107     public void testPublicStaticMethod() {
108 
109         new NonStrictExpectations(ClassA.class) {
110             {
111                 ClassA.getStaticAnything();
112                 result = 100;
113                 times = 1;
114             }
115         };
116 
117         Assert.assertEquals(100, ClassA.getStaticAnything());
118 
119         new Verifications() {
120             {
121                 ClassA.getStaticAnything();
122                 times = 1;
123             }
124         };
125     }
126     
127     /**
128      * mock公有靜態方法--基於狀態
129      */
130     @Test
131     public void testPublicStaticMethodBaseOnStatus() {
132 
133         MockUp<ClassA> mockUp = new MockUp<ClassA>() {
134             @Mock
135             public int getStaticAnything() { //注意這裏不用聲明爲static
136                 return 100;
137             }
138         };
139         
140         Assert.assertEquals(100, ClassA.getStaticAnything());
141     }
142     
143     /**
144      * mock接口
145      */
146     @Test
147     public void testInterface() {
148         
149         InterfaceB interfaceB = new MockUp<InterfaceB>() {
150             @Mock
151             public int getAnything() {
152                 return 100;
153             }
154         }.getMockInstance();
155         
156         
157         ClassA classA = new ClassA();
158         classA.setInterfaceB(interfaceB);
159         
160         Assert.assertEquals(100, classA.getClassBAnything());
161     }
162     
163     /**
164      * mock接口--基於狀態
165      */
166     @Test
167     public void testInterfaceBasedOnStatus() {
168         final InterfaceB interfaceB = new ClassB();
169         
170         new NonStrictExpectations(interfaceB) {
171             {
172                 interfaceB.getAnything();
173                 result = 100;
174                 times = 1;
175             }
176         };
177         
178         ClassA classA = new ClassA();
179         classA.setInterfaceB(interfaceB);
180         
181         Assert.assertEquals(100, classA.getClassBAnything());
182         
183         new Verifications() {
184             {
185                 interfaceB.getAnything();
186                 times = 1;
187             }
188         };
189     }
190     
191     
192     
193     /**
194      * mock抽象類
195      */
196     @Test
197     public void testAbstract() {
198         AbstractA abstractA = new MockUp<AbstractA>() {
199             @Mock
200             public int getAbstractAnything(){
201                 return 100;
202             }
203             
204             @Mock
205             public int getAnything(){
206                 return 1000;
207             }
208         }.getMockInstance();
209         
210         Assert.assertEquals(100, abstractA.getAbstractAnything());
211         
212         Assert.assertEquals(1000, abstractA.getAnything());
213     }
214 }
View Code
相關文章
相關標籤/搜索