Mock(模擬測試)
What(它是什么?)##
它是開發模式: 測試驅動開發
它是工具:EasyMock, JMock, Mockito, Powermock-*
EasyMock,JMock,Mockito: 對象模擬技術,只能模擬公共非靜態方法。
Powermock: PowerMock基於三者擴展,能夠模擬靜態類、靜態方法、私有方法、構造方法等等。
它強調的是業務邏輯的聯通性,一般用於單元測試和集成測試!
Requirements(需求)##
No Dependency:每一個團隊都希望自己開發的模塊不依賴任何其它的外界條件,溝通成本僅限於雙方接口定義。
Why(為什么要使用它?)##
敏捷、輕量級
避免開發模塊之間的耦合
簡單 極為靈活
Principle##
通過定義基於方法的模擬調用規則模擬任何代碼的調用過程替代真實代碼執行!
How(如何使用?)##
場景###
模擬RPC服務:目前存在很多應用通過RPC服務調用獲取數據,應用前端的展現嚴重依賴后端服務的穩定性,在測試階段可以選擇通過模擬的方式直接模擬后端服務。
選擇哪一種模擬框架?###
Mockito+Powermock-*
why?###
相對於EasyMock和JMock,Mockito的書寫風格更為簡潔。
核心Api###
- org.mockito.Mockito.mock: 根據給定的類或實例創建模擬對象實例.
- org.mockito.Mockito.when: 綁定模擬行為.
- org.mockito.Mockito.verify: 用於校驗模擬行為的預期結果,比如調用次數與返回值.
- org.mockito.Matchers.any: 用於生成任意類型的對象,例如any(String.class).
- org.mockito.Mockito.times: 用於校驗模擬方法被調用次數的匹配.
- org.junit.runner.RunWith: Junit4之后開放給開發者自定義測試類運行器的注解.
- org.powermock.core.classloader.annotations.PrepareForTest(注解): 包裹上下文中的被模擬類和模擬類方法的調用者,提供一種標識給Powermock框架對其做字節碼修改等處理.
- org.powermock.core.classloader.annotations.PowerMockIgnore(注解): 用於過濾由部分自定義類加載器或spi接口的類型的加載.
- org.powermock.api.mockito.PowerMockito.verifyStatic: 用於靜態方法校驗.
- org.powermock.api.mockito.PowerMockito.verifyPrivate: 用於私有方法校驗.
入門示例###
模擬公共方法(public)
模擬私有方法(private)
模擬公共靜態方法(public static)
模擬私有靜態方法(private static)
模擬構造函數(public constructor)
模擬私有構造函數但存在公共創建實例的方法(private construtor)
模擬包含final修飾符的函數(非靜態函數同private, 靜態函數同private static)
-
模擬公共方法
業務代碼
UserAction: public void executeForPublic(String something){ userService.sayHi(something); System.out.println(userService.sayHello(something)); }UserService: public void sayHi(String arg){ System.out.println("real"+arg+"!"); } public String sayHello(String arg){ return "real"+arg+"!"; }測試樣例
import static org.mockito.Matchers.any; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; import org.junit.Test; import org.mockito.Mockito; import org.wit.service.UserAction; import org.wit.service.UserService; public class MockForPublicDemo { @Test public void demo(){ UserService userService = mock(UserService.class); //userService.sayHi(any(String.class)); //對無返回值的方法 mock(UserService.class);后內部執行邏輯會被空調用覆蓋. Mockito.doNothing().when(userService).sayHi(any(String.class)); //有返回值的方法 when(userService.sayHello(any(String.class))).thenReturn("mock sayHello!"); // 設置業務服務. UserAction userAction = new UserAction(); userAction.setUserService(userService); // 執行目標業務方法. userAction.executeForPublic("public"); // 執行校驗. verify(userService, times(1)).sayHello(any(String.class)); verify(userService, times(1)).sayHi(any(String.class)); } }
輸出:mock sayHello!
-
模擬私有方法
業務代碼
UserAction: public void executeForPrivate(String something){ userService.secreteSay(something); }UserService: public void secreteSay(String arg){ secreteSayHi(arg); System.out.println(secreteSayHello(arg)); } private void secreteSayHi(String arg){ System.out.println("real"+arg+"!"); } private String secreteSayHello(String arg){ return "real"+arg+"!"; }測試代碼
import static org.mockito.Matchers.any; import org.junit.Test; import org.junit.runner.RunWith; import org.mockito.Mockito; import org.powermock.api.mockito.PowerMockito; import org.powermock.core.classloader.annotations.PrepareForTest; import org.powermock.modules.junit4.PowerMockRunner; import org.wit.service.UserAction; import org.wit.service.UserService; @RunWith(PowerMockRunner.class) @PrepareForTest({UserService.class,UserAction.class}) public class MockForPrivateDemo { @Test public void demo() throws Exception { UserService userService = PowerMockito.spy(new UserService()); // 模擬返回值私有方法. PowerMockito.doReturn("mock").when(userService, "secreteSayHello", any(String.class)); // 模擬私有空方法. PowerMockito.doNothing().when(userService, "secreteSayHi", any(String.class)); // 設置業務服務. UserAction userAction = new UserAction(); userAction.setUserService(userService); // 調用業務方法. userAction.executeForPrivate("private"); // 驗證. PowerMockito.verifyPrivate(userService, Mockito.times(1)).invoke("secreteSayHello", any(String.class)); PowerMockito.verifyPrivate(userService, Mockito.times(1)).invoke("secreteSayHi", any(String.class)); } }
輸出:mock
-
模擬靜態公共方法
業務代碼
UserAction: public void executeForPublicStatic(String something){ StaticUserService.sayHi(something); System.out.println(StaticUserService.sayHello(something)); }StaticUserService: public static void sayHi(String arg){ System.out.println("real"+arg+"!"); } public static String sayHello(String arg){ return "real"+arg+"!"; }測試代碼
import static org.mockito.Matchers.any; import static org.mockito.Matchers.anyString; import static org.mockito.Mockito.when; import org.junit.Test; import org.junit.runner.RunWith; import org.mockito.Mockito; import org.powermock.api.mockito.PowerMockito; import org.powermock.core.classloader.annotations.PrepareForTest; import org.powermock.modules.junit4.PowerMockRunner; import org.wit.service.StaticUserService; import org.wit.service.UserAction; @RunWith(PowerMockRunner.class) @PrepareForTest({StaticUserService.class,UserAction.class}) public class MockForPublicStaticDemo { @Test public void demo() throws Exception { //mock會模擬所有的方法. //PowerMockito.mock(StaticUserService.class); //spy只會模擬指定模擬行為的方法. PowerMockito.spy(StaticUserService.class); //模擬返回值的public T static 方法. when(StaticUserService.sayHello(any(String.class))).thenReturn("mock"); //模擬無返回值的public void static 方法 PowerMockito.doNothing().when(StaticUserService.class, "sayHi", anyString()); // 業務方法調用. UserAction userAction = new UserAction(); userAction.executeForPublicStatic("public static"); // 驗證 sayHello. PowerMockito.verifyStatic(Mockito.times(1)); StaticUserService.sayHello(anyString()); // 驗證 sayHi. PowerMockito.verifyStatic(Mockito.times(1)); StaticUserService.sayHi(anyString()); } }
輸出:mock
-
模擬靜態私有方法
業務代碼
UserAction: public void executeForPrivateStatic(String something){ StaticUserService.secreteSay(something); }public static void secreteSay(String arg){ secreteSayHi(arg); System.out.println(secreteSayHello(arg)); } private static void secreteSayHi(String arg){ System.out.println("real"+arg+"!"); } private static String secreteSayHello(String arg){ return "real"+arg+"!"; }測試代碼
import static org.mockito.Matchers.any; import org.junit.Test; import org.junit.runner.RunWith; import org.mockito.Mockito; import org.powermock.api.mockito.PowerMockito; import org.powermock.core.classloader.annotations.PrepareForTest; import org.powermock.modules.junit4.PowerMockRunner; import org.wit.service.StaticUserService; import org.wit.service.UserAction; @RunWith(PowerMockRunner.class) @PrepareForTest({StaticUserService.class}) public class MockForPrivateStaticDemo { @Test public void demo() throws Exception { PowerMockito.spy(StaticUserService.class); //模擬返回值私有方法. //PowerMockito.when(userService, "secreteSayHello", any(String.class)).thenReturn("mock"); PowerMockito.doReturn("mock").when(StaticUserService.class, "secreteSayHello", any(String.class)); //模擬私有空方法. PowerMockito.doNothing().when(StaticUserService.class, "secreteSayHi", any(String.class)); // 執行業務方法. UserAction userAction = new UserAction(); userAction.executeForPrivateStatic("real"); userAction.executeForPrivateStatic("real"); // 驗證私有方法. PowerMockito.verifyPrivate(StaticUserService.class,Mockito.times(2)).invoke("secreteSayHello", any(String.class)); PowerMockito.verifyPrivate(StaticUserService.class,Mockito.times(2)).invoke("secreteSayHi", any(String.class)); } }
輸出: mock
輸出: mock
-
模擬構造函數
業務代碼
UserAction: public void executeForConstructor(String arg){ System.out.println(new ConstructorService(arg).doNoting()); }ConstructorService public class ConstructorService { private String tag; public ConstructorService(String tag){ this.tag = tag; } public String doNoting(){ return tag+" doNoting!"; } }測試代碼
import static org.mockito.Matchers.anyString; import static org.mockito.Mockito.times; import org.junit.Test; import org.junit.runner.RunWith; import org.powermock.api.mockito.PowerMockito; import org.powermock.core.classloader.annotations.PrepareForTest; import org.powermock.modules.junit4.PowerMockRunner; import org.wit.service.ConstructorService; import org.wit.service.UserAction; @RunWith(PowerMockRunner.class) @PrepareForTest({ConstructorService.class,UserAction.class}) public class MockForConstructorDemo { @Test public void testMockForConstructor() throws Exception { UserAction userAction = new UserAction(); //模擬構造函數. //value必須提前構造. ConstructorService value = new ConstructorService("mock"); PowerMockito.spy(ConstructorService.class); //模擬構造函數. PowerMockito.whenNew(ConstructorService.class).withArguments(anyString()).thenReturn(value); //執行業務邏輯. userAction.executeForConstructor("real"); //驗證構造方法. PowerMockito.verifyNew(ConstructorService.class, times(1)).withArguments(anyString()); } }
輸出:mock doNoting!
-
模擬私有構造函數
業務代碼
UserAction: public void executeForPrivateConstrutor(String arg){ System.out.println(PrivateConstructorService.createInstance().getDetail(arg)); }PrivateConstructorService: public class PrivateConstructorService { private PrivateConstructorService(){ } public String getDetail(String arg){ return "private service " + arg; } public static PrivateConstructorService createInstance(){ return new PrivateConstructorService(); } }測試代碼
import static org.mockito.Mockito.when; import org.junit.Test; import org.junit.runner.RunWith; import org.mockito.Mockito; import org.powermock.api.mockito.PowerMockito; import org.powermock.core.classloader.annotations.PrepareForTest; import org.powermock.modules.junit4.PowerMockRunner; import org.wit.service.PrivateConstructorService; import org.wit.service.UserAction; @RunWith(PowerMockRunner.class) @PrepareForTest({PrivateConstructorService.class,UserAction.class}) public class MockForPrivateConstrutorDemo { @Test public void demo() throws Exception { PowerMockito.spy(PrivateConstructorService.class); // 使用PowerMockito創建擁有私有構造函數類的實例 PrivateConstructorService instance = PowerMockito.constructor(PrivateConstructorService.class).newInstance(new Object[]{}); // 模擬靜態函數. when(PrivateConstructorService.createInstance()).thenReturn(instance); // 業務方法調用. UserAction userAction = new UserAction(); userAction.executeForPrivateConstrutor("real"); // 驗證 sayHello. PowerMockito.verifyStatic(Mockito.times(1)); PrivateConstructorService.createInstance(); } }
輸出:private service real
Mock流程##
- 初始化
- 定制規則
- 業務調用
- 驗證
結束語##
Mock是CI利器,能夠在測試階段最大程度減少各開發團隊之間的耦合,但它並非萬能,畢竟實際上線時業務模塊之間必然是真實調用,所以它並不能替代聯調測試!
