一个牛x的mock框架--Powermock

转载 2015年07月08日 17:20:56

首先,官网:
http://code.google.com/p/powermock/

先做好心理准备,这个开源工具的官网基本上没啥文字说明。但是可以下载源代码,里面有一些示例测试用例。

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

你会觉得这人疯了。

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

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

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

  1. Mocking static methods
  2. Mocking final methods or classes
  3. Mocking private methods
  4. Mock construction of new objects
  5. Partial Mocking
  6. Replay and verify all
  7. Mock Policies
  8. Test listeners
  9. 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方法等,现在都可以被测试了。大家去参考官网的文档吧~

一个牛x的mock框架--Powermock

首先,官网: http://code.google.com/p/powermock/ 先做好心理准备,这个开源工具的官网基本上没啥文字说明。但是可以下载源代码,里面有一些示例测试用例。   当...
  • mingtianhaiyouwo
  • mingtianhaiyouwo
  • 2016年10月02日 10:23
  • 172

Java 各种Mock工具比较

转自: http://billben.iteye.com/blog/1872196 http://harrywu304.blog.163.com/blog/static/84566032011...
  • yasi_xi
  • yasi_xi
  • 2014年04月28日 14:56
  • 16032

PowerMock 之进阶学习

模拟局部变量 Mock Local Variable 有返回值得局部变量的模拟! 这里的局部变量没有采取依赖注入的方式,而是采取了一种而是在方法内部 new出一个 EmployeeDao,我们通常都...
  • u012881904
  • u012881904
  • 2016年05月06日 22:04
  • 1119

power mock 入门介绍及使用示例

相关框架 JUnit4、Mockit、PowerMock 相关maven依赖 junit junit 4.11 test ...
  • rainbow702
  • rainbow702
  • 2016年06月29日 15:20
  • 9521

Powermock and sonar jacoco的覆盖率不兼容问题解决 2

Powermock and sonar jacoco的覆盖率不兼容问题解决2:surefire和failsafe与jacoco plugin结合使用。...
  • cloud_ll
  • cloud_ll
  • 2016年08月12日 18:05
  • 1458

powermock如何阻止静态代码块和调用私有方法

在项目中进行单元测试,发现很多类都采用了静态代码块,而静态代码块在class被rongqi...
  • luo_yifan
  • luo_yifan
  • 2014年04月15日 17:58
  • 3462

单元测试从Mockito到PowerMock再到Robolectric的详细解析

单元测试从Mockito到PowerMock再到Robolectric
  • watertekhqx
  • watertekhqx
  • 2017年05月18日 15:44
  • 1219

Java单元测试之模拟利器-使用PowerMock进行Mock测试

原文:http://www.w2bc.com/article/111452 简介 mock是模拟对象,用于模拟真实对象的行为。 Powermock主要用于打桩。比如:方法A的参数需要传入...
  • xjj1314
  • xjj1314
  • 2016年11月23日 20:53
  • 1284

PowerMockito使用详解

一、为什么要使用Mock工具       在做单元测试的时候,我们会发现我们要测试的方法会引用很多外部依赖的对象,比如:(发送邮件,网络通讯,远程服务, 文件系统等等)。 而我们没法控制这些外部...
  • knighttools
  • knighttools
  • 2015年03月25日 23:33
  • 27171

使用PowerMock和Easymock进行单元测试

Java单元测试对于开发人员质量保证至关重要,尤其当面对一团乱码的遗留代码时,没有高覆盖率的单元测试做保障,没人敢轻易对代码进行重构。然而单元测试的编写也不是一件容易的事情,除非使用TDD方式,否则编...
  • u010860412
  • u010860412
  • 2016年02月16日 20:11
  • 1255
内容举报
返回顶部
收藏助手
不良信息举报
您举报文章:一个牛x的mock框架--Powermock
举报原因:
原因补充:

(最多只允许输入30个字)