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. 基本应用
待测试方法:
- public String methodToTest() {
- return classToMock.methodToMock();
- }
测试case:
- @RunWith(PowerMockRunner. class)
- public class PowerMocTestCase {
-
- @Test
- public void normalTest(){
- ClassToMockclassToMock = PowerMockito.mock(ClassToMock.class);
- PowerMockito. when(classToMock.methodToMock()).thenReturn( "mockObject");
- ClassToTestclassToTest = new ClassToTest(classToMock);
- Assert. assertEquals("mockObject",classToTest.methodToTest());
- }
- }
其中@RunWith注解用于指定PowerMockRunner作为Junit的runner。
classToMock为生成的mock对象。
PowerMockito.when().thenReturn()...用于为mock对象的方法指定返回值。如果没用显示的指定方法行为,mock对象的方法调用就会根据powermock的具体配置执行默认行为,而不会调用真实方法。
3. Mock Static
待测试方法:
- public String methodToTest() {
- return ClassToMock.methodToMock();
- }
测试case:
- @RunWith(PowerMockRunner. class)
- @PrepareForTest({ClassToMock.class })
- public class PowerMocTestCase {
-
- @Test
- public void mockStaticTest(){
- PowerMockito. mockStatic(ClassToMock.class);
- PowerMockito. when(ClassToMock.methodToMock ()).thenReturn("staticReturnValue" );
-
- ClassToTestclassToTest = new ClassToTest();
- Assert. assertEquals("staticReturnValue",classToTest.methodToTest());
- }
- }
在Mock Static method的时候,含静态方法的类需要写到@PrepareForTest注解里面。该注解的作用,将在下面详细介绍。
4. Partial Mock & Mock Private
测试case:
- @RunWith(PowerMockRunner. class)
- @PrepareForTest({ClassToTest. class})
- public class PowerMocTestCase {
-
- @Test
- public void mockPrivateTest(){
- ClassToTest classToTest=PowerMockito. spy(new ClassToTest());
-
- try {
- PowerMockito. doReturn("privateObject").when(classToTest, "privateMethodToMock",Mockito.anyObject());
- classToTest.methodCallPrivate();
-
- PowerMockito. verifyPrivate(classToTest).invoke("privateMethodToMock",Mockito. anyObject());
-
- } catch (Exceptione) {
- e.printStackTrace();
- Assert. fail();
- }
- }
- }
由于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行为是行不通的
- PowerMockito. when(classToTest.methodCallPrivate()).thenReturn( "returnSomeValue");
因为“classToTest.methodCallPrivate()”会触发真是方法的调用。所以partialmock的时候需要通过如下方式指定mock行为:
- PowerMockito.doReturn("returnSomeValue" ).when(classToTest).methodCallPrivate();
5. Whitebox & Mock 内部成员
Mock内部成员就是通过PowerMock get及set类的内部属性值(包括私有)。使用该功能需要导入Whitebox类:
- import org.powermock.reflect.Whitebox ;
PowerMock 提供WhiteBox的目的就是跳过面向对象语言的封装性,允许test case直接操作类的私有成员、私有方法、甚至通过私有构造函数创建实例。
获取内部成员值:
- boolean result = Whitebox.getInternalState(classToTest, "innerFieldName" );
设置内部成员值:
- Whitebox. setInternalState(classToTest, "innerFieldName", true);
直接调用类或对象的私有方法:
- Whitebox.invokeMethod(..)
通过私有构造函数创建对象:
- Whitebox.invokeConstructor(..)
Whitebox跳过构造函数直接实例化对象:
- ClassToTest classToTest = Whitebox.newInstance( ClassToTest. class);
6. Mock 对象的构建(construction)
待测试方法:
- public class ClassThatNewObject {
-
- public void methodThatNewObject(){
- ClassToMockclassToMock = new ClassToMock();
- classToMock.methodToMock ();
- }
- }
上述方法内部new了一个对象,如何mock改内部对象的某些方法的行为?请看下面test case:
- @RunWith(PowerMockRunner. class)
- @PrepareForTest({ClassThatNewObject.class})
- public class PowerMocTestCase {
-
- @Test
- public void constructionMockTest(){
- ClassToMockclassToMock = PowerMockito.mock(ClassToMock.class);
- try {
- PowerMockito.whenNew(ClassToMock.class).withAnyArguments().thenReturn(classToMock);
- ClassThatNewObjectclassNewObject = new ClassThatNewObject();
- classNewObject.methodThatNewObject();
-
- PowerMockito.verifyNew(ClassToMock.class,Mockito.times(1));
- } catch (Exceptione) {
- e.printStackTrace();
- Assert. fail();
- }
- }
- }
利用PowerMock mock对象构建的时候需要将执行对象创建的类放到PrepareForTest注解中,也就是上例中的ClassThatNewObject. class 。
上述case中首先通过正常的mock方式准备一个mock对象,然后通过PowerMockito.whenNew方法来指定ClassToMock在构建的时候直接返回已经准备好的classToMock对象。
PowerMock同时提供了PowerMockito.verifyNew方法,用于验证特定的类是否在被测方法内部进行了实例化,以及实例化了几次。
7. 跳过特定方法的调用
有的时候需要跳过特定的构造函数、方法、静态初始化代码等才能保证单元测试正常执行。下面分几种情况进行介绍:
7.1 跳过父类构造函数的调用。
有些情况我们的类需要继承一些来自第三方库的类,而有些类在单元测试中无法成功的构造,如下面例子所示:
- public class ExampleWithEvilParent extends EvilParent {
-
- private finalString message;
-
- public ExampleWithEvilParent(Stringmessage) {
- this.message= message;
- }
-
- public String getMessage() {
- returnmessage;
- }
- }
- public class EvilParent {
-
- public EvilParent(){
- System.loadLibrary("evil.dll");
- }
- }
父类EvilParent的构造函数中需要加载native的lib,这种情况在junit中直接运行通常会因为无法成功加载native lib而失败,所以在进行unit test的时候需要跳过父类构造函数的调用。具体Test Case如下:
- @RunWith(PowerMockRunner.class)
- @PrepareForTest(ExampleWithEvilParent.class)
- public class ExampleWithEvilParentTest {
-
- @Test
- public voidtestSuppressConstructorOfEvilParent() throws Exception {
- suppress(constructor(EvilParent.class));
- finalString message = "myMessage";
- ExampleWithEvilParenttested = new ExampleWithEvilParent(message);
- assertEquals(message, tested.getMessage());
- }
- }
这种应用场景中,需要将子类添加到PrepareForTest注解中。然后使用suppress(constructor(EvilParent.class))将父类的构造函数跳过。使用suppress方法的时候需要将其进行导入
- import static org.powermock.api.support.membermodification.MemberModifier.suppress;
7.2 跳过类自身的构造函数
如#5中所述,通过Whitebox.newInstance()来跳过构造函数而直接创建实例
7.3 跳过指定的方法调用
如下所示,类中getEvilMessage涉及native lib的加载,在junit无法直接调用
- public class ExampleWithEvilMethod {
-
- private finalString message;
-
- public ExampleWithEvilMethod(Stringmessage) {
- this.message= message;
- }
-
- public StringgetMessage() {
- returnmessage + getEvilMessage();
- }
-
- private StringgetEvilMessage() {
- System.loadLibrary("evil.dll");
- return"evil!";
- }
- }
可以通过suppress(method(ExampleWithEvilMethod.class, "getEvilMessage"));通跳过该方法的调用,具体TestCase如下
- @RunWith(PowerMockRunner.class)
- @PrepareForTest(ExampleWithEvilMethod.class)
- public class ExampleWithEvilMethodTest {
-
- @Test
- public voidtestSuppressMethod() throws Exception {
- suppress(method(ExampleWithEvilMethod.class, "getEvilMessage"));
- finalString message = "myMessage";
- ExampleWithEvilMethodtested = new ExampleWithEvilMethod(message);
- assertEquals(message, tested.getMessage());
- }
- }
7.4 跳过静态初始化
有些情况下被测试类含有静态初始化代码段,静态代码段会在类被加载的时候调用。很多情况下,由于条件不满足,unit test中执行类的静态初始化会失败。一个典型的例子是eclipse swt的一系列类,如org.eclipse.swt.widgets...它们在静态初始化的时候会加载一些native的lib,所以在unittest中无法成功加载。此时可以通过@SuppressStaticInitializationFor跳过静态初始化代码段。如:
- @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作为需要准备得对象:
- @PrepareForTest("com.mypackage.*")