EasyMock 以及 Mockito 都因为可以极大地简化单元测试的书写过程而被许多人应用在自己的工作中,但是这 2 种 Mock 工具都不可以实现对静态函数、构造函数、私有函数、Final 函数以及系统函数的模拟,但是这些方法往往是我们在大型系统中需要的功能。PowerMock 是在 EasyMock 以及 Mockito 基础上的扩展,通过定制类加载器等技术,PowerMock 实现了之前提到的所有模拟功能,使其成为大型系统上单元测试中的必备工具。

单元测试模拟框架的功能及其实现简介

单元测试在软件开发过程中的重要性不言而喻,特别是在测试驱动开发的开发模式越来越流行的前提下,单元测试更成为了软件开发过程中不可或缺的部分。于是相应的,各种单元测试技术也应运而生。本文要介绍的 PowerMock 以及 Mockito 都是简化单元测试书写过程的工具。

Mockito 是一个针对 Java 的单元测试模拟框架,它与 EasyMock 和 jMock 很相似,都是为了简化单元测试过程中测试上下文 ( 或者称之为测试驱动函数以及桩函数 ) 的搭建而开发的工具。在有这些模拟框架之前,为了编写某一个函数的单元测试,程序员必须进行十分繁琐的初始化工作,以保证被测试函数中使用到的环境变量以及其他模块的接口能返回预期的值,有些时候为了单元测试的可行性,甚至需要牺牲被测代码本身的结构。单元测试模拟框架则极大的简化了单元测试的编写过程:在被测试代码需要调用某些接口的时候,直接模拟一个假的接口,并任意指定该接口的行为。这样就可以大大的提高单元测试的效率以及单元测试代码的可读性。

相对于 EasyMock 和 jMock,Mockito 的优点是通过在执行后校验哪些函数已经被调用,消除了对期望行为(expectations)的需要。其它的 mocking 库需要在执行前记录期望行为(expectations),而这导致了丑陋的初始化代码。

但是,Mockito 也并不是完美的,它不提供对静态方法、构造方法、私有方法以及 Final 方法的模拟支持。而程序员时常都会发现自己有对以上这些方法的模拟需求,特别是当一个已有的软件系统摆在面前时。幸好 , 还有 PowerMock。

PowerMock 也是一个单元测试模拟框架,它是在其它单元测试模拟框架的基础上做出的扩展。通过提供定制的类加载器以及一些字节码篡改技巧的应用,PowerMock 现了对静态方法、构造方法、私有方法以及 Final 方法的模拟支持,对静态初始化过程的移除等强大的功能。因为 PowerMock 在扩展功能时完全采用和被扩展的框架相同的 API, 熟悉 PowerMock 所支持的模拟框架的开发者会发现 PowerMock 非常容易上手。PowerMock 的目的就是在当前已经被大家所熟悉的接口上通过添加极少的方法和注释来实现额外的功能,目前,PowerMock 仅支持 EasyMock 和 Mockito。

本文的目的就是和大家一起学习在 Mockito 框架上扩展的 PowerMock 的强大功能。

环境配置方法

对于需要的开发包,PowerMock 网站提供了”一站式”下载 : 从 此页面中选择以类似 PowerMock 1.4.10 with Mockito and JUnit including dependencies 为注释的链接,该包中包含了最新的 JUnit 库,Mockito 库,PowerMock 库以及相关的依赖。

如果是使用 Eclipse 开发,只需要在 Eclipse 工程中包含这些库文件即可。

如果是使用 Maven 开发,则需要根据版本添加以下清单内容到 POM 文件中:

JUnit 版本 4.4 以上请参考清单 1,

清单 1
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
<properties>
   <powermock.version>1.4.10</powermock.version>
</properties>
<dependencies>
  <dependency>
     <groupId>org.powermock</groupId>
     <artifactId>powermock-module-junit4</artifactId>
     <version>${powermock.version}</version>
     <scope>test</scope>
  </dependency>
  <dependency>
     <groupId>org.powermock</groupId>
     <artifactId>powermock-api-mockito</artifactId>
     <version>${powermock.version}</version>
     <scope>test</scope>
  </dependency>
</dependencies>

JUnit 版本 4.0-4.3 请参考清单 2,

清单 2
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
<properties>
   <powermock.version>1.4.10</powermock.version>
</properties>
<dependencies>
  <dependency>
     <groupId>org.powermock</groupId>
     <artifactId>powermock-module-junit4-legacy</artifactId>
     <version>${powermock.version}</version>
     <scope>test</scope>
  </dependency>
  <dependency>
     <groupId>org.powermock</groupId>
     <artifactId>powermock-api-mockito</artifactId>
     <version>${powermock.version}</version>
     <scope>test</scope>
  </dependency>
</dependencies>

JUnit 版本 3 请参考清单 3,

清单 3
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
<properties>
   <powermock.version>1.4.10</powermock.version>
</properties>
<dependencies>
  <dependency>
     <groupId>org.powermock</groupId>
     <artifactId>powermock-module-junit3</artifactId>
     <version>${powermock.version}</version>
     <scope>test</scope>
  </dependency>
  <dependency>
     <groupId>org.powermock</groupId>
     <artifactId>powermock-api-mockito</artifactId>
     <version>${powermock.version}</version>
     <scope>test</scope>
  </dependency>
</dependencies>

PowerMock 在单元测试中的应用

模拟 Static 方法

在任何需要用到 PowerMock 的类开始之前,首先我们要做如下声明:

@RunWith(PowerMockRunner.class)

然后,还需要用注释的形式将需要测试的静态方法提供给 PowerMock:

@PrepareForTest( { YourClassWithEgStaticMethod.class })

然后就可以开始写测试代码:

首先,需要有一个含有 static 方法的代码 , 如清单 4:

清单 4
1
2
3
4
5
6
7
8
9
10
public class IdGenerator {
 
   ...
    
   public static long generateNewId() {
       ...
   }
    
   ...
}

然后,在被测代码中,引用了以上方法 , 如清单 5 所示:

清单 5
1
2
3
4
5
6
7
8
9
public class ClassUnderTest {
   ...
   public void methodToTest() {
       ..
       final long id = IdGenerator.generateNewId();
       ..
    }
   ...
}

为了达到单元测试的目的,需要让静态方法 generateNewId()返回各种值来达到对被测试方法 methodToTest()的覆盖测试,实现方式如清单 6 所示:

清单 6
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
@RunWith(PowerMockRunner.class)
//We prepare the IdGenerator for test because the static method is normally not mockable
@PrepareForTest(IdGenerator.class)
public class MyTestClass {
   @Test
   public void demoStaticMethodMocking() throws Exception {
       mockStatic(IdGenerator.class);
       /*
        * Setup the expectation using the standard Mockito syntax,
        * generateNewId() will now return 2 everytime it's invoked
        * in this test.
        */
       when(IdGenerator.generateNewId()).thenReturn(2L);
 
       new ClassUnderTest().methodToTest();
 
       // Optionally verify that the static method was actually called
       verifyStatic();
       IdGenerator.generateNewId();
   }
}

如清单 6 中所展示,在测试代码中,可以使用 When().thenReturn() 语句来指定被引用的静态方法返回任意需要的值,达到覆盖测试的效果。

模拟构造函数

有时候,能模拟构造函数,从而使被测代码中 new 操作返回的对象可以被随意定制,会很大程度的提高单元测试的效率,考虑如清单 7 的代码:

清单 7
1
2
3
4
5
6
7
8
9
10
11
12
public class DirectoryStructure {
   public boolean create(String directoryPath) {
       File directory = new File(directoryPath);
 
       if (directory.exists()) {
           throw new IllegalArgumentException(
           "\"" + directoryPath + "\" already exists.");
       }
 
       return directory.mkdirs();
   }
}

为了充分测试 create()函数,我们需要被 new 出来的 File 对象返回文件存在和不存在两种结果。在 PowerMock 出现之前,实现这个单元测试的方式通常都会需要在实际的文件系统中去创建对应的路径以及文件。然而,在 PowerMock 的帮助下,本函数的测试可以和实际的文件系统彻底独立开来:使用 PowerMock 来模拟 File 类的构造函数,使其返回指定的模拟 File 对象而不是实际的 File 对象,然后只需要通过修改指定的模拟 File 对象的实现,即可实现对被测试代码的覆盖测试,参考如清单 8 的代码:

清单 8
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
@RunWith(PowerMockRunner.class)
@PrepareForTest(DirectoryStructure.class)
public class DirectoryStructureTest {
   @Test
   public void createDirectoryStructureWhenPathDoesntExist() throws Exception {
       final String directoryPath = "mocked path";
 
       File directoryMock = mock(File.class);
 
       // This is how you tell PowerMockito to mock construction of a new File.
       whenNew(File.class).withArguments(directoryPath).thenReturn(directoryMock);
 
       // Standard expectations
       when(directoryMock.exists()).thenReturn(false);
       when(directoryMock.mkdirs()).thenReturn(true);
 
       assertTrue(new NewFileExample().createDirectoryStructure(directoryPath));
 
       // Optionally verify that a new File was "created".
       verifyNew(File.class).withArguments(directoryPath);
   }
}

使用 whenNew().withArguments().thenReturn() 语句即可实现对具体类的构造函数的模拟操作。然后对于之前创建的模拟对象 directoryMock使用 When().thenReturn() 语句,即可实现需要的所有功能,从而实现对被测对象的覆盖测试。在本测试中,因为实际的模拟操作是在类 DirectoryStructureTest 中实现,所以需要指定的 @PrepareForTest 对象是 DirectoryStructureTest.class

模拟私有以及 Final 方法

为了实现对类的私有方法或者是 Final 方法的模拟操作,需要 PowerMock 提供的另外一项技术:局部模拟。

在之前的介绍的模拟操作中,我们总是去模拟一整个类或者对象,然后使用 When().thenReturn()语句去指定其中值得关心的部分函数的返回值,从而达到搭建各种测试环境的目标。对于没有使用 When().thenReturn()方法指定的函数,系统会返回各种类型的默认值(具体值可参考官方文档)。

局部模拟则提供了另外一种方式,在使用局部模拟时,被创建出来的模拟对象依然是原系统对象,虽然可以使用方法 When().thenReturn()来指定某些具体方法的返回值,但是没有被用此函数修改过的函数依然按照系统原始类的方式来执行。

这种局部模拟的方式的强大之处在于,除开一般方法可以使用之外,Final 方法和私有方法一样可以使用。

参考如清单 9 所示的被测代码:

清单 9
1
2
3
4
5
6
7
8
9
public final class PrivatePartialMockingExample {
   public String methodToTest() {
       return methodToMock("input");
   }
 
   private String methodToMock(String input) {
       return "REAL VALUE = " + input;
   }
}

为了保持单元测试的纯洁性,在测试方法 methodToTest()时,我们不希望受到私有函数 methodToMock()实现的干扰,为了达到这个目的,我们使用刚提到的局部模拟方法来实现 , 实现方式如清单 10:

清单 10
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
@RunWith(PowerMockRunner.class)
@PrepareForTest(PrivatePartialMockingExample.class)
public class PrivatePartialMockingExampleTest {
   @Test
   public void demoPrivateMethodMocking() throws Exception {
       final String expected = "TEST VALUE";
       final String nameOfMethodToMock = "methodToMock";
       final String input = "input";
 
       PrivatePartialMockingExample underTest = spy(new PrivatePartialMockingExample());
 
       /*
        * Setup the expectation to the private method using the method name
        */
       when(underTest, nameOfMethodToMock, input).thenReturn(expected);
 
       assertEquals(expected, underTest.methodToTest());
 
       // Optionally verify that the private method was actually called
       verifyPrivate(underTest).invoke(nameOfMethodToMock, input);
   }
}

可以发现,为了实现局部模拟操作,用来创建模拟对象的函数从 mock() 变成了 spy(),操作对象也从类本身变成了一个具体的对象。同时,When() 函数也使用了不同的版本:在模拟私有方法或者是 Final 方法时,When() 函数需要依次指定模拟对象、被指定的函数名字以及针对该函数的输入参数列表。

结束语

以上列举了扩展于 Mockito 版本的 PowerMock 的一部分强大的功能,特别是针对已有的软件系统,利用以上功能可以轻易的完成清晰独立的单元测试代码,帮助我们提高代码质量。

PowerMock的更多相关文章

  1. Mock之easymock, powermock, and mockito

    easymock, powermock, and mockito Easymock Class Mocking Limitations To be coherent with interface mo ...

  2. 用PowerMock mock 由工厂方法产生的对象

    有些对象需要mock的对象是由工厂方法产生出来的,而工厂方法一般是静态方法,这时候就需要同时mock工厂方法及对象 被测方法: public class EmployeeServiceFactory ...

  3. 用PowerMock spy mock private方法

    在实际的工作中,经常碰到只需要mock一个类的一部分方法,这时候可以用spy来实现. 被测类: public class EmployeeService { public boolean exist( ...

  4. 用PowerMock mock static方法

    在编写代码时,经常需要调用别人已经写好的工具类,而这些工具提供的方法经常是static方法,在这里,直接贴出<PowerMock实战手册>中的例子 待测试方法: public class ...

  5. 用PowerMock mock 临时变量

    在开发时,经常遇到这种情况,被测试方法所调用的类不是通过构造注入的,而是通过临时new出来的,如下面待测方法: public class EmployeeService { private Emplo ...

  6. PowerMock遇到的问题——5

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

  7. 用maven搭建 testNG+PowerMock+Mockito测试框架

    单元测试是开发中必不可少的一部分,是产品代码的重要保证. Junit和testNG是当前最流行的测试框架,Junit是使用最广泛的测试框架,有兴趣的话自己baidu一下. testNG基于Junit和 ...

  8. PowerMock 简介--转载

    原文地址:https://www.ibm.com/developerworks/cn/java/j-lo-powermock/ EasyMock 以及 Mockito 都因为可以极大地简化单元测试的书 ...

  9. PowerMock遇到的问题——4

    当我们在测试一个方法的构造方法的时候,有的时候内部需要new一些对象,这是就需要用到PowerMock.exceptNew(),这个方法,但有时候传的参数有关键字this,比如SAPPublisher ...

  10. PowerMock 遇到的问题——2

    如果我们要测试的类继承另一个类,而且在构造方法中有super():那么怎么Mock那个super语句呢? 在PowerMock中有一个suppressConstructor方法,具体写法如下: Pow ...

随机推荐

  1. STM32CubeMx配置SPI注意的一个问题

    这样配置SPI引脚 然后这样配置SPI参数 生成立这样的配置代码 /* SPI2 init function */static void MX_SPI2_Init(void){ /* SPI2 par ...

  2. 使用virtual安装Windows系列操作系统总结

    最近在安装Windows操作系统的过程中,发现总是报错,无法安装成功,后来经过不断地摸索,发现根本的问题在于镜像,所以在以后的大文件传输下载后,一定要校验其MD5值是否与源文件一致,需要的朋友可以联系 ...

  3. 【GUI】一、Swing外观框架BeautyEye使用

    一.Swing外观框架BeautyEye使用 1.1 导包 BeautyEye.jar 1.2 使用BeautyEye L&F public static void main(String[] ...

  4. ESP32 LyraT音频开发板试玩(二):播放音乐

    我是卓波,很高兴你来看我的博客. 系列文章: ESP32 LyraT音频开发板试玩(一):搭建开发环境 ESP32 LyraT音频开发板试玩(二):播放音乐 本文延续上一篇博客 将D:\msys32\ ...

  5. node 分层开发

    app.js var express = require('express');var app = express();app.use('/',require('./control'));app.us ...

  6. Hibernate-ORM:15.Hibernate中的Criteria查询

    ------------吾亦无他,唯手熟尔,谦卑若愚,好学若饥------------- 本篇博客讲师Hibernate中的Criteria查询! 一,Criteria简介: 刚接触Hibernate ...

  7. 使用PSSH批量操作Linux服务器

    简介 服务器多了,有一个问题就是如何批量快速操作多台服务器,在网上搜到了PSSH工具,试用了一下发现挺好用,推荐给大家. pssh是一个python编写的可以在多台服务器上执行命令的轻量级管理工具,同 ...

  8. bug单的提交

    顶头信息 所属产品,所属项目,所属模块,影响版本,当前指派,bug类型:代码错误,界面优化,设计缺陷,性能问题,标准规范,其他,安全相关.bug标题,严重程度,优先级 缺陷描述 bug描述,预置条件, ...

  9. Tensorflow编程基础之Mnist手写识别实验+关于cross_entropy的理解

    好久没有静下心来写点东西了,最近好像又回到了高中时候的状态,休息不好,无法全心学习,恶性循环,现在终于调整的好一点了,听着纯音乐突然非常伤感,那些曾经快乐的大学时光啊,突然又慢慢的一下子出现在了眼前, ...

  10. 团队项目-第五次Scrum 会议

    时间:10.31 时长:30分钟 地点:教室(主南201) 工作情况 团队成员 已完成任务 待完成任务 解小锐 修复在接受任务时,前端和后端对接中的bug 完成员工信息的简单初始化 陈鑫 完成hire ...