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 }
接口類
1 package com.khlin.test.junit.jmockit.demo.jmockit; 2 3 public interface InterfaceB { 4 5 public int getAnything(); 6 }
普通類
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 }
接口實現類
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 }
終極測試代碼
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 }