PowerMock介紹

【原創文章,轉載請注明原文章地址,謝謝!】

16180055_YaY3.png
一、為什么要使用Mock工具

在做單元測試的時候,我們會發現我們要測試的方法會引用很多外部依賴的對象,比如:(發送郵件,網絡通訊,遠程服務, 文件系統等等)。 而我們沒法控制這些外部依賴的對象,為了解決這個問題,我們就需要用到Mock工具來模擬這些外部依賴的對象,來完成單元測試。

二、PowerMock簡介

PowerMock 也是一個單元測試模擬框架,它是在其它單元測試模擬框架的基礎上做出的擴展。通過提供定制的類加載器以及一些字節碼篡改技巧的應用,PowerMock 現了對靜態方法、構造方法、私有方法以及 Final 方法的模擬支持,對靜態初始化過程的移除等強大的功能。因為 PowerMock 在擴展功能時完全采用和被擴展的框架相同的 API, 熟悉 PowerMock 所支持的模擬框架的開發者會發現 PowerMock 非常容易上手。PowerMock 的目的就是在當前已經被大家所熟悉的接口上通過添加極少的方法和注釋來實現額外的功能。

三、環境配置

如果是使用 Eclipse 開發,只需要在 Eclipse 工程中包含相關庫文件即可。
如果是使用 Maven 開發,則需要根據版本添加以下清單內容到 POM 文件中:

JUnit 版本 4.4 以上請參考清單 1
清單 1

<properties> <powermock.version>1.4.10</powermock.version> </properties> <dependencies> <dependency> <groupId>org.powermock</groupId> <artifactId>powermock-module-junit4</artifactId> <version>${powermock.version}</version> <scope>test</scope> </dependency> <dependency> <groupId>org.powermock</groupId> <artifactId>powermock-api-mockito</artifactId> <version>${powermock.version}</version> <scope>test</scope> </dependency> </dependencies>

JUnit 版本 4.0-4.3 請參考清單 2,
清單 2

<properties> <powermock.version>1.4.10</powermock.version> </properties> <dependencies> <dependency> <groupId>org.powermock</groupId> <artifactId>powermock-module-junit4-legacy</artifactId> <version>${powermock.version}</version> <scope>test</scope> </dependency> <dependency> <groupId>org.powermock</groupId> <artifactId>powermock-api-mockito</artifactId> <version>${powermock.version}</version> <scope>test</scope> </dependency> </dependencies>

JUnit 版本 3 請參考清單 3,
清單 3

` <properties>
<powermock.version>1.4.10</powermock.version>
</properties>

<dependencies>
<dependency>
<groupId>org.powermock</groupId>
<artifactId>powermock-module-junit3</artifactId>
<version>${powermock.version}</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.powermock</groupId>
<artifactId>powermock-api-mockito</artifactId>
<version>${powermock.version}</version>
<scope>test</scope>
</dependency>
</dependencies>`

四、PowerMock入門

PowerMock有兩個重要的注解:

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

如果你的測試用例里沒有使用注解 @PrepareForTest,那么可以不用加注解 @RunWith(PowerMockRunner.class),反之亦然。當你需要使用PowerMock強大功能(Mock靜態、final、私有方法等)的時候,就需要加注解 @PrepareForTest

五、PowerMock基本用法

模擬 Static 方法
首先,我們需要有一個含有 static 方法的代碼,如下所示:

package com._520it.test01;
public class IdGenerator {
  public static long generateNewId() { 
    return 0L;
  } 
}

然后,需要在在被測試代碼中調用上面的方法,測試代碼如下所示:

package com._520it.test01;
public class ClassUnderTest {
    public long methodToTest() { 
          final long id = IdGenerator.generateNewId(); 
          return id;
    } 
}

為了測試各種情況,我們需要讓靜態方法generateNewId()返回不同的值來對被測試的方法methodToTest()的覆蓋測試,實現方式如下:

package com._520it.test01;
import 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;
@RunWith(PowerMockRunner.class)
@PrepareForTest(IdGenerator.class)
public class TestStatic {
// 模擬 Static 方法
    @Test
    public void testCallInternalInstance() throws Exception {
          PowerMockito.mockStatic(IdGenerator.class);
          // 在這個測試用例中,當generateNewId()每次被調用時,都會返回15
          PowerMockito.when(IdGenerator.generateNewId()).thenReturn(15L);
          Assert.assertEquals(15L, new ClassUnderTest().methodToTest());
          //驗證generateNewId()方法是否被調用
          PowerMockito.verifyStatic(); 
          IdGenerator.generateNewId(); 
  }
}

模擬構造函數
有時候,可以很好的模擬構造函數,從而使被測代碼中 new 操作返回的對象可以被隨意定制,會很大程度的提高單元測試的效率,測試代碼如下所示:

package com._520it.test02;
import java.io.File;
public class ClassUnderTest {
      public boolean createDirectoryStructure(String directoryPath) {
            File directory = new File(directoryPath);
            if (directory.exists()) {
                  String msg = "\"" + directoryPath + "\" 已經存在.";
                  throw new IllegalArgumentException(msg);
            }
            return directory.mkdirs();
      }
}

為了充分測試 createDirectoryStructure()函數,我們需要被 new 出來的 File 對象返回文件存在和不存在兩種結果。在 PowerMock 出現之前,實現這個單元測試的方式通常都會需要在實際的文件系統中去創建對應的路徑以及文件。然而,在 PowerMock 的幫助下,本函數的測試可以和實際的文件系統徹底獨立開來:使用 PowerMock 來模擬 File 類的構造函數,使其返回指定的模擬 File 對象而不是實際的 File 對象,然后只需要通過修改指定的模擬 File 對象的實現,即可實現對被測試代碼的覆蓋測試.測試用例如下圖所示:

package com._520it.test02;
import static org.junit.Assert.*;
import static org.powermock.api.mockito.PowerMockito.mock;
import static org.powermock.api.mockito.PowerMockito.verifyNew;
import static org.powermock.api.mockito.PowerMockito.when;
import static org.powermock.api.mockito.PowerMockito.whenNew;
import java.io.File;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.powermock.core.classloader.annotations.PrepareForTest;
import org.powermock.modules.junit4.PowerMockRunner;
@RunWith(PowerMockRunner.class) 
@PrepareForTest(ClassUnderTest.class) 
public class TestConstruction { 
    //模擬構造函數
    @Test 
    public void createDirectoryStructureWhenPathDoesntExist() throws Exception { 
            final String directoryPath = "seemygod"; 
            //創建File的模擬對象
            File directoryMock = mock(File.class); 
            //在當前測試用例下,當出現new File("seemygod")時,就返回模擬對象
            whenNew(File.class).withArguments(directoryPath).thenReturn(directoryMock); 
            //當調用模擬對象的exists時,返回false
            when(directoryMock.exists()).thenReturn(false); 
            //當調用模擬對象的mkdirs時,返回true
            when(directoryMock.mkdirs()).thenReturn(true); 
            assertTrue(new ClassUnderTest().createDirectoryStructure(directoryPath)); 
            //驗證new File(directoryPath);  是否被調用過
            verifyNew(File.class).withArguments(directoryPath); 
    } 
}

模擬私有以及 Final 方法

類的私有方法和Final方法的測試則需要用到局部模擬.
在使用局部模擬,被創建出來的模擬對象依然是原系統對象,被 When().thenReturn()指定的函數將返回指定的值,沒有指定的函數將按原有的方式執行.測試代碼如下圖所示:

package com._520it.test03;
public class PrivatePartialMockingExample {
    public String methodToTest() {
            return methodToMock("input");
    }
    private String methodToMock(String input) {
            return "REAL VALUE = " + input;
    }
}

測試用例:

package com._520it.test03;
import static org.junit.Assert.assertEquals;
import static org.powermock.api.mockito.PowerMockito.*;
import java.util.List;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.powermock.core.classloader.annotations.PrepareForTest;
import org.powermock.modules.junit4.PowerMockRunner;
@RunWith(PowerMockRunner.class)
@PrepareForTest(PrivatePartialMockingExample.class)
public class PrivatePartialMockingExampleTest {
    @Test
    public void demoPrivateMethodMocking() throws Exception {
            final String expected = "TEST VALUE";
            final String nameOfMethodToMock = "methodToMock";
            final String input = "input";
            PrivatePartialMockingExample underTest = spy(new PrivatePartialMockingExample());
            when(underTest, nameOfMethodToMock, input).thenReturn(expected);
            assertEquals(expected, underTest.methodToTest());
            verifyPrivate(underTest).invoke(nameOfMethodToMock, input);
    }
}
更多的Mock方法

六、PowerMock簡單實現原理

1.當某個測試方法被注解@PrepareForTest標注以后,在運行測試用例時,會創建一個新的org.powermock.core.classloader.MockClassLoader實例,然后加載該測試用例使用到的類(系統類除外)。

2.PowerMock會根據你的mock要求,去修改寫在注解@PrepareForTest里的class文件(當前測試類會自動加入注解中),以滿足特殊的mock需求。例如:去除'final方法的final標識,在靜態方法的最前面加入自己的虛擬實現等。

3.如果需要mock的是系統類的final方法和靜態方法,PowerMock不會直接修改系統類的class文件,而是修改調用系統類的class文件,以滿足mock需求。

image
最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 230,527評論 6 544
  • 序言:濱河連續發生了三起死亡事件,死亡現場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發現死者居然都...
    沈念sama閱讀 99,687評論 3 429
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事?!?“怎么了?”我有些...
    開封第一講書人閱讀 178,640評論 0 383
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 63,957評論 1 318
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 72,682評論 6 413
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發上,一...
    開封第一講書人閱讀 56,011評論 1 329
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 44,009評論 3 449
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 43,183評論 0 290
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當地人在樹林里發現了一具尸體,經...
    沈念sama閱讀 49,714評論 1 336
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 41,435評論 3 359
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發現自己被綠了。 大學時的朋友給我發了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 43,665評論 1 374
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 39,148評論 5 365
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質發生泄漏。R本人自食惡果不足惜,卻給世界環境...
    茶點故事閱讀 44,838評論 3 350
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 35,251評論 0 28
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 36,588評論 1 295
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 52,379評論 3 400
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 48,627評論 2 380

推薦閱讀更多精彩內容