EasyMock可以滿足單元測試中的大部分需求,但是由於動態代理是使用了面向對象的繼承和多態特性,JDK自身的動態代理只針對接口進行代理,其本質是為接口生成一個實現類,而CGLIB可以針對類進行代理,其本質是將類自身作為基類。
如果遇到了靜態、final類型的類和方法,以及私有方法,EasyMock的動態代理局限性使得無法測試這些特性情況。
PowerMock是在EasyMock基礎上進行擴展(只是補充,不是替代),使用了字節碼操作技術直接對生成的字節碼類文件進行修改,從而可以方便對靜態,final類型的類和方法進行Mock,還可以對私有方法進行Mock,更可以對類進行部分Mock。
PowerMock的工作過程和EasyMock類似,不同之處在於需要在類層次聲明@RunWith(PowerMockRunner.class)注解,以確保使用PowerMock框架引擎執行單元測試。
通過如下方式在maven添加PowerMock相關依賴:
- <dependency>
- <groupId>org.powermock</groupId>
- <artifactId>powermock-api-easymock</artifactId>
- <version>1.5.1</version>
- <scope>test</scope>
- </dependency>
- <dependency>
- <groupId>org.powermock</groupId>
- <artifactId>powermock-module-junit4</artifactId>
- <version>1.5.1</version>
- <scope>test</scope>
- </dependency>
例子如下:
(1).Miock final類的靜態方法:
如果測試代碼中使用到了java.lang.System類,代碼如下:
- public class SystemPropertyMockDemo {
- public String getSystemProperty() throws IOException {
- return System.getProperty("property");
- }
- }
如果對System.getProperty()方法進行Mock,代碼如下:
- @RunWith(PowerMockRunner.class)
- @PrepareForTest({SystemPropertyMockDemo.class})//聲明要Mock的類
- public class SystemPropertyMockDemoTest {
- @Test
- public void demoOfFinalSystemClassMocking() throws Exception {
- PowerMock.mockStatic(System.class);//Mock靜態方法
- EasyMock.expect(System.getProperty("property")).andReturn("my property");//錄制Mock對象的靜態方法
- PowerMock.replayAll();//重放Mock對象
- Assert.assertEquals("my property",
- new SystemPropertyMockDemo().getSystemProperty());
- PowerMock.verifyAll();//驗證Mock對象
- }
- }
非final類的靜態方法代碼相同,注意(上述代碼只能在EasyMock3.0之后版本正常運行)
如果要在EasyMock3.0之前版本正常Mock final類的靜態方法,需要使用PowerMockito,
通過如下方式在maven中添加PowerMockito相關依賴:
- <dependency>
- <groupId>org.powermock</groupId>
- <artifactId>powermock-api-mockito</artifactId>
- <version>1.5.1</version>
- <scope>test</scope>
- </dependency>
代碼如下:
- @RunWith(PowerMockRunner.class)
- @PrepareForTest({SystemPropertyMockDemo.class})
- public class SystemPropertyMockDemoTest {
- @Test
- public void demoOfFinalSystemClassMocking() throws Exception {
- PowerMockito.mockStatic(System.class);
- PowerMockito.when(System.getProperty("property")).thenReturn("my property");
- PowerMock.replayAll();
- Assert.assertEquals("my property",
- new SystemPropertyMockDemo().getSystemProperty());
- PowerMock.verifyAll();
- }
- }
注意:
對於JDK的類如果要進行靜態或final方法Mock時,@PrepareForTest()注解中只能放被測試的類,而非JDK的類,如上面例子中的SystemPropertyMockDemo.class。
對於非JDK的類如果需要進行靜態活final方法Mock時, @PrepareForTest()注解中直接放方法所在的類,若上面例子中的System不是JDK的類,則可以直接放System.class。
@PrepareForTest({......}) 注解既可以加在類層次上(對整個測試文件有效),也可以加在測試方法上(只對測試方法有效)。
(2).Mock非靜態的final方法:
被測試代碼如下:
- public class ClassDependency {
- public final boolean isAlive() {
- return false;
- }
- }
- public class ClassUnderTest{
- public boolean callFinalMethod(ClassDependency refer) {
- return refer.isAlive();
- }
- }
使用PowerMock的測試代碼如下:
- @RunWith(PowerMockRunner.class)
- public class FinalMethodMockDemoTest {
- @Test
- @PrepareForTest(ClassDependency.class)
- public void testCallFinalMethod() {
- ClassDependency depencency = PowerMock.createMock(ClassDependency.class); //創建Mock對象
- ClassUnderTest underTest = new ClassUnderTest();
- EasyMock.expect(depencency.isAlive()).andReturn(true);
- PowerMock.replayAll();
- Assert.assertTrue(underTest.callFinalMethod(depencency));
- PowerMock.verifyAll();
- }
- }
(3)部分Mock和私有方法Mock:
如果被測試類某個方法不太容易調用,可以考慮只對該方法進行Mock,而其他方法全部使用被測試對象的真實方法,可以考慮使用PowerMock的部分Mock,被測試代碼如下:
- public class DataService {
- public boolean replaceData(final String dataId, final byte[] binaryData) {
- return modifyData(dataId, binaryData);
- }
- public boolean deleteData(final String dataId) {
- return modifyData(dataId, null);
- }
- private boolean modifyData(final String dataId, final byte[] binaryData) {
- return true;
- }
- }
只對modifyData方法進行Mock,而其他方法調用真實方法,測試代碼如下:
- @RunWith(PowerMockRunner.class)
- @PrepareForTest(DataService.class)
- public class DataServiceTest {
- @Test
- public void testReplaceData() throws Exception {
- DataService tested = PowerMock.createPartialMock(DataService.class, “modifyData”);//創建部分mock對象,只對modifyData方法Mock
- PowerMock.expectPrivate(tested, “modifyData”, “id”, null).andReturn(true);//錄制私有方法
- PowerMock.replay(tested);
- assertTrue(tested.deleteData(“id”));
- PowerMock.verify(tested);
- }
- }
部分Mock在被測試方法的依賴在同一個類,且不容易創建時比較有用。
個人認為私有方法的Mock意義不是很大,完全可以使用反射機制直接調用。
(4).調用對象的構造方法Mock對象:
在被測試方法內部調用構造創建了一個對象很常見,被測試代碼如下:
- public class PersistenceManager {
- public boolean createDirectoryStructure(String directoryPath) {
- File directory = new File(directoryPath);
- if (directory.exists()) {
- throw new IllegalArgumentException("\"" + directoryPath + "\" already exists.");
- }
- return directory.mkdirs();
- }
- }
創建文件操作(new File(path))依賴與操作系統底層實現,如果給定的路徑不合法,將會出現異常導致測試無法正常覆蓋,此時需要使用PowerMock的提供的調用構造方法創建Mock對象,測試代碼如下:
- @RunWith(PowerMockRunner.class)
- @PrepareForTest( PersistenceManager.class )
- public class PersistenceManagerTest {
- @Test
- public void testCreateDirectoryStructure_ok() throws Exception {
- File fileMock = PowerMock.createMock(File.class);
- PersistenceManager tested = new PersistenceManager();
- PowerMock.expectNew(File.class, "directoryPath").andReturn(fileMock);
- EasyMock.expect(fileMock.exists()).andReturn(false);
- EasyMock.expect(fileMock.mkdirs()).andReturn(true);
- PowerMock.replay(fileMock, File.class);
- assertTrue(tested.createDirectoryStructure("directoryPath"));
- PowerMock.verify(fileMock, File.class);
- }
- }
也可以使用更簡便的方法:
FilefileMock = PowerMock.createMockAndExpectNew(File.class,“directoryPath”);
通過EasyMock+PowerMock,開發中絕大部分的方法都可以被測試完全覆蓋。
更多關於PowerMock的用法和參考文檔請參考PowerMock官方網址: