Mock

Mock(模擬測試)


What(它是什麼?)

它是開發模式: 測試驅動開發前端

它是工具:EasyMock, JMock, Mockito, Powermock-*java

EasyMockJMockMockito: 對象模擬技術,只能模擬公共非靜態方法。
Powermock: PowerMock基於三者擴展,可以模擬靜態類、靜態方法、私有方法、構造方法等等。git

它強調的是業務邏輯的聯通性,通常用於單元測試和集成測試!github

Requirements(需求)

No Dependency:每個團隊都但願本身開發的模塊不依賴任何其它的外界條件,溝通成本僅限於雙方接口定義。後端

Why(爲何要使用它?)

敏捷、輕量級api

避免開發模塊之間的耦合框架

簡單 極爲靈活函數

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+"!";
    }

    測試樣例
    ``` java
    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+"!";
    }

    測試代碼
    ```java
    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

  • 模擬靜態公共方法

    業務代碼
    java UserAction: public void executeForPublicStatic(String something){ StaticUserService.sayHi(something); System.out.println(StaticUserService.sayHello(something)); }
    ```java
    StaticUserService:
    public static void sayHi(String arg){
    System.out.println("real"+arg+"!");
    }

    public static String sayHello(String arg){
    return "real"+arg+"!";
    }
    **測試代碼** java
    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

  • 模擬構造函數

    業務代碼
    java UserAction: public void executeForConstructor(String arg){ System.out.println(new ConstructorService(arg).doNoting()); }
    ```java
    ConstructorService
    public class ConstructorService {

    private String tag;
    
      public ConstructorService(String tag){
          this.tag = tag;
      }
    
      public String doNoting(){
          return tag+" doNoting!";
      }

    }
    ```

    測試代碼
    ``` java
    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利器,可以在測試階段最大程度減小各開發團隊之間的耦合,但它並不是萬能,畢竟實際上線時業務模塊之間必然是真實調用,因此它並不能替代聯調測試!

相關文章
相關標籤/搜索