使用Powermock实现单元测试,提高单元测试覆盖率

http://blog.csdn.net/highth/article/details/42497269

1. PowerMock介绍(本章属于普及知识,熟悉这直接跳过)

   软件设计开发过程中,通常采用分模块、并行开发的模式。在开发周期中,当前模块所依赖的其他模块只有接口,没有具体实现。为了实现对当前模块的单元测试,需要通过mock手段来mock未实现的其他接口。另外,模块还有依赖其他第三方库的情况,而在运行单元测试的过程中,很多第三方lib要么因为缺少条件或资源无法加载,要么直接调用非常消耗运行资源。既然单元测试的重点是当前模块的逻辑,所以可以使用mock手段代替对第三方库的直接调用。

    在Java程序的单元测试中常用的mock工具有Mockito和EasyMock。但是这两种mock工具都无法实现对静态、final、私有方法或类的mock。因此有了功能强大的PowerMock工具。PowerMock并不是一个独立、全新的工具而是在Mockito和EasyMock的基础上进行的扩展,它分别有针对Mockito级EasyMock的扩展实现。本文重点介绍PowerMockito。

    PowerMock是通过直接修改class文件字节码的方式实现对特定对象的mock。事实上是直接改变了mock对象的实现逻辑。在使用PowerMock的情况下,如Jacoco等的测试覆盖率工具是无发正确统计出单元测试覆盖率的。

2. 基本应用

待测试方法:

[java]  view plain  copy
  1. public String methodToTest() {  
  2. return classToMock.methodToMock();  
  3. }  

测试case:

[java]  view plain  copy
  1. @RunWith(PowerMockRunner. class)  
  2. public class PowerMocTestCase {  
  3.    
  4.      @Test  
  5.      public void normalTest(){  
  6.            ClassToMockclassToMock = PowerMockito.mock(ClassToMock.class);  
  7.           PowerMockito. when(classToMock.methodToMock()).thenReturn( "mockObject");  
  8.            ClassToTestclassToTest = new ClassToTest(classToMock);  
  9.            Assert. assertEquals("mockObject",classToTest.methodToTest());  
  10.      }  
  11. }  

其中@RunWith注解用于指定PowerMockRunner作为Junit的runner。

classToMock为生成的mock对象。

PowerMockito.when().thenReturn()...用于为mock对象的方法指定返回值。如果没用显示的指定方法行为,mock对象的方法调用就会根据powermock的具体配置执行默认行为,而不会调用真实方法。

 

3. Mock Static

待测试方法:

[java]  view plain  copy
  1. public String methodToTest() {  
  2.             return ClassToMock.methodToMock();  
  3.      }  

测试case:

[java]  view plain  copy
  1. @RunWith(PowerMockRunner. class)  
  2. @PrepareForTest({ClassToMock.class })  
  3. public class PowerMocTestCase {  
  4.    
  5.      @Test  
  6.      public void mockStaticTest(){  
  7.            PowerMockito. mockStatic(ClassToMock.class);  
  8.           PowerMockito. when(ClassToMock.methodToMock ()).thenReturn("staticReturnValue" );  
  9.              
  10.            ClassToTestclassToTest = new ClassToTest();  
  11.            Assert. assertEquals("staticReturnValue",classToTest.methodToTest());  
  12.      }      
  13. }  

在Mock Static method的时候,含静态方法的类需要写到@PrepareForTest注解里面。该注解的作用,将在下面详细介绍。

 

4. Partial Mock & Mock Private

测试case:

[java]  view plain  copy
  1. @RunWith(PowerMockRunner. class)  
  2. @PrepareForTest({ClassToTest. class})  
  3. public class PowerMocTestCase {  
  4.    
  5.      @Test  
  6.      public void mockPrivateTest(){  
  7.             ClassToTest classToTest=PowerMockito. spy(new ClassToTest());  
  8.             //ClassToTestclassToTest = Whitebox.newInstance(ClassToTest.class);  
  9.             try {  
  10.                 PowerMockito. doReturn("privateObject").when(classToTest, "privateMethodToMock",Mockito.anyObject());      
  11.                 classToTest.methodCallPrivate();  
  12.                   
  13.                 PowerMockito. verifyPrivate(classToTest).invoke("privateMethodToMock",Mockito. anyObject());  
  14.                   
  15.            } catch (Exceptione) {  
  16.                 e.printStackTrace();  
  17.                 Assert. fail();  
  18.            }  
  19.      }  
  20. }  


 

由于private 方法只能在类内部调用,如果将待测试类整体mock ,则调用private的主调方法也会同时被mock掉。

此处通过spy进行partial mock 通过spy产生的mock对象,在没用通过do...return显式指定mock行为的情况下,会调用真实的方法。上述case中通过method name 和参数列表的方式指定private 方法privateMethodToMock(Object obj)的行为。

PowerMockito.verifyPrivate 通过判断private方法是否被调用来断言case是否执行通过。

对于Partial Mock来说,使用如下方式指定mock行为是行不通的

[java]  view plain  copy
  1. PowerMockito. when(classToTest.methodCallPrivate()).thenReturn( "returnSomeValue");  

因为“classToTest.methodCallPrivate()”会触发真是方法的调用。所以partialmock的时候需要通过如下方式指定mock行为:

[java]  view plain  copy
  1. PowerMockito.doReturn("returnSomeValue" ).when(classToTest).methodCallPrivate();  

5. Whitebox & Mock 内部成员

Mock内部成员就是通过PowerMock get及set类的内部属性值(包括私有)。使用该功能需要导入Whitebox类:

[java]  view plain  copy
  1. import org.powermock.reflect.Whitebox ;  

PowerMock 提供WhiteBox的目的就是跳过面向对象语言的封装性,允许test case直接操作类的私有成员、私有方法、甚至通过私有构造函数创建实例。

获取内部成员值:

[java]  view plain  copy
  1. boolean result = Whitebox.getInternalState(classToTest, "innerFieldName" );  

设置内部成员值:

[java]  view plain  copy
  1. Whitebox. setInternalState(classToTest, "innerFieldName"true);  

直接调用类或对象的私有方法:

[java]  view plain  copy
  1. Whitebox.invokeMethod(..)  

通过私有构造函数创建对象:

[java]  view plain  copy
  1. Whitebox.invokeConstructor(..)  

Whitebox跳过构造函数直接实例化对象:

[java]  view plain  copy
  1. ClassToTest classToTest = Whitebox.newInstance( ClassToTest. class);  

6. Mock 对象的构建(construction)

待测试方法:

[java]  view plain  copy
  1. public class ClassThatNewObject {  
  2.        
  3.      public void methodThatNewObject(){  
  4.            ClassToMockclassToMock = new ClassToMock();  
  5.             classToMock.methodToMock ();  
  6.      }  
  7. }  

上述方法内部new了一个对象,如何mock改内部对象的某些方法的行为?请看下面test case:

[java]  view plain  copy
  1. @RunWith(PowerMockRunner. class)  
  2. @PrepareForTest({ClassThatNewObject.class})  
  3. public class PowerMocTestCase {  
  4.        
  5.      @Test  
  6.      public void constructionMockTest(){  
  7.            ClassToMockclassToMock = PowerMockito.mock(ClassToMock.class);  
  8.             try {  
  9.                  PowerMockito.whenNew(ClassToMock.class).withAnyArguments().thenReturn(classToMock);  
  10.                 ClassThatNewObjectclassNewObject = new ClassThatNewObject();  
  11.                 classNewObject.methodThatNewObject();  
  12.                   
  13.                  PowerMockito.verifyNew(ClassToMock.class,Mockito.times(1));  
  14.            } catch (Exceptione) {  
  15.                 e.printStackTrace();  
  16.                 Assert. fail();  
  17.            }  
  18.      }     
  19. }  

利用PowerMock mock对象构建的时候需要将执行对象创建的类放到PrepareForTest注解中,也就是上例中的ClassThatNewObject. class 。

上述case中首先通过正常的mock方式准备一个mock对象,然后通过PowerMockito.whenNew方法来指定ClassToMock在构建的时候直接返回已经准备好的classToMock对象。

PowerMock同时提供了PowerMockito.verifyNew方法,用于验证特定的类是否在被测方法内部进行了实例化,以及实例化了几次。

7. 跳过特定方法的调用

有的时候需要跳过特定的构造函数、方法、静态初始化代码等才能保证单元测试正常执行。下面分几种情况进行介绍:

7.1 跳过父类构造函数的调用。

有些情况我们的类需要继承一些来自第三方库的类,而有些类在单元测试中无法成功的构造,如下面例子所示:

[java]  view plain  copy
  1. public class ExampleWithEvilParent extends EvilParent {  
  2.    
  3.         private finalString message;  
  4.    
  5.         public ExampleWithEvilParent(Stringmessage) {  
  6.                 this.message= message;  
  7.         }  
  8.    
  9.         public String getMessage() {  
  10.                 returnmessage;  
  11.         }  
  12. }  
  13. public class EvilParent {  
  14.    
  15.         public EvilParent(){  
  16.                 System.loadLibrary("evil.dll");  
  17.         }  
  18. }  

父类EvilParent的构造函数中需要加载native的lib,这种情况在junit中直接运行通常会因为无法成功加载native lib而失败,所以在进行unit test的时候需要跳过父类构造函数的调用。具体Test Case如下:

[java]  view plain  copy
  1. @RunWith(PowerMockRunner.class)  
  2. @PrepareForTest(ExampleWithEvilParent.class)  
  3. public class ExampleWithEvilParentTest {  
  4.    
  5.         @Test  
  6.         public voidtestSuppressConstructorOfEvilParent() throws Exception {  
  7.                suppress(constructor(EvilParent.class));  
  8.                 finalString message = "myMessage";  
  9.                 ExampleWithEvilParenttested = new ExampleWithEvilParent(message);  
  10.                assertEquals(message, tested.getMessage());  
  11.         }  
  12. }  

这种应用场景中,需要将子类添加到PrepareForTest注解中。然后使用suppress(constructor(EvilParent.class))将父类的构造函数跳过。使用suppress方法的时候需要将其进行导入

[java]  view plain  copy
  1. import static org.powermock.api.support.membermodification.MemberModifier.suppress;  

7.2 跳过类自身的构造函数

如#5中所述,通过Whitebox.newInstance()来跳过构造函数而直接创建实例

7.3 跳过指定的方法调用

如下所示,类中getEvilMessage涉及native lib的加载,在junit无法直接调用

[java]  view plain  copy
  1. public class ExampleWithEvilMethod {  
  2.    
  3.         private finalString message;  
  4.    
  5.         public ExampleWithEvilMethod(Stringmessage) {  
  6.                 this.message= message;  
  7.         }  
  8.    
  9.         public StringgetMessage() {  
  10.                 returnmessage + getEvilMessage();  
  11.         }  
  12.    
  13.         private StringgetEvilMessage() {  
  14.                 System.loadLibrary("evil.dll");  
  15.                 return"evil!";  
  16.         }  
  17. }  

可以通过suppress(method(ExampleWithEvilMethod.class, "getEvilMessage"));通跳过该方法的调用,具体TestCase如下

[java]  view plain  copy
  1. @RunWith(PowerMockRunner.class)  
  2. @PrepareForTest(ExampleWithEvilMethod.class)  
  3. public class ExampleWithEvilMethodTest {  
  4.    
  5.         @Test  
  6.         public voidtestSuppressMethod() throws Exception {  
  7.                suppress(method(ExampleWithEvilMethod.class"getEvilMessage"));  
  8.                 finalString message = "myMessage";  
  9.                 ExampleWithEvilMethodtested = new ExampleWithEvilMethod(message);  
  10.                assertEquals(message, tested.getMessage());  
  11.         }  
  12. }  

7.4 跳过静态初始化

有些情况下被测试类含有静态初始化代码段,静态代码段会在类被加载的时候调用。很多情况下,由于条件不满足,unit test中执行类的静态初始化会失败。一个典型的例子是eclipse swt的一系列类,如org.eclipse.swt.widgets...它们在静态初始化的时候会加载一些native的lib,所以在unittest中无法成功加载。此时可以通过@SuppressStaticInitializationFor跳过静态初始化代码段。如:

[java]  view plain  copy
  1. @SuppressStaticInitializationFor ({ "org.eclipse.swt.widgets.Widget" , "org.eclipse.swt.widgets.Display" ,"org.eclipse.swt.widgets.Shell" })  


8. PrepareForTest注解

前面的讲解中很多地方用到了PrepareForTest的注解。(如何理解PrepareForTest,何时使用preparefortest)

PrepareForTest注解是用来告诉PowerMock为unit test运行准备指定的类,通常是指那些需要对字节码进行修改的类。

那么何时需要将类放到PrepareForTest中呢?通常在mock final 类、包含final 、private、static、native方法的类以及需要返回一个mock对象的类。

可以这样理解,所有Mockito不能做而PowerMock可以做的事情都需要PrepareForTest。即mock static、private、final的时候相应包含private、static、final的类需要添加到PrepareForTest。而在mock对象构建的时候,需要将真实的new Object()过程换成成指定mock对象的返回,因此具体执行new Object()的类需要添加到PrepareForTest,如上面#6所述。

PrepareForTest可以将整个package作为需要准备得对象:

[java]  view plain  copy
  1. @PrepareForTest("com.mypackage.*")  

评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值