悠悠小仙仙 发表于 2019-4-1 14:02:36

一个牛x的mock框架--Powermock

当你的领导对你说,UT的代码覆盖率要达到100%!!

你会觉得这人疯了。

但是现在有了powermock,100%就成为the goal you can reach!!!

powermock将以往我们认为无法完成的任务变成了可能。

打开Powermock的官网,我们可以看到Usage:

Mocking static methods
Mocking final methods or classes
Mocking private methods
Mock construction of new objects
Partial Mocking
Replay and verify all
Mock Policies
Test listeners
More to come...
可见,Powermock专门用来应付一些奇怪的测试需求,例如mock private方法,mock 静态方法,mock final方法。
这些需求传统而言,都是不需要,不应该测试的。

下面我们就来举一个例子,看看其他工具不能解决的怪异问题,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方法等,现在都可以被测试了。
页: [1]
查看完整版本: 一个牛x的mock框架--Powermock