`

有效使用Mock编写java单元测试

阅读更多
 

有效使用Mock编写java单元测试

Java单元测试对于开发人员质量保证至关重要,尤其当面对一团乱码的遗留代码时,没有高覆盖率的单元测试做保障,没人敢轻易对代码进行重构。然而单元测试的编写也不是一件容易的事情,除非使用TDD方式,否则编写出容易测试的代码不但对开发人员的设计编码要求很高,而且代码中的各种依赖也常常为单元测试带来无穷无尽的障碍。

令人欣慰的是开源社区各种优秀的Mock框架让单元测试不再复杂,本文简单介绍EasyMock,PowerMock等的基本常用用法。

Mock说白了就是打桩(Stub)或则模拟,当你调用一个不好在测试中创建的对象时,Mock框架为你模拟一个和真实对象类似的替身来完成相应的行为。

EasyMock:

使用如下方式在Maven中添加EasyMock的依赖:

 

[html] view plain copy
 
 在CODE上查看代码片派生到我的代码片
  1. <dependency>  
  2.       <groupId>org.easymock</groupId>  
  3.       <artifactId>easymock</artifactId>  
  4.       <version>3.2</version>  
  5.       <scope>test</scope>  
  6.     </dependency>  

EasyMock使用动态代理实现模拟对象创建,其基本步骤为以下四步:

 

以数据库应用为例的被测试代码如下:

 

[java] view plain copy
 
 在CODE上查看代码片派生到我的代码片
  1. public class UserServiceImpl{  
  2.     private UserDao dao;  
  3.     public User query(String id) throws Exception{  
  4.         try{  
  5.     return dao.getById(id);  
  6. }catch(Exception e){  
  7.     throw e;  
  8. }  
  9. return null;  
  10. }  
  11. }  
  12.   
  13. public class UserDao{  
  14.     public User getById(String id) throws Exception{  
  15.         try{  
  16.     return ……;  
  17. }catch(Exception e){  
  18.     throw e;  
  19. }  
  20. return null;  
  21. }  
  22. }  
现在希望对UserServiceImpl进行测试,而UserDao开发组只给出接口,尚未完成功能实现。

 

使用Mock对UserDao进行模拟来测试UserServiceImpl。

(1).基本的测试代码如下:

 

[java] view plain copy
 
 在CODE上查看代码片派生到我的代码片
  1. public class UserServiceImplTest {  
  2.         @Test  
  3.         public void testQuery() {  
  4.             User expectedUser = new User();  
  5.             user.setId(“1001”);  
  6.             UserDao mock  = EasyMock.createMock(UserDao.class);//创建Mock对象  
  7.             Easymock.expect(mock.getById("1001")).andReturn(expectedUser);//录制Mock对象预期行为  
  8.             Easymock.replay(mock);//重放Mock对象,测试时以录制的对象预期行为代替真实对象的行为  
  9.   
  10.             UserServiceImpl  service = new UserServiceImpl();  
  11.             service.setUserDao(mock);  
  12.             user user = service.query("1001");//调用测试方法  
  13.             assertEquals(expectedUser, user); //断言测试结果   
  14.             Easymock.verify(mock);//验证Mock对象被调用  
  15.         }  
  16.     }   

注意:

在EasyMock3.0之前,org.easymock.EasyMock使用JDK的动态代理实现Mock对象创建,因此只能针对接口进行Mock,org.easymock.classextension.EasyMock使用CGLIB动态代理创建Mock对象,可以针对普通类进行Mock。

在EasyMock3.0之后,org.easymock.classextension.EasyMock被废弃,使用org.easymock.EasyMock可以针对接口和普通类进行Mock对象创建。

(2).调用测试设定:

如果想测试UserServiceImpl调用了UserDao的getById方法3次,则使用如下代码即可:

 

[java] view plain copy
 
 在CODE上查看代码片派生到我的代码片
  1. Easymock.expect(mock.getById("1001")).andReturn(exceptUser).times(3);  

 

(3).方法异常:

如果想测试UserServiceImpl在调用UserDao的getById方法时发生异常,可以使用如下代码:

 

[java] view plain copy
 
 在CODE上查看代码片派生到我的代码片
  1. Easymock.expect(mock.getById("1001")).andThrow(new RuntimeException());  
在测试UserServiceImpl时就可以使用try-catch捕获Mock的异常。

 

(4).基本参数匹配:

上面的方法在Mock UserDao的getById方法时传入了“0001”的预期值,这种方式是精确参数匹配,如果UserServiceImpl在调用是传入的参数不是“0001”就会发生Unexpect method的Mock异常,可以使用下面的方法在Mock时进行参数匹配:

 

[java] view plain copy
 
 在CODE上查看代码片派生到我的代码片
  1. Easymock.expect(mock.getById(Easymock.isA(String.class))).andReturn(exceptedUser).times(3);  

 

isA()方法会使用instanceof进行参数类型匹配,类似的方法还有anyInt(),anyObject(), isNull(),same(), startsWith()......

(5).数组类型参数匹配:

如果UserServiceImpl在调用UserDao的方法时传入的参数是数组,代码如下:

 

[java] view plain copy
 
 在CODE上查看代码片派生到我的代码片
  1. public class UserServiceImpl{    
  2.     private UserDao dao;    
  3.     public List<String> queryNames(String[] ids) throws Exception{    
  4.         try{    
  5.     return dao.getNames(ids);    
  6. }catch(Exception e){    
  7.     throw e;    
  8. }    
  9. return null;    
  10. }    
  11. }    
  12.     
  13. public class UserDao{    
  14.     public List<String> getNames(String[] ids) throws Exception{    
  15.         try{    
  16.     return ……;    
  17. }catch(Exception e){    
  18.     throw e;    
  19. }    
  20. return null;    
  21. }    
  22. }  
此时有两种办法来进行参数匹配:

 

a.数组必须和测试给定的一致:

 

[java] view plain copy
 
 在CODE上查看代码片派生到我的代码片
  1. Easymock.expect(mock.getNames(EasyMock.aryEq(testIds))).andReturn(exceptedNames);  
b.不考虑测试数组内容:

 

 

[java] view plain copy
 
 在CODE上查看代码片派生到我的代码片
  1. Easymock.expect(mock.getNames(EasyMock.isA(String[].class))).andReturn(exceptedNames);  
(6).void方法Mock:

 

如果要Mock的方法是无返回值类型,例子如下:

 

[java] view plain copy
 
 在CODE上查看代码片派生到我的代码片
  1. public class UserDao {  
  2.         public void updateUserById(String id) throws Exception{  
  3.             try{  
  4.             update…  
  5.         }catch(Exception e){  
  6.             throw e;   
  7.         }  
  8.         }  
  9.     }  
a.正常Mock代码如下:

 

 

[java] view plain copy
 
 在CODE上查看代码片派生到我的代码片
  1. mock.updateUserById(“TestId”);  
  2. EasyMock.expectLastCall().anytimes();  
b.模拟发生异常的Mock代码如下:

 

 

[java] view plain copy
 
 在CODE上查看代码片派生到我的代码片
  1. mock.updateUserById(“TestId”);  
  2. EasyMock.expectLastCall().andThrow(new RuntimeException()).anytimes();  
(7).多次调用返回不同值的Mock:

 

对于迭代器类型的遍历代码来说,需要在不同调用时间返回不同的结果,以JDBC结果集为例代码如下:

 

[java] view plain copy
 
 在CODE上查看代码片派生到我的代码片
  1. public List<String> getUserNames () throws Exception{  
  2.     List<String> usernames = new ArrayList<String>();  
  3.     ResultSet rs = pstmt.executeQuery(query);  
  4.     try {  
  5.         while(rs.next()){  
  6.             usernames.add(rs.getString(2));  
  7.         }  
  8.     } catch (SQLException e) {  
  9.         throw e;  
  10.     }  
  11.  }  
在Mock结果集的next方法时如果总返回true,则代码就会陷入死循环,如果总返回false则代码逻辑根本无法执行到循环体内。

 

正常的测试逻辑应该是先返回几次true执行循环体,然后在返回false退出循环,使用Mock可以方便模拟这种预期的行为,代码如下:

 

[java] view plain copy
 
 在CODE上查看代码片派生到我的代码片
  1. EasyMock.expect(rs.next()).andReturn(true).times(2).andReturn(false).times(1);  

更多的关于EasyMock的用法,请参考EasyMock官方文档:

http://easymock.org/EasyMock3_0_Documentation.html

 

PowerMock:

上面介绍的EasyMock可以满足单元测试中的大部分需求,但是由于动态代理是使用了面向对象的继承和多态特性,JDK自身的动态代理只针对接口进行代理,其本质是为接口生成一个实现类,而CGLIB可以针对类进行代理,其本质是将类自身作为基类。

如果遇到了静态、final类型的类和方法,以及私有方法,EasyMock的动态代理局限性使得无法测试这些特性情况。

PowerMock是在EasyMock基础上进行扩展(只是补充,不是替代),使用了字节码操作技术直接对生成的字节码类文件进行修改,从而可以方便对静态,final类型的类和方法进行Mock,还可以对私有方法进行Mock,更可以对类进行部分Mock。

PowerMock的工作过程和EasyMock类似,不同之处在于需要在类层次声明@RunWith(PowerMockRunner.class)注解,以确保使用PowerMock框架引擎执行单元测试。

通过如下方式在maven添加PowerMock相关依赖:

 

[html] view plain copy
 
 在CODE上查看代码片派生到我的代码片
  1. <dependency>  
  2.       <groupId>org.powermock</groupId>  
  3.       <artifactId>powermock-api-easymock</artifactId>  
  4.       <version>1.5.1</version>  
  5.       <scope>test</scope>  
  6.     </dependency>  
  7.     <dependency>  
  8.       <groupId>org.powermock</groupId>  
  9.       <artifactId>powermock-module-junit4</artifactId>  
  10.       <version>1.5.1</version>  
  11.       <scope>test</scope>  
  12.     </dependency>  

例子如下:

 

(1).Miock final类的静态方法:

如果测试代码中使用到了java.lang.System类,代码如下:

 

[java] view plain copy
 
 在CODE上查看代码片派生到我的代码片
  1. public class SystemPropertyMockDemo {       
  2.     public String getSystemProperty() throws IOException {       
  3.         return System.getProperty("property");       
  4.     }       
  5. }  
如果对System.getProperty()方法进行Mock,代码如下:

 

 

[java] view plain copy
 
 在CODE上查看代码片派生到我的代码片
  1. @RunWith(PowerMockRunner.class)       
  2. @PrepareForTest({SystemPropertyMockDemo.class})//声明要Mock的类       
  3. public class SystemPropertyMockDemoTest {       
  4.     @Test      
  5.     public void demoOfFinalSystemClassMocking() throws Exception {       
  6.         PowerMock.mockStatic(System.class);//Mock静态方法       
  7.         EasyMock.expect(System.getProperty("property")).andReturn("my property");//录制Mock对象的静态方法       
  8.         PowerMock.replayAll();//重放Mock对象       
  9.         Assert.assertEquals("my property",       
  10.                                   new SystemPropertyMockDemo().getSystemProperty());       
  11.         PowerMock.verifyAll();//验证Mock对象       
  12.     }       
  13. }   
非final类的静态方法代码相同,注意(上述代码只能在EasyMock3.0之后版本正常运行)

 

如果要在EasyMock3.0之前版本正常Mock final类的静态方法,需要使用PowerMockito,

通过如下方式在maven中添加PowerMockito相关依赖:

 

[html] view plain copy
 
 在CODE上查看代码片派生到我的代码片
  1. <dependency>  
  2.       <groupId>org.powermock</groupId>  
  3.       <artifactId>powermock-api-mockito</artifactId>  
  4.       <version>1.5.1</version>  
  5.       <scope>test</scope>  
  6.     </dependency>  

代码如下:

 

 

[java] view plain copy
 
 在CODE上查看代码片派生到我的代码片
  1. @RunWith(PowerMockRunner.class)       
  2. @PrepareForTest({SystemPropertyMockDemo.class})       
  3. public class SystemPropertyMockDemoTest {       
  4.     @Test      
  5.     public void demoOfFinalSystemClassMocking() throws Exception {       
  6.         PowerMockito.mockStatic(System.class);       
  7.         PowerMockito.when(System.getProperty("property")).thenReturn("my property");       
  8.         PowerMock.replayAll();       
  9.         Assert.assertEquals("my property",       
  10.                                   new SystemPropertyMockDemo().getSystemProperty());       
  11.         PowerMock.verifyAll();       
  12.     }       
  13. }  

 

注意:  

对于JDK的类如果要进行静态或final方法Mock时,@PrepareForTest()注解中只能放被测试的类,而非JDK的类,如上面例子中的SystemPropertyMockDemo.class。  

对于非JDK的类如果需要进行静态活final方法Mock时, @PrepareForTest()注解中直接放方法所在的类,若上面例子中的System不是JDK的类,则可以直接放System.class。

@PrepareForTest({......}) 注解既可以加在类层次上(对整个测试文件有效),也可以加在测试方法上(只对测试方法有效)。

(2).Mock非静态的final方法:

 

被测试代码如下:

 

[java] view plain copy
 
 在CODE上查看代码片派生到我的代码片
  1. public class ClassDependency {          
  2.     public final boolean isAlive() {    
  3.         return false;    
  4.     }    
  5. }  
  6.   
  7. public class ClassUnderTest{  
  8.     public boolean callFinalMethod(ClassDependency refer) {    
  9.         return refer.isAlive();    
  10.     }  
  11. }  
使用PowerMock的测试代码如下:

 

 

[java] view plain copy
 
 在CODE上查看代码片派生到我的代码片
  1. @RunWith(PowerMockRunner.class)          
  2. public class FinalMethodMockDemoTest {       
  3.     @Test    
  4.     @PrepareForTest(ClassDependency.class)    
  5.     public void testCallFinalMethod() {    
  6.         ClassDependency depencency = PowerMock.createMock(ClassDependency.class); //创建Mock对象  
  7.         ClassUnderTest underTest = new ClassUnderTest();    
  8.         EasyMock.expect(depencency.isAlive()).andReturn(true);    
  9.         PowerMock.replayAll();  
  10.         Assert.assertTrue(underTest.callFinalMethod(depencency));    
  11.        PowerMock.verifyAll();  
  12.     }  
  13. }  
(3)部分Mock和私有方法Mock:

 

如果被测试类某个方法不太容易调用,可以考虑只对该方法进行Mock,而其他方法全部使用被测试对象的真实方法,可以考虑使用PowerMock的部分Mock,被测试代码如下:

 

[java] view plain copy
 
 在CODE上查看代码片派生到我的代码片
  1. public class DataService {  
  2.         public boolean replaceData(final String dataId, final byte[] binaryData) {  
  3.                 return modifyData(dataId, binaryData);  
  4.         }  
  5.         public boolean deleteData(final String dataId) {  
  6.                 return modifyData(dataId, null);  
  7.         }  
  8.   
  9.         private boolean modifyData(final String dataId, final byte[] binaryData) {  
  10.                 return true;  
  11.         }  
  12. }   
只对modifyData方法进行Mock,而其他方法调用真实方法,测试代码如下:

 

 

[java] view plain copy
 
 在CODE上查看代码片派生到我的代码片
  1. @RunWith(PowerMockRunner.class)   
  2. @PrepareForTest(DataService.class)  
  3. public class DataServiceTest {  
  4. @Test  
  5. public void testReplaceData() throws Exception {  
  6.         DataService tested = PowerMock.createPartialMock(DataService.class, “modifyData”);//创建部分mock对象,只对modifyData方法Mock  
  7.         PowerMock.expectPrivate(tested, “modifyData”, “id”, null).andReturn(true);//录制私有方法  
  8.         PowerMock.replay(tested);  
  9.         assertTrue(tested.deleteData(“id”));  
  10.         PowerMock.verify(tested);  
  11. }  
  12. }   
部分Mock在被测试方法的依赖在同一个类,且不容易创建时比较有用。

 

个人认为私有方法的Mock意义不是很大,完全可以使用反射机制直接调用。

(4).调用对象的构造方法Mock对象:

在被测试方法内部调用构造创建了一个对象很常见,被测试代码如下:

 

[java] view plain copy
 
 在CODE上查看代码片派生到我的代码片
  1. public class PersistenceManager {  
  2.         public boolean createDirectoryStructure(String directoryPath) {  
  3.                 File directory = new File(directoryPath);  
  4.                 if (directory.exists()) {  
  5.                         throw new IllegalArgumentException("\"" + directoryPath + "\" already exists.");  
  6.                 }  
  7.                 return directory.mkdirs();  
  8.         }  
  9. }   
创建文件操作(new File(path))依赖与操作系统底层实现,如果给定的路径不合法,将会出现异常导致测试无法正常覆盖,此时需要使用PowerMock的提供的调用构造方法创建Mock对象,测试代码如下:

 

 

[java] view plain copy
 
 在CODE上查看代码片派生到我的代码片
  1. @RunWith(PowerMockRunner.class)  
  2. @PrepareForTest( PersistenceManager.class )  
  3. public class PersistenceManagerTest {  
  4.            @Test  
  5.         public void testCreateDirectoryStructure_ok() throws Exception {  
  6.                 File fileMock = PowerMock.createMock(File.class);  
  7.                 PersistenceManager tested = new PersistenceManager();  
  8.                 PowerMock.expectNew(File.class"directoryPath").andReturn(fileMock);  
  9.                 EasyMock.expect(fileMock.exists()).andReturn(false);  
  10.                 EasyMock.expect(fileMock.mkdirs()).andReturn(true);  
  11.                 PowerMock.replay(fileMock, File.class);  
  12.                 assertTrue(tested.createDirectoryStructure("directoryPath"));  
  13.                 PowerMock.verify(fileMock, File.class);  
  14.         }  
  15. }   
也可以使用更简便的方法:

 

FilefileMock = PowerMock.createMockAndExpectNew(File.class,“directoryPath”);

通过EasyMock+PowerMock,开发中绝大部分的方法都可以被测试完全覆盖。

 

更多关于PowerMock的用法和参考文档请参考PowerMock官方网址:

https://code.google.com/p/powermock/

分享到:
评论

相关推荐

    java单元测试 spring mock的使用

    "Java单元测试Spring Mock的使用" 在Java Web应用中,单元测试是非常重要的一步,它可以帮助开发者检测代码的正确性和可靠性。传统的单元测试方法需要部署到容器中,然而,这种方法存在一些缺陷,例如需要长时间的...

    深入学习Java单元测试(Junit+Mock+代码覆盖率)

    深入学习Java单元测试(Junit+Mock+代码覆盖率) Java单元测试是软件测试的一种,旨在验证软件的正确性和可靠性。单元测试是编写测试代码,...Java单元测试需要使用Junit框架和Mock技术,并且需要注意代码覆盖率指标。

    java单元测试.rar

    在Java编程中,我们通常使用JUnit框架来进行单元测试。JUnit是开源的、基于Java的测试框架,它允许开发者编写可重复运行的测试用例,以确保代码的正确性和稳定性。 在Java单元测试中,我们需要遵循以下核心概念: ...

    java单元测试demo

    总结来说,这个"java单元测试demo"是关于如何使用JUnit进行接口的功能测试,通过编写测试用例、设置断言以及可能的模拟技术,确保接口的输入和输出符合预期,从而提升代码质量和可维护性。通过运行测试并查看结果,...

    Java单元测试Java单元测试Java单元测试Java单元测试

    1. **JUnit框架**:JUnit是Java中最常用的单元测试框架,它提供了一套简单的API来编写测试用例。开发者可以创建测试类,并在其中定义各种@Test注解的方法,这些方法代表了一个个具体的测试点。 2. **断言...

    java 单元测试

    综上所述,Java单元测试涉及到许多方面,从选择合适的测试框架,到编写有效的测试用例,再到集成到持续集成流程中,都是保证软件质量的关键步骤。熟练掌握这些知识点,将有助于开发出更加健壮、可维护的Java应用程序...

    用Java编写的最流行的单元测试mock框架.zip

    JUnit是一个流行的Java测试框架,它提供了编写和运行测试用例的基础设施。Mockito可以与JUnit无缝集成,通过`@RunWith(MockitoJUnitRunner.class)`注解启动测试,让JUnit自动管理mock对象的生命周期。 在实际开发中...

    java单元测试之道

    单元测试之道在于理解和掌握如何有效地编写和组织这些测试,以便它们能有效地服务于整个项目生命周期。 单元测试是对软件中的最小可测试单元进行检查和验证,对于Java来说,这通常是单个方法或类。通过单元测试,...

    junit4 jar包 Java单元测试框架绿色免费版.zip

    JUnit4是Java编程语言中最广泛使用的单元测试框架之一,它为开发者提供了一种方便、高效的方式来验证代码的正确性。这个“junit4 jar包 Java单元测试框架绿色免费版.zip”文件包含的是JUnit4框架的可执行jar包,用于...

    Java单元测试框架Demo

    理解并熟练使用单元测试框架是每个Java开发者必备的技能。通过这个Demo,你不仅可以了解单元测试的基本概念,还能掌握如何在实际项目中应用它们。持续地进行单元测试,可以提高代码质量,减少bug,并且使重构变得...

    Java单元测试方法与技术

    JUnit是一个开放源码的Java测试框架,它提供了编写和运行可重复测试的能力。在JUnit中,测试用例以类的形式存在,每个测试方法都标记为`@Test`注解。通过这个注解,JUnit知道哪些方法应该被执行。例如: ```java ...

    junit单元测试及Mock应用,超详细的PPT实战应用

    JUnit是Java编程语言中最流行的单元测试框架,它提供了注解、断言和测试套件等机制,使得编写和运行测试变得简单。JUnit5是当前的最新版本,支持Maven项目管理,并且被广泛集成在现代Java开发环境中,如Spring Boot...

    单元测试

    《新建 Microsoft Word 文档.doc》可能包含了具体的单元测试示例或技巧,比如如何编写有效的测试用例,如何处理异步操作的测试,以及如何组织测试代码以保持可读性和可维护性。 《spring测试.rar》可能是一个包含更...

    单元测试 单元测试 java

    JUnit是Java领域最广泛使用的单元测试框架,它提供了丰富的注解、断言和测试结构,使得测试代码编写简洁且易于理解。以下是一些关于单元测试和JUnit的重要知识点: 1. **测试驱动开发(TDD)** - TDD是一种软件开发...

    单元测试与 Mock 方法

    单元测试是软件开发过程中的重要环节,它允许开发者对代码的各个独立部分进行验证,确保它们按照预期工作。...通过使用EasyMock这样的库,我们可以有效地管理和控制代码中的依赖,确保每一个单元都能独立、正确地工作。

    基于软件测试的ATM服务机

    对于单元测试,Eclipse的JUnit插件提供了便捷的测试框架,允许开发者编写和运行JUnit测试用例。JUnit是一个流行的Java单元测试框架,它提供了断言方法来验证代码行为,以及注解来简化测试用例的定义。 接着,...

    Mock.java.zip

    在Java编程语言的学习过程中,`Mock.java.zip`这个压缩包显然包含了与Mocking相关的学习资料,特别是关于如何在Java中进行单元测试时模拟对象(Mock Objects)的技术。Mock Objects是测试驱动开发(TDD)和行为驱动...

Global site tag (gtag.js) - Google Analytics