【原創文章,轉載請注明原文章地址,謝謝!】
一、為什么要使用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需求。