一個牛x的mock框架--Powermock

首先,官網:
http://code.google.com/p/powermock/

先做好心理準備,這個開源工具的官網基本上沒啥文字說明。但是可以下載源代碼,裏面有一些示例測試用例。

 
當你的領導對你說,UT的代碼覆蓋率要達到100%!!

你會覺得這人瘋了。

但是現在有了powermock,100%就成爲the goal you can reach!!!

powermock將以往我們認爲無法完成的任務變成了可能。

打開Powermock的官網,我們可以看到Usage:

  1. Mocking static methods
  2. Mocking final methods or classes
  3. Mocking private methods
  4. Mock construction of new objects
  5. Partial Mocking
  6. Replay and verify all
  7. Mock Policies
  8. Test listeners
  9. More to come...
可見,Powermock專門用來應付一些奇怪的測試需求,例如mock private方法,mock 靜態方法,mock final方法。

這些需求傳統而言,都是不需要,不應該測試的。

下面我們就來舉一個例子,看看其他工具不能解決的怪異問題,powermock是怎麼實現的。

 

對象是在方法內部被實例化的

我們來看一個簡單的類,然後考察如何完成對應的測試。

public class SayHi {
 
 public String sayHi(String a, String b){
   Adder adder = new Adder(); //實例化了一個adder,作用就是將兩個字符串加在一起。
   String result = "";
 
   result = adder.add(a, b);
   return result;
 }
}

public class Adder {
 public String add(String a, String b){
  return a + " " + b;
 }
}

如果我們要測試SayHi這個類,很簡單:

public class SayHiTest extends TestCase {

 @Test
 public void testSayHi() {
     SayHi sh = new SayHi();
     assertTrue(sh.sayHi("abc", "def").equalsIgnoreCase("abc def"));
 }
}

通過運行Cobertura,可以看到這個類的測試覆蓋率是100%。(關於Cobertura,我準備過幾天有時間寫篇文章介紹一下)

 

現在,高難度的來了~~稍微更改一下Adder類和SayHi類:

public class Adder throws Exception{
 public String add(String a, String b){
  return a + " " + b;
 }
}

public class SayHi {
 
 public String sayHi(String a, String b){
  Adder adder = new Adder();
  String result = "";
  try {

   //由於Adder類拋出了一個Exception,導致在使用這個類時必須加上try/catch。
   result = adder.add(a, b);
  } catch (Exception e) {
   result = "Failed";
  }
  return result;
 }
}

現在再看看Cobertura,可以看到這個類的測試覆蓋率是75%。因爲在現有的UT中,沒有對異常處理部分的測試。換言之,如果要想測試覆蓋率達到100%,就必須在UT中使Adder拋出異常,進而測試代碼是否做了正確的異常處理。

此時應該是mock出場的時候了,我們想做的事情是,用mock對象代替真實的Adder,強行讓mock對象拋出異常,從而進一步測試。

例如在這個test case中,我們就希望創建一個mockAdder對象,代替Adder。在調用mockAdder.add()時,一定會拋出異常,進而進入到異常處理部分,使運行結果爲failed。

這時真正的問題出現了:
在SayHi這個類的方法sayHi中,實例化了Adder adder = new Adder(); 即,adder這個對象不是inject進來的,而是直接在方法內部實例化出來的。

在Mockito的介紹中我已經提到了,要用mock測試,前提條件就是如何用mock對象覆蓋掉真實對象,讓mock對象代替真實對象做出我們希望的動作。

在Mockito介紹的示例中,我們都假定源代碼提供了get/set方法,因此我們很容易使用set方法,將mock對象傳遞進去。也就是說,一個易於被測試的源代碼應該是:

public class SayHi {

 Adder adder;
 
 public String sayHi(String a, String b){
  adder = getAdder();
  String result = "";
  try {
   result = adder.add(a, b);
  } catch (IOException e) {
   e.printStackTrace();
  }
 
  return result;
 }
 
 public Adder getAdder(){
  return adder;
 }
 
    public void setAdder(Adder a){
  this.adder = a;
 }

}

而對於之前的SayHi類,卻無法將mock對象傳遞進去。100%成爲了一個不可能完成的任務。

 

此時,我們的選擇之一是修改源代碼。在面向對象的語言中,我們一直強調靈活,獨立的代碼結構。如果一個類難於被測試,這很可能是代碼結構不好的象徵。

很明顯sayHi這個方法依賴於Adder這個類,這種寫法很不靈活。很容易由於外圍的更改導致不得不修改這個類的代碼。

但是由於種種原因,也許我們不願意修改源代碼。

此時就進入了本文的正題~~~~~powermock如何將不可能變爲可能。

import static org.junit.Assert.*;
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 static org.mockito.Mockito.*;

//需要注意的是,powermock依賴於JUnit或TestNG,Mockito或EasyMock。

//這裏我使用的是JUnit+Mockito,所以需要import上面的這些類。

@RunWith(PowerMockRunner.class)
@PrepareForTest( { SayHi.class })

//這兩句annotation很重要,否則powermock不會生效的。
public class SayHiTest {

 @Test
 public void testSayHi() throws Exception {
  Adder adder = mock(Adder.class);  //mock出一個模擬的對象,用於代替真實的adder。
  when(adder.add(anyString(), anyString())).thenThrow(new Exception()); //Stub虛擬對象的行爲,即當調用模擬對象的add方法時,拋出異常。到這裏使用的都是Mockito的功能。

  PowerMockito.whenNew(Adder.class).withNoArguments().thenReturn(adder);//這裏powerMock開始發揮作用:當Add.class被實例化的時候,強制使用模擬對象adder代替代碼中被實例化出來的對象。
     
  SayHi sh = new SayHi();
     assertTrue(sh.sayHi("abc", "def").equalsIgnoreCase("failed"));//這裏我們看到了希望的效果:異常處理中的語句result = "Failed";被執行了
 }
}

在這裏很high的去看一下代碼覆蓋率:100%~~yes!!

 

Powermock爲什麼能將不可能變爲可能,我們不需要深究,大概的實現方法是:

PowerMock uses a custom classloader and bytecode manipulation to enable mocking of static methods, constructors, final classes and methods, private methods, removal of static initializers and more.  

簡單的說,powermock是通過修改字節碼.class file + 用戶自定義類裝載器(class loader是JVM的組件之一)來實現的。

你基本上可以認爲,powermock通過修改字節碼文件,修改了你的源代碼,從而用mock對象代替了源代碼中調用的對象。

以往很難被測試的情況,如private方法等,現在都可以被測試了。大家去參考官網的文檔吧~


發佈了313 篇原創文章 · 獲贊 49 · 訪問量 87萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章