Mock測試框架

引用:忘記引用哪篇博客了。
不管是敏捷開發、持續交付,仍是測試驅動開發(TDD)都把單元測試做爲實現的基石。隨着這些先進的編程開發模式日益深刻人心,單元測試現在顯得愈來愈重要了。在敏捷開發、持續交付中要求單元測試必定要快(不能訪問實際的文件系統或數據庫),而TDD常常會碰到協同模塊還沒有開發的狀況,而mock技術正是解決這些問題的靈丹妙藥。
mock技術的目的和做用是模擬一些在應用中不容易構造或者比較複雜的對象,從而把測試與測試邊界之外的對象隔離開。
咱們能夠本身編寫自定義的Mock對象實現mock技術,可是編寫自定義的Mock對象須要額外的編碼工做,同時也可能引入錯誤。如今實現mock技術的優秀開源框架有不少,本文對幾個典型的mock測試框架做了簡明介紹,但願對你們有所幫助。
1.EasyMock
EasyMock 是早期比較流行的MocK測試框架。它提供對接口的模擬,可以經過錄制、回放、檢查三步來完成大致的測試過程,能夠驗證方法的調用種類、次數、順序,能夠令 Mock 對象返回指定的值或拋出指定異常。經過 EasyMock,咱們能夠方便的構造 Mock 對象從而使單元測試順利進行。
EasyMock 是採用 MIT license 的一個開源項目,能夠在 Sourceforge 上下載到。( http://sourceforge.net/projects/easymock/files/EasyMock/)
若是使用maven也能夠以下引入:
<dependency>
  <groupId>org.easymock</groupId>
  <artifactId>easymock</artifactId>
  <version>3.1</version>
  <scope>test</scope>
</dependency>
使用EasyMock大體能夠劃分爲如下幾個步驟:
①    使用 EasyMock 生成 Mock 對象;
②    錄製 Mock 對象的預期行爲和輸出;
③    將 Mock 對象切換到 播放 狀態;
④    調用 Mock 對象方法進行單元測試;
⑤    對 Mock 對象的行爲進行驗證。
如今用一個例子來簡單呈現以上的步驟,假設有一個類須要被模擬的類以下:
public class Class1Mocked {
         public String hello(String name){
                   System.out.println("hello "+name);
                   return "hello "+name;
         }
         public void show(){
                   System.out.println("Class1Mocked.show()");
         }
}
首先靜態導入EasyMock的方法:
import static org.easymock.EasyMock.*;
 
例1.1 EasyMock第一個例子
@Test
public void testMockMethod() {
         Class1Mocked obj = createMock(Class1Mocked.class);①
         expect(obj.hello("z3")).andReturn("hello l4");②
         replay(obj);③
         String actual = obj.hello("z3");④
         assertEquals("hello l4", actual); verify(obj);⑤
}
在⑤驗證階段中,會嚴格驗證mock對象是否按錄製的行爲如期發生(包括執行的順序及次數)。
 
2.mockito
EasyMock以後流行的mock工具。相對EasyMock學習成本低,並且具備很是簡潔的API,測試代碼的可讀性很高。
mockito能夠在 https://code.google.com/p/mockito/上下載,若是使用maven能夠以下引入:
<dependency>
  <groupId>org.mockito</groupId>
  <artifactId>mockito-all</artifactId>
  <version>1.9.5</version>
  <scope>test</scope>
</dependency>
使用mockito大體能夠劃分爲如下幾個步驟:
①    使用 mockito 生成 Mock 對象;
②    定義(並不是錄製) Mock 對象的行爲和輸出(expectations部分);
③    調用 Mock 對象方法進行單元測試;
④    對 Mock 對象的行爲進行驗證。
如今用一個例子來簡單呈現以上的步驟:
  首先靜態導入mockito的方法:
import static org.mockito.Mockito.*;
例2.1 mockito第一個例子
@Test
public void testMockMethod() {
    Class1Mocked obj=mock(Class1Mocked.class);①
    when(obj.hello("z3")).thenReturn("hello l4");②
    String actual=obj.hello("z3");③
    assertEquals("hello l4",actual);
    
    verify(obj).hello("z3");④
    //verify(obj,times(1)).hello("z3"); //能夠加參數驗證次數
}
能夠看到與EasyMock相比,少了切換到播放狀態一步。這是很天然的,原本就不是錄製而談播放呢,而在驗證階段能夠經過增長參數(time(int)、atLeastOnce()、atLeast(int)、never()等)來精確驗證調用次數。
而若是要驗證調用順序能夠以下控制:
例2.2 驗證順序
@Test
public void testMockMethodInOrder() {
    Class1Mocked objOther = mock(Class1Mocked.class);
    Class1Mocked objCn = mock(Class1Mocked.class);
    when(objOther.hello("z3")).thenReturn("hello l4");
    when(objCn.hello("z3")).thenReturn("hello 張三");
    String other = objOther.hello("z3");
    assertEquals("hello l4", other);
    String cn = objCn.hello("z3");
    assertEquals("hello 張三", cn);
    InOrder inOrder = inOrder(objOther, objCn); //此行並不決定順序,下面的兩行纔開始驗證順序
    inOrder.verify(objOther).hello("z3");
    inOrder.verify(objCn).hello("z3");
}
在以前的介紹的模擬操做中,咱們老是去模擬一整個類或者對象,對於沒有使用 When().thenReturn()方法指定的函數,系統會返回各類類型的默認值(具體值可參考官方文檔)。而局部模擬建立出來的模擬對象依然是原系統對象,雖然可使用方法When().thenReturn()來指定某些具體方法的返回值,可是沒有被用此函數修改過的函數依然按照系統原始類的方式來執行,下面對非局部模擬和局部模擬分別舉例來講明:
例2.3 非局部模擬
@Test
public void testSkipExpect() {
    Class1Mocked obj = mock(Class1Mocked.class);
    assertEquals(null, obj.hello("z3"));
    obj.show(); verify(obj).hello("z3");
    verify(obj).show();
}
上面的代碼省略了expectations部分(即定義代碼行爲和輸出),運行該測試能夠看到hello方法默認返回null(show方法原本就是無返回值的),並且在控制檯中兩個方法都沒有輸出任何語句。
mockito的局部模擬有兩種方式,一種是doCallRealMethod()方式,另外一種是spy()方式。
例2.4 局部模擬doCallRealMethod ()方式
@Test
public void testCallRealMethod () {
    Class1Mocked obj = mock(Class1Mocked.class);
    doCallRealMethod().when(obj).hello("z3"); 
    
    assertEquals("hello z3",obj.hello("z3"));
    assertEquals(null,obj.hello("l4"));
    obj.show(); verify(obj).hello("z3");
    verify(obj).hello("l4");
    verify(obj).show();
}
運行這個測試會發如今執行hello("z3")時會執行原有的代碼,而執行hello("l4")時則是返回默認值null且沒有輸出打印,執行show()一樣沒有輸出打印。
例2.5 局部模擬spy()方式
@Test
public void testSpy() {
    Class1Mocked obj = spy(new Class1Mocked());
    
    doNothing().when(obj).show();
    
    assertEquals("hello z3",obj.hello("z3"));
    obj.show();
    
    verify(obj).hello("z3");
    verify(obj).show();
}
運行這個測試會發如今執行hello("z3")時會執行原有的代碼,可是執行show()時在控制檯中沒有打印語句。
但值得注意的是在mockito的psy()方式模擬中expectations部分使用的語法不一樣,執行起來存在微妙的不一樣,以下:
例2.6 值得注意的「陷阱」
@Test
public void testSpy2() {
    Class1Mocked obj = spy(new Class1Mocked());
    
    when(obj.hello("z3")).thenReturn("hello l4");
    
    assertEquals("hello l4",obj.hello("z3"));
    
    verify(obj).hello("z3");
}
上面的代碼雖然能順利運行,但在控制檯中輸出了hello z3,說明實際的代碼仍然執行了,只是mockito在最後替換了返回值。但下面的代碼就不會執行實際的代碼:
@Test
public void testSpy3() {
    Class1Mocked obj = spy(new Class1Mocked());
    
    doReturn("hello l4").when(obj).hello("z3");
    
    assertEquals("hello l4",obj.hello("z3"));
    
    verify(obj).hello("z3");
}
 
3.PowerMock
這個工具是在EasyMock和Mockito上擴展出來的,目的是爲了解決EasyMock和Mockito不能解決的問題,好比對static, final, private方法均不能mock。其實測試架構設計良好的代碼,通常並不須要這些功能,但若是是在已有項目上增長單元測試,老代碼有問題且不能改時,就不得不使用這些功能了。
PowerMock 在擴展功能時徹底採用和被擴展的框架相同的 API, 熟悉 PowerMock 所支持的模擬框架的開發者會發現 PowerMock 很是容易上手。PowerMock 的目的就是在當前已經被你們所熟悉的接口上經過添加極少的方法和註釋來實現額外的功能。目前PowerMock 僅擴展了 EasyMock 和 mockito,須要和EasyMock或Mockito配合一塊兒使用。
PowerMock能夠在 https://code.google.com/p/powermock/上下載,本文以PowerMock+mockito爲例,使用maven的話,添加以下依賴便可,maven會自動引入mockito的包。
<dependency>
  <groupId>org.powermock</groupId>
  <artifactId>powermock-api-mockito</artifactId>
  <version>1.5</version>
<scope>test</scope>
</dependency>
<dependency>
  <groupId>org.powermock</groupId>
  <artifactId>powermock-module-junit4</artifactId>
  <version>1.5</version>
<scope>test</scope>
</dependency>
如今舉例來講明PowerMock的使用,假設有一個類須要被模擬的類以下:
public class Class2Mocked {
    public static int getDouble(int i){
        return i*2;
    }
    public String getTripleString(int i){
        return multiply3(i)+"";
    }
    private int multiply3(int i){
        return i*3;
    }
}
首先靜態導入PowerMock的方法:
import static org.powermock.api.mockito.PowerMockito.*;
而後在使用junit4的測試類上作以下聲明:
@RunWith(PowerMockRunner.class) @PrepareForTest( { Class2Mocked.class })
例3.1 模擬靜態方法
@Test
public void testMockStaticMethod() {
    mockStatic(Class2Mocked.class);
    when(Class2Mocked.getDouble(1)).thenReturn(3); int actual = Class2Mocked.getDouble(1);
    assertEquals(3, actual); verifyStatic();
    Class2Mocked.getDouble(1);
}
PowerMockit的局域模擬使用方式和mockito相似(畢竟是擴展mockito),但強大之處在於能夠模擬private方法,普通方法和final方法。模擬普通方法和final方法的方式與模擬private方法如出一轍,現以模擬private方法爲例。
 
例3.2 模擬私有方法(doCallRealMethod方式)
@Test
public void testMockPrivateMethod() throws Exception {
    Class2Mocked obj = mock(Class2Mocked.class);
    
    when(obj, "multiply3", 1).thenReturn(4);
    doCallRealMethod().when(obj).getTripleString(1);
    
    String actual = obj.getTripleString(1);
    assertEquals("4", actual);
    
    verifyPrivate(obj).invoke("multiply3", 1); 
}
 
例3.3 模擬私有方法(spy方式)
@Test
public void testMockPrivateMethod2() throws Exception {
    Class2Mocked obj = spy(new Class2Mocked());
    when(obj, "multiply3", 1).thenReturn(4);
    String actual = obj.getTripleString(1);
    assertEquals("4", actual);
    verifyPrivate(obj).invoke("multiply3", 1); 
}
除此以外,PowerMock也能夠模擬構造方法,以下所示:
 
例3.4 模擬構造方法
@Test 
public void testStructureWhenPathDoesntExist() throws Exception { 
    final String directoryPath = "mocked path";
    File directoryMock = mock(File.class);
    whenNew(File.class).withArguments(directoryPath).thenReturn(directoryMock); 
    when(directoryMock.exists()).thenReturn(true);
    File file=new File(directoryPath);
    assertTrue(file.exists());
    verifyNew(File.class).withArguments(directoryPath); 
    verifyPrivate(directoryMock).invoke("exists");
}
 
4.Jmockit
JMockit 是一個輕量級的mock框架是用以幫助開發人員編寫測試程序的一組工具和API,該項目徹底基於 Java 5 SE 的 java.lang.instrument 包開發,內部使用 ASM 庫來修改Java的Bytecode。
Jmockit功能和PowerMock相似,某些功能甚至更爲強大,但我的感受其代碼的可讀性並不強。
Jmockit能夠在 https://code.google.com/p/jmockit/上下載,使用maven的話添加以下依賴便可:
<dependency>
  <groupId>com.googlecode.jmockit</groupId>
  <artifactId>jmockit</artifactId>
  <version>1.0</version>
  <scope>test</scope>
</dependency>
Jmockit也能夠分類爲非局部模擬與局部模擬,區分在於Expectations塊是否有參數,有參數的是局部模擬,反之是非局部模擬。而Expectations塊通常由Expectations類和NonStrictExpectations類定義。用Expectations類定義的,則mock對象在運行時只能按照 Expectations塊中定義的順序依次調用方法,不能多調用也不能少調用,因此能夠省略掉Verifications塊;而用NonStrictExpectations類定義的,則沒有這些限制,因此若是須要驗證,則要添加Verifications塊。
如今舉例說明Jmockit的用法:
例4.1 非局部模擬Expectations類定義
@Mocked //用@Mocked標註的對象,不須要賦值,jmockit自動mock
Class1Mocked obj;@Test
public void testMockNormalMethod1() {
    new Expectations() {
        {
            obj.hello("z3");
            returns("hello l4", "hello w5");
            obj.hello("張三");
            result="hello 李四";
        }
    };
    assertEquals("hello l4", obj.hello("z3"));
    assertEquals("hello w5", obj.hello("z3"));
    assertEquals("hello 李四", obj.hello("張三"));
    try {
        obj.hello("z3");
    } catch (Throwable e) {
        System.out.println("第三次調用hello(\"z3\")會拋出異常");
    }
    try {
        obj.show();
    } catch (Throwable e) {
        System.out.println("調用沒有在Expectations塊中定義的方法show()會拋出異常");
    }
}
例4.2 非局部模擬 NonStrictExpectations類定義
public void testMockNormalMethod2() {
    new NonStrictExpectations() {
        {
            obj.hello("z3");
            returns("hello l4", "hello w5");
        }
    };
    assertEquals("hello l4", obj.hello("z3"));
    assertEquals("hello w5", obj.hello("z3"));
    assertEquals("hello w5", obj.hello("z3"));// 會返回在NonStrictExpectations塊中定義的最後一個返回值
    obj.show(); new Verifications() {
        {
            obj.hello("z3");
            times = 3;
            obj.show();
            times = 1;
        }
    };
}
運行這個測試會發現show()方法沒有在控制檯輸出打印語句,說明是Jmockit對show方法也進行了默認mock。
 
例4.3 局部模擬
@Test
public void testMockNormalMethod() throws IOException {
    final Class1Mocked obj = new Class1Mocked();//也能夠不用@Mocked標註,但須要final關鍵字
    new NonStrictExpectations(obj) {
        {
            obj.hello("z3");
            result = "hello l4";
        }
    };
    assertEquals("hello l4", obj.hello("z3"));
    assertEquals("hello 張三", obj.hello("張三"));
    new Verifications() {
        {
            obj.hello("z3");
            times = 1;
            obj.hello("張三");
            times = 1;
        }
    };
}
運行這個測試發現hello("z3")返回由Expectations塊定義的值,但hello("張三")執行的是實際的代碼。
 
例4.4 模擬靜態方法
@Test
public void testMockStaticMethod() {
    new NonStrictExpectations(Class2Mocked.class) {
        {
            Class2Mocked.getDouble(1);
            result = 3;
        }
    };
   assertEquals(3, Class2Mocked.getDouble(1));
   new Verifications() {
        {
            Class2Mocked.getDouble(1);
            times = 1;
        }
    };
}
 
例4.5 模擬私有方法
@Test
public void testMockPrivateMethod() throws Exception {
    final Class2Mocked obj = new Class2Mocked();
    new NonStrictExpectations(obj) {
        {
            this.invoke(obj, "multiply3", 1);
            result = 4;
        }
    };
    String actual = obj.getTripleString(1);
    assertEquals("4", actual);
    new Verifications() {
        {
            this.invoke(obj, "multiply3", 1);
            times = 1;
        }
    };
}
例4.6 設置私有屬性的值
假設有一個類須要被模擬的類以下:
public class Class3Mocked {
    private String name = "name_init";
    public String getName() {
        return name;
    }
    
    private static String className="Class3Mocked_init";
    
    public static String getClassName(){
        return className;
    }
    
    public static int getDouble(int i){
        return i*2;
    }
    
    public int getTriple(int i){
        return i*3;
    }
}
以下能夠設置私有屬性的值:
@Test
public void testMockPrivateProperty() throws IOException {
    final Class3Mocked obj = new Class3Mocked();
    new NonStrictExpectations(obj) {
        {
            this.setField(obj, "name", "name has bean change!");
        }
    };
    assertEquals("name has bean change!", obj.getName());
}
例4.7 設置靜態私有屬性的值
@Test
public void testMockPrivateStaticProperty() throws IOException {
    new NonStrictExpectations(Class3Mocked.class) {
        {
            this.setField(Class3Mocked.class, "className", "className has bean change!");
        }
    };
    assertEquals("className has bean change!", Class3Mocked.getClassName());
}
 
例4.8 改寫普通方法的內容
@Test
public void testMockNormalMethodContent() throws IOException {
    final Class3Mocked obj = new Class3Mocked();
    new NonStrictExpectations(obj) {
        {
            new MockUp<Class3Mocked>() {
                @Mock
                public int getTriple(int i) {
                    return i * 30;
                }
            };
        }
    };
    assertEquals(30, obj.getTriple(1));
    assertEquals(60, obj.getTriple(2));
}
 
例4.9 改寫靜態方法的內容
若是要改寫Class3Mocked類的靜態getDouble方法,則須要新建一個類含有與getDouble方法相同的函數聲明,而且用@Mock標註,以下:
public class Class4Mocked {
    @Mock
    public static int getDouble(int i){
        return i*20;
    }
}
以下便可改寫:
@Testpublic void testDynamicMockStaticMethodContent() throws IOException {    Mockit.setUpMock(Class3Mocked.class, Class4Mocked.class);     assertEquals(20, Class3Mocked.getDouble(1));    assertEquals(40, Class3Mocked.getDouble(2));}
相關文章
相關標籤/搜索