首先,官網:
http://code.google.com/p/powermock/
先做好心理準備,這個開源工具的官網基本上沒啥文字說明。但是可以下載源代碼,裏面有一些示例測試用例。
當你的領導對你說,UT的代碼覆蓋率要達到100%!!
你會覺得這人瘋了。
但是現在有了powermock,100%就成爲the goal you can reach!!!
powermock將以往我們認爲無法完成的任務變成了可能。
打開Powermock的官網,我們可以看到Usage:
- Mocking static methods
- Mocking final methods or classes
- Mocking private methods
- Mock construction of new objects
- Partial Mocking
- Replay and verify all
- Mock Policies
- Test listeners
- More to come...
這些需求傳統而言,都是不需要,不應該測試的。
下面我們就來舉一個例子,看看其他工具不能解決的怪異問題,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方法等,現在都可以被測試了。大家去參考官網的文檔吧~