首先,官网:
http://code.google.com/p/powermock/
先做好心理准备,这个开源工具的官网基本上没啥文字说明。但是可以下载源代码,里面有一些示例测试用例。
当你的领导对你说,UT的代码覆盖率要达到100%!!
你会觉得这人疯了。
但是现在有了powermock,100%就成为the goal you can reach!!!
powermock将以往我们认为无法完成的任务变成了可能。
打开Powermock的官网,我们可以看到Usage:
这些需求传统而言,都是不需要,不应该测试的。
下面我们就来举一个例子,看看其他工具不能解决的怪异问题,powermock是怎么实现的。
对象是在方法内部被实例化的
我们来看一个简单的类,然后考察如何完成对应的测试。
public class SayHi {
public String sayHi(String a, String b){
Adder adder = new Adder(); //实例化了一个adder,作用就是将两个字符串加在一起。
String result = "";
result = adder.add(a, b);
return result;
}
}
public class Adder {
public String add(String a, String b){
return a + " " + b;
}
}
如果我们要测试SayHi这个类,很简单:
public class SayHiTest extends TestCase {
@Test
public void testSayHi() {
SayHi sh = new SayHi();
assertTrue(sh.sayHi("abc", "def").equalsIgnoreCase("abc def"));
}
}
通过运行Cobertura,可以看到这个类的测试覆盖率是100%。(关于Cobertura,我准备过几天有时间写篇文章介绍一下)
现在,高难度的来了~~稍微更改一下Adder类和SayHi类:
public class Adder throws Exception{
public String add(String a, String b){
return a + " " + b;
}
}
public class SayHi {
public String sayHi(String a, String b){
Adder adder = new Adder();
String result = "";
try {
//由于Adder类抛出了一个Exception,导致在使用这个类时必须加上try/catch。
result = adder.add(a, b);
} catch (Exception e) {
result = "Failed";
}
return result;
}
}
现在再看看Cobertura,可以看到这个类的测试覆盖率是75%。因为在现有的UT中,没有对异常处理部分的测试。换言之,如果要想测试覆盖率达到100%,就必须在UT中使Adder抛出异常,进而测试代码是否做了正确的异常处理。
此时应该是mock出场的时候了,我们想做的事情是,用mock对象代替真实的Adder,强行让mock对象抛出异常,从而进一步测试。
例如在这个test case中,我们就希望创建一个mockAdder对象,代替Adder。在调用mockAdder.add()时,一定会抛出异常,进而进入到异常处理部分,使运行结果为failed。
这时真正的问题出现了:
在SayHi这个类的方法sayHi中,实例化了Adder adder = new Adder(); 即,adder这个对象不是inject进来的,而是直接在方法内部实例化出来的。
在Mockito的介绍中我已经提到了,要用mock测试,前提条件就是如何用mock对象覆盖掉真实对象,让mock对象代替真实对象做出我们希望的动作。
在Mockito介绍的示例中,我们都假定源代码提供了get/set方法,因此我们很容易使用set方法,将mock对象传递进去。也就是说,一个易于被测试的源代码应该是:
public class SayHi {
Adder adder;
public String sayHi(String a, String b){
adder = getAdder();
String result = "";
try {
result = adder.add(a, b);
} catch (IOException e) {
e.printStackTrace();
}
return result;
}
public Adder getAdder(){
return adder;
}
public void setAdder(Adder a){
this.adder = a;
}
}
而对于之前的SayHi类,却无法将mock对象传递进去。100%成为了一个不可能完成的任务。
此时,我们的选择之一是修改源代码。在面向对象的语言中,我们一直强调灵活,独立的代码结构。如果一个类难于被测试,这很可能是代码结构不好的象征。
很明显sayHi这个方法依赖于Adder这个类,这种写法很不灵活。很容易由于外围的更改导致不得不修改这个类的代码。
但是由于种种原因,也许我们不愿意修改源代码。
此时就进入了本文的正题~~~~~powermock如何将不可能变为可能。
import static org.junit.Assert.*;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.powermock.api.mockito.PowerMockito;
import org.powermock.core.classloader.annotations.PrepareForTest;
import org.powermock.modules.junit4.PowerMockRunner;
import static org.mockito.Mockito.*;
//需要注意的是,powermock依赖于JUnit或TestNG,Mockito或EasyMock。
//这里我使用的是JUnit+Mockito,所以需要import上面的这些类。
@RunWith(PowerMockRunner.class)
@PrepareForTest( { SayHi.class })
//这两句annotation很重要,否则powermock不会生效的。
public class SayHiTest {
@Test
public void testSayHi() throws Exception {
Adder adder = mock(Adder.class); //mock出一个模拟的对象,用于代替真实的adder。
when(adder.add(anyString(), anyString())).thenThrow(new Exception()); //Stub虚拟对象的行为,即当调用模拟对象的add方法时,抛出异常。到这里使用的都是Mockito的功能。
PowerMockito.whenNew(Adder.class).withNoArguments().thenReturn(adder);//这里powerMock开始发挥作用:当Add.class被实例化的时候,强制使用模拟对象adder代替代码中被实例化出来的对象。
SayHi sh = new SayHi();
assertTrue(sh.sayHi("abc", "def").equalsIgnoreCase("failed"));//这里我们看到了希望的效果:异常处理中的语句result = "Failed";被执行了
}
}
在这里很high的去看一下代码覆盖率:100%~~yes!!
Powermock为什么能将不可能变为可能,我们不需要深究,大概的实现方法是:
PowerMock uses a custom classloader and bytecode manipulation to enable mocking of static methods, constructors, final classes and methods, private methods, removal of static initializers and more.
简单的说,powermock是通过修改字节码.class file + 用户自定义类装载器(class loader是JVM的组件之一)来实现的。
你基本上可以认为,powermock通过修改字节码文件,修改了你的源代码,从而用mock对象代替了源代码中调用的对象。
以往很难被测试的情况,如private方法等,现在都可以被测试了。大家去参考官网的文档吧~