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());
//ClassToTestclassToTest = Whitebox.newInstance(ClassToTest.class);
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.*")