转:http://agiledon.github.io/blog/2013/11/21/play-trick-with-powermock/

当我们面对一个遗留系统时,常见的问题是没有测试。正如Michael Feathers在Working Effectively with Legacy Code一书中对“遗留代码”的定义。他将其简单归纳为“没有测试的代码”。真是太贴切了!正是因为没有测试,使得我们对遗留代码的任何重构都有些战战兢 兢,甚至成为开发人员抵制重构的借口。从收益与成本的比例来看,对于这样的系统,我一贯认为不要盲目进行重构。因为重构的真正适用场景其实是发生在开发期 间,而非维护期间。当然,提升自己的重构能力,尤其学会运用IDE提供的自动重构工具,可以在一定程度上保障重构的质量。然而,安全的做法,还是需要为其 编写测试。

测试是分层的,即使是针对自动化测试。面对遗留系统,成本相对较低的是针对功能特性编写的功能测试(或者说是验收测试),这可以运用一些BDD框架 如Cucumber、JBehave等。由于它的测试粒度较粗,可以以较少的测试用例覆盖系统的主要功能。然而,它的缺点同样存在,那就是反馈周期相对较 长。这就好像你置身一个陌生的城市,在找不到路的情况下,只是跟着感觉走。走了数十公里之后,方才幡然醒悟,想起要翻一翻带在手上的地图。倘若发现方向走 错,再要回转就已经晚了。反馈周期最短的自然是单元测试。同样根据Michael Feather的定义,单元测试一定要快,一定要不依赖于外部资源。单元测试的粒度自然是最小的,但不要直观地认为单元测试就是针对方法。若只是针对方法 来编写单元测试,就会陷入为测试而测试的怪圈。即使是位于技术象限的单元测试,我们仍然要按照业务规则来编写。一个测试方法应该对应一个粒度最小的原子功 能。

要让单元测试跑得快,还要不吃草(依赖外部资源),应该怎么办?答案呼之欲出,那就是Mock。Mock当然不是万能的,记得胡凯写过一篇文章,提 及Mock不是银弹。我知道他仅仅是为了强调这个观点,避免太多人过于依赖Mock,因为Brooks早就发表过论断,在软件行业,其实根本就“没有银 弹”。关于Mock的争论由来已久,对此,我准备避而不谈。至少在我看来,如下几点基本已成定论:

1、是Mock行为,而非Mock数据;如果是针对数据,则应该属于Stub的范畴;

2、Mock通常发生在三种情况(让我们假设被测试对象为消费者,它要协作的对象为服务,此时需要Mock服务):服务的行为只有定义,还未实现; 服务需要访问外部资源(这意味着它可能很慢,也意味着它需要依赖外部资源);服务的行为结果不确定(例如天气服务,股票服务)。

自然,我们不需要自己写Mock,有许多现成的好用框架,例如Java平台下的Mockito与EasyMock,.NET平台下的Moq,以及C++下的Google Mock和MockCpp。

然而,问题依然存在。考虑这样两种情况:

1、当我们要Mock的服务,其实是Utils的静态方法时,应该怎么办?

2、当我们要测试的方法内部直接实例化了协作的服务对象,又该怎么办?

显然,这是设计和代码的坏味道,它明显违背了DIP原则,即它不应该依赖于细节,而应该依赖于抽象。换言之,它产生了对服务对象的具体依赖。若要遵循DIP,就应该在被测对象的外部来注入依赖。这种紧耦合酿成了我们设计的类不具备良好的可测试性。

一个蠢蠢欲动的声音在说:让我们重构吧!且住,先让我们把这苛求的眼光放柔和一点。当你视所有丑陋的代码为“蝼蚁”时,那是因为你站在了足够的高 度。可是站得太高,往往摔得更惨。现在,还是脚踏实地,先设身处地地考虑这样的场景:这是一个代码行数超过1000万行的软件系统,一共有十余个开发团 队,一百多名开发人员在这个团队中工作。这个系统几乎没有测试,而系统的Jar包则达到上千个。这些Utils的静态方法被数十乃至上百个类调用,牵涉到 的模块也有多个甚至十余个。而且,这个系统并没有引入任何一个IoC容器。有了这样一个背景,让我们再把柔和的眼光变得锐利一点,分析分析重构的可行性。 要消除前面提到的坏味道,就需要将这些静态方法修改为实例方法,并通过依赖注入的方式注入。这个变化带来的是对整个系统的全局影响,即使我们有一些自动化 重构的手段,仍然不认为这种重构一定就是可行的。

这就是我要谈PowerMock的前提!

现在,轮到玩花招的PowerMock出场了。有了它,什么静态方法,方法内部实例,乃至私有方法,统统都是浮云。而且,它对Mockito与 EasyMock的扩展,使得我们更容易熟悉它的语法。要使用它很简单,需先设置对它的依赖。我选择了PowerMock针对Mockito的扩展:

        <dependency>
<groupId>org.powermock</groupId>
<artifactId>powermock-api-mockito</artifactId>
<version>1.5.1</version>
</dependency>
<dependency>
<groupId>org.powermock</groupId>
<artifactId>powermock-module-junit4</artifactId>
<version>1.5.1</version>
</dependency>
<dependency>
<groupId>org.mockito</groupId>
<artifactId>mockito-all</artifactId>
<version>1.9.5</version>
</dependency> 让我先给出如下的一份奇奇怪怪的设计,它主要是为了迎合之前提到的代码臭味。 public class EmployeeTableUtil {
public int count() {
return 0;
}
public static final List<Employee> findAll() {
return new ArrayList<Employee>();
}
public void insert(Employee employee) {
if (existed(employee.getId())) {
throw new ExistedEmployeeException();
}
//insert employee
}
public static final void update(Employee employee) {
if (employee == null) {
throw new NullEmployeeException();
}
}
public boolean delete(Employee employee) {
if (existed(employee.getId())) {
//delete employee
return true;
}
return false;
}
private boolean existed(String id) {
return false;
}
}
public class EmployeeRepository {
private EmployeeTableUtil tableUtil;
public int count() {
return new EmployeeTableUtil().count();
}
public List<Employee> findAll() {
return EmployeeTableUtil.findAll();
}
public boolean insert(Employee employee) {
try {
tableUtil.insert(employee);
return true;
} catch (ExistedEmployeeException e) {
return false;
}
}
public boolean update(Employee employee) {
try {
EmployeeTableUtil.update(employee);
return true;
} catch (NullEmployeeException e) {
return false;
}
}
public boolean delete(Employee employee) {
return tableUtil.delete(employee);
}
private double bonus(Employee employee) {
return employee.getSalary() * 0.1d;
}
public void setTableUtil(EmployeeTableUtil tableUtil) {
this.tableUtil = tableUtil;
}
} 现在,我要针对EmployeeRepository编写测试,它协作的服务类为EmployeTableUtil,主要承担了访问数据库的职责。在测试EmployeeRepository时,我们需要去Mock协作对象EmployeeTableUtil的行为。 在使用PowerMock编写测试时,首先需要在测试类上运用框架提供的Annotation:@PrepareForTest,以及一个 Runner:PowerMockRunner。因为我们要Mock的对象为EmployeeTableUtil,故而测试类的定义为: @RunWith(PowerMockRunner.class)
@PrepareForTest(EmployeeTableUtil.class)
public class EmployeeRepositoryTest {
private EmployeeRepository repository;
@Before
public void setUp() throws Exception {
repository = new EmployRepository();
}
} 现在我要使用PowerMock去Mock静态方法,如EmployeeTableUtil的findAll()方法,至于要测试的方法则为EmployeeRepository的findAll()方法。则编写的单元测试为: @Test
public void should_mock_static_method() {
List<Employee> employee = new ArrayList<Employee>();
employee.add(new Employee("1"));
employee.add(new Employee("2"));
PowerMockito.mockStatic(EmployeeTableUtil.class);
when(EmployeeTableUtil.findAll()).thenReturn(employee);
List<Employee> employees = repository.findAll();
assertThat(employees.size(), is(2));
assertThat(employees.get(0).getId(), is("1"));
assertThat(employees.get(1).getId(), is("2"));
PowerMockito.verifyStatic();
EmployeeTableUtil.findAll();
} Mock静态方法的关键是先要调用框架定义的PowerMockito类的mockStatic()方法(针对EasyMock有相似的类)。方法 接收的参数就是我们要Mock的类的类型。接下来就可以调用Mockito框架的方法,对我们要模拟的方法findAll()进行模拟,这里主要的工作是 为模拟方法的返回值设置一个stub。之后就是单元测试的验证逻辑。如果需要验证被Mock的方法是否被调用,则需要调用 PowerMockito.verifyStatic()方法,紧随其后的是被mock的方法。 如果要Mock的方法是一个命令方法(即没有返回值的方法),做法又有不同。倘若熟悉Mockito,可以看出PowerMock完全沿袭了Mockito的风格(当然,针对EasyMock的扩展则会沿袭EasyMock的风格,这是PowerMock体贴人的地方): @Test
public void should_mock_exception_for_command_method_in_mock_object() {
Employee employee = new Employee("1");
PowerMockito.mockStatic(EmployeeTableUtil.class);
PowerMockito.doThrow(new NullEmployeeException()).when(EmployeeTableUtil.class);
EmployeeTableUtil.update(employee);
assertThat(repository.update(employee), is(false));
} PowerMock还可以Mock私有方法,当然只能是实例的私有方法。这主要发生在当我们不希望Mock服务的公开方法时(例如,公开方法的逻辑 没有Mock的必要),但这些公开方法的内部又调用了自己的私有方法,而私有方法却需要Mock。例如,EmployeeTableUtil的 insert()和delete()方法调用了私有的existed()方法。假设insert()和delete()方法不需要我们Mock,此时就需 要对私有方法existed()进行Mock。因为是实例方法,所以下面的测试方法通过调用setTableUtil()方法将被模拟的对象注入到 EmployeeRepository对象中: @Test
public void should_mock_private_method() throws Exception {
Employee employee = new Employee("1");
EmployeeTableUtil util = PowerMockito.spy(new EmployeeTableUtil());
PowerMockito.when(util,"existed", anyString())
.thenReturn(true);
repository.setTableUtil(util);
assertThat(repository.insert(employee), is(false));
assertThat(repository.delete(employee), is(true));
} PowerMock顺带还提供了测试私有方法的便捷办法(注意是测试,而不是Mock)。例如,测试EmployeeReployee类的私有方法bonus(): @Test
public void should_test_private_method() throws Exception {
Employee employee = new Employee("1");
employee.setSalary(8000);
double result = Whitebox.<Double>invokeMethod(repository, "bonus", employee);
assertThat(result, is(800d));
} 最后再来看看另外一种诡异的手段。假设我们要测试的方法其内部调用了协作对象的方法,而该协作对象不是在外部注入的,而是在方法中直接实例化。例如在前面例子中,EmployeeRepository的count()方法: public class EmployeeRepository {
private EmployeeTableUtil tableUtil;
public int count() {
return new EmployeeTableUtil().count();
}
} 要针对这样一种情形进行Mock,做法有所不同。因为它实际针对的是待测类——即这里的EmployeeRepository——执行 count()方法,这就需要在count()方法内部形成一个拦截点。因此,需要在@PrepareForTest标记中指向 EmployeeRepository类的类型,而非我们要Mock的EmployeeTableUtil。故而,我们需要为这个测试定义一个新的测试 类: @RunWith(PowerMockRunner.class)
@PrepareForTest(EmployeeRepository.class)
public class ConstructionEmployeeRepositoryTest {
@Test
public void should_mock_construction_object() throws Exception {
EmployeeTableUtil util = mock(EmployeeTableUtil.class);
when(util.count()).thenReturn(100);
PowerMockito.whenNew(EmployeeTableUtil.class).withNoArguments().thenReturn(util);
EmployeeRepository repository = new EmployeeRepository();
assertThat(repository.count(), is(100));
}
} 注意,测试方法的前两行代码调用的mock()与when()方法都是Mockito提供的方法,与PowerMock无关。 我虽然没有看过PowerMock的源代码,但我猜测,当我们在使用PowerMock去Mock静态方法时,定然是结合反射与代理的方式来完成对 该方法的调用,其中必然需要初始化该类。由于是静态方法,更多的是需要静态初始化。此外,还有一种情形时,你所要测试的类声明和初始化了一个静态的字段。 这些都可能需要调用静态初始化。我们在开发中就碰到一种情形是,我们希望Mock的一个类,定义了一个static块,其中又调用了私有的静态方法。在这 个私有静态方法中,依赖了其他的一些对象,这些对象还牵扯到服务容器的问题。即使以静态的方式Mock了该类,仍然逃不过运行static块的命运,换言 之,仍然需要依赖服务容器。这时,又可以祭出PowerMock的杀器了。它提供了@SuppressStaticInitializationFor的 标注,在该标注中需要传入字符串类型的目标类型的全名。假设EmployeeTableUtil有一个static块是我们需要绕过的,它的类全名为 com.agiledon.powermock.EmployeeTableUtil: @RunWith(PowerMockRunner.class)
@PrepareForTest(EmployeeTableUtil.class)
@SuppressStaticInitializationFor("com.agiledon.powermock.EmployeeTableUtil")
public class EmployeeRepositoryTest {} 此外,对于@PrepareForTest以及@SuppressStaticInitializationFor标记而言,如果需要针对多个类型,则需要传入一个数组,例如: @RunWith(PowerMockRunner.class)
@PrepareForTest({MockedObjectA.class, MockObjectB.class})
@SuppressStaticInitializationFor({"com.agiledon.powermock.MockedObjectA", "com.agiledon.powermock.MockedObjectB"})
public class OneTest {}

PowerMock用法[转]的更多相关文章

  1. PowerMockito(PowerMock用法)

    网络上大部分是powermock 的用法, PowerMock有两个重要的注解: –@RunWith(PowerMockRunner.class) –@PrepareForTest( { YourCl ...

  2. PowerMock遇到的问题——5

    在做单元测试时,有时在一个方法中会调用这个类的其他私有方法,那么如何指定这些方法的返回值呢? 解决方法:用 createPartialMock 具体用法如下: TestClass test=Power ...

  3. powerMock比easyMock和Mockito更强大(转)

    powerMock是基于easyMock或Mockito扩展出来的增强版本,所以powerMock分两种类型,如果你习惯于使用easyMock的,那你就下载基于easyMock的powerMock,反 ...

  4. mock测试之powermock

    由于公司框架依赖别的模块, 导致我们开发老是需要跟着他们的脚步, 所以我的上级领导提出这个方案说直接跳过他们,我们自己在本地测试,然后就找了它, 导入相关jar <dependency> ...

  5. PowerMock介绍

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

  6. PowerMock单元测试

    在Java程序的单元测试中常用的mock工具有Mockito和EasyMock.但是这两种mock工具都无法实现对静态.final.私有方法或类的mock.因此有了功能强大的PowerMock工具.P ...

  7. PowerMock学习之PoweMock的入门(二)

    前言 在上一篇<PowerMock学习之PoweMock的入门(一)>文章中,已经简单提及一些关于powermock的用法,但是入门还未完,我还要坚持把它学习并坚持更新到博客中. Mock ...

  8. PowerMock学习(六)之Mock Final的使用

    Mock Final mockfinal相对来说就比较简单了,使用powermock来测试使用final修饰的method或class,比较简单,接口调用部分,还是service调用dao. 对于接口 ...

  9. PowerMock框架讲解及使用

    为什么要使用PowerMock 现如今比较流行的Mock工具如jMock .EasyMock .Mockito等都有一个共同的缺点:不能mock静态.final.私有方法等.而PowerMock能够完 ...

随机推荐

  1. consonant_摩擦音

    consonant_摩擦音_[t∫].[dʒ].[tr].[dr].[ts].[dz] 破擦音:即有爆破音又有摩擦音. [t∫]:噘嘴,舌尖抵住上牙龈,舌头下切,用一瞬间的气流发出声音,不震动. ch ...

  2. consonant_摩擦音_咬舌音

    consonant_摩擦音_咬舌音_[θ]和[ð].[h] 咬舌音:咬住舌尖发音. [θ]:牙齿咬住舌尖,送气,气流摩擦发出声音,声带不振动: faith.thank.healthy.both.th ...

  3. python新手之字典增删改查

    一.字典的定义 city_list = { 'beijin':"北京",'shanghai':"上海" } print(city_list) 二.字典添加一个元 ...

  4. avast:中兴手机预装恶意软件 嵌入固件底层

    著名安全机构 avast 发布报告称,旗下安全威胁实验室发现,中兴.爱可视.MyPhone 等厂商的多款安卓手机居然预装了恶意广告软件.该恶意软件被命名为“ Cosiloon ”,它会在用户使用浏览器 ...

  5. Divisibility题解

    From lyh 学长 2018.5.3 信(liang)心(liang)杯T3 一道略弱的数论题. 题目描述 给定 n个数,问是否能从中选出恰好 k个数,使得这些数两两之差可以被 m 整除. 输入输 ...

  6. 单节锂电池充电(电路)芯片TP4056

  7. [Real World Haskell翻译]第24章 并发和多核编程 第一部分并发编程

    第24章 并发和多核编程 第一部分并发编程 当我们写这本书的时候,CPU架构正在以比过去几十年间更快的速度变化. 并发和并行的定义 并发程序需要同时执行多个不相关任务.考虑游戏服务器的例子:它通常是由 ...

  8. OpenCV代码提取:dft函数的实现

    The Fourier Transform will decompose an image into its sinus and cosines components. In other words, ...

  9. 1057: [ZJOI2007]棋盘制作

    1057: [ZJOI2007]棋盘制作 https://www.lydsy.com/JudgeOnline/problem.php?id=1057 分析: 首先对于(i+j)&1的位置0-& ...

  10. Java:break和continue关键字的作用

    二者的作用和区别 1. break:直接跳出当前循环体(while.for.do while)或程序块(switch).其中switch case执行时,一定会先进行匹配,匹配成功返回当前case的值 ...