【原創(chuàng)文章,轉(zhuǎn)載請(qǐng)注明原文章地址,謝謝!】

一、為什么要使用Mock工具
在做單元測(cè)試的時(shí)候,我們會(huì)發(fā)現(xiàn)我們要測(cè)試的方法會(huì)引用很多外部依賴的對(duì)象,比如:(發(fā)送郵件,網(wǎng)絡(luò)通訊,遠(yuǎn)程服務(wù), 文件系統(tǒng)等等)。 而我們沒法控制這些外部依賴的對(duì)象,為了解決這個(gè)問題,我們就需要用到Mock工具來模擬這些外部依賴的對(duì)象,來完成單元測(cè)試。
二、PowerMock簡(jiǎn)介
PowerMock 也是一個(gè)單元測(cè)試模擬框架,它是在其它單元測(cè)試模擬框架的基礎(chǔ)上做出的擴(kuò)展。通過提供定制的類加載器以及一些字節(jié)碼篡改技巧的應(yīng)用,PowerMock 現(xiàn)了對(duì)靜態(tài)方法、構(gòu)造方法、私有方法以及 Final 方法的模擬支持,對(duì)靜態(tài)初始化過程的移除等強(qiáng)大的功能。因?yàn)?PowerMock 在擴(kuò)展功能時(shí)完全采用和被擴(kuò)展的框架相同的 API, 熟悉 PowerMock 所支持的模擬框架的開發(fā)者會(huì)發(fā)現(xiàn) PowerMock 非常容易上手。PowerMock 的目的就是在當(dāng)前已經(jīng)被大家所熟悉的接口上通過添加極少的方法和注釋來實(shí)現(xiàn)額外的功能。
三、環(huán)境配置
如果是使用 Eclipse 開發(fā),只需要在 Eclipse 工程中包含相關(guān)庫文件即可。
如果是使用 Maven 開發(fā),則需要根據(jù)版本添加以下清單內(nèi)容到 POM 文件中:
JUnit 版本 4.4 以上請(qǐng)參考清單 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 請(qǐng)參考清單 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 請(qǐng)參考清單 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有兩個(gè)重要的注解:
@RunWith(PowerMockRunner.class)
@PrepareForTest( { YourClassWithEgStaticMethod.class })
如果你的測(cè)試用例里沒有使用注解 @PrepareForTest,那么可以不用加注解 @RunWith(PowerMockRunner.class),反之亦然。當(dāng)你需要使用PowerMock強(qiáng)大功能(Mock靜態(tài)、final、私有方法等)的時(shí)候,就需要加注解 @PrepareForTest。
五、PowerMock基本用法
模擬 Static 方法
首先,我們需要有一個(gè)含有 static 方法的代碼,如下所示:
package com._520it.test01;
public class IdGenerator {
public static long generateNewId() {
return 0L;
}
}
然后,需要在在被測(cè)試代碼中調(diào)用上面的方法,測(cè)試代碼如下所示:
package com._520it.test01;
public class ClassUnderTest {
public long methodToTest() {
final long id = IdGenerator.generateNewId();
return id;
}
}
為了測(cè)試各種情況,我們需要讓靜態(tài)方法generateNewId()返回不同的值來對(duì)被測(cè)試的方法methodToTest()的覆蓋測(cè)試,實(shí)現(xiàn)方式如下:
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);
// 在這個(gè)測(cè)試用例中,當(dāng)generateNewId()每次被調(diào)用時(shí),都會(huì)返回15
PowerMockito.when(IdGenerator.generateNewId()).thenReturn(15L);
Assert.assertEquals(15L, new ClassUnderTest().methodToTest());
//驗(yàn)證generateNewId()方法是否被調(diào)用
PowerMockito.verifyStatic();
IdGenerator.generateNewId();
}
}
模擬構(gòu)造函數(shù)
有時(shí)候,可以很好的模擬構(gòu)造函數(shù),從而使被測(cè)代碼中 new 操作返回的對(duì)象可以被隨意定制,會(huì)很大程度的提高單元測(cè)試的效率,測(cè)試代碼如下所示:
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 + "\" 已經(jīng)存在.";
throw new IllegalArgumentException(msg);
}
return directory.mkdirs();
}
}
為了充分測(cè)試 createDirectoryStructure()函數(shù),我們需要被 new 出來的 File 對(duì)象返回文件存在和不存在兩種結(jié)果。在 PowerMock 出現(xiàn)之前,實(shí)現(xiàn)這個(gè)單元測(cè)試的方式通常都會(huì)需要在實(shí)際的文件系統(tǒng)中去創(chuàng)建對(duì)應(yīng)的路徑以及文件。然而,在 PowerMock 的幫助下,本函數(shù)的測(cè)試可以和實(shí)際的文件系統(tǒng)徹底獨(dú)立開來:使用 PowerMock 來模擬 File 類的構(gòu)造函數(shù),使其返回指定的模擬 File 對(duì)象而不是實(shí)際的 File 對(duì)象,然后只需要通過修改指定的模擬 File 對(duì)象的實(shí)現(xiàn),即可實(shí)現(xiàn)對(duì)被測(cè)試代碼的覆蓋測(cè)試.測(cè)試用例如下圖所示:
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 {
//模擬構(gòu)造函數(shù)
@Test
public void createDirectoryStructureWhenPathDoesntExist() throws Exception {
final String directoryPath = "seemygod";
//創(chuàng)建File的模擬對(duì)象
File directoryMock = mock(File.class);
//在當(dāng)前測(cè)試用例下,當(dāng)出現(xiàn)new File("seemygod")時(shí),就返回模擬對(duì)象
whenNew(File.class).withArguments(directoryPath).thenReturn(directoryMock);
//當(dāng)調(diào)用模擬對(duì)象的exists時(shí),返回false
when(directoryMock.exists()).thenReturn(false);
//當(dāng)調(diào)用模擬對(duì)象的mkdirs時(shí),返回true
when(directoryMock.mkdirs()).thenReturn(true);
assertTrue(new ClassUnderTest().createDirectoryStructure(directoryPath));
//驗(yàn)證new File(directoryPath); 是否被調(diào)用過
verifyNew(File.class).withArguments(directoryPath);
}
}
模擬私有以及 Final 方法
類的私有方法和Final方法的測(cè)試則需要用到局部模擬.
在使用局部模擬,被創(chuàng)建出來的模擬對(duì)象依然是原系統(tǒng)對(duì)象,被 When().thenReturn()指定的函數(shù)將返回指定的值,沒有指定的函數(shù)將按原有的方式執(zhí)行.測(cè)試代碼如下圖所示:
package com._520it.test03;
public class PrivatePartialMockingExample {
public String methodToTest() {
return methodToMock("input");
}
private String methodToMock(String input) {
return "REAL VALUE = " + input;
}
}
測(cè)試用例:
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簡(jiǎn)單實(shí)現(xiàn)原理
1.當(dāng)某個(gè)測(cè)試方法被注解@PrepareForTest標(biāo)注以后,在運(yùn)行測(cè)試用例時(shí),會(huì)創(chuàng)建一個(gè)新的org.powermock.core.classloader.MockClassLoader實(shí)例,然后加載該測(cè)試用例使用到的類(系統(tǒng)類除外)。
2.PowerMock會(huì)根據(jù)你的mock要求,去修改寫在注解@PrepareForTest里的class文件(當(dāng)前測(cè)試類會(huì)自動(dòng)加入注解中),以滿足特殊的mock需求。例如:去除'final方法的final標(biāo)識(shí),在靜態(tài)方法的最前面加入自己的虛擬實(shí)現(xiàn)等。
3.如果需要mock的是系統(tǒng)類的final方法和靜態(tài)方法,PowerMock不會(huì)直接修改系統(tǒng)類的class文件,而是修改調(diào)用系統(tǒng)類的class文件,以滿足mock需求。
