`

PowerMock 简介

阅读更多
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

<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

<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

<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

public class IdGenerator {

    ...
   
    public static long generateNewId() {
        ...
    }
   
    ...
}

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

清单 5

public class ClassUnderTest {
    ...
    public void methodToTest() {
        ..
        final long id = IdGenerator.generateNewId();
        ..
     }
    ...
}

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

清单 6

@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

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

@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

public final class PrivatePartialMockingExample {
    public String methodToTest() {
        return methodToMock("input");
    }

    private String methodToMock(String input) {
        return "REAL VALUE = " + input;
    }
}

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

清单 10

@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 的一部分强大的功能,特别是针对已有的软件系统,利用以上功能可以轻易的完成清晰独立的单元测试代码,帮助我们提高代码质量。
回页首
注:
本文中的部分测试代码引用自 Johan Haleby 的 Untestable code with Mockito and PowerMock。

参考资料
学习
Mockito 官方网站:Mockito 的官方站点 , 在这里你可以找到完整的 Mockito 介绍、接口文档,以及一些代码示例。

PowerMock 官方网站:PowerMock 的官方站点 , 在这里你可以找到完整的 PowerMock 介绍、接口文档,以及一些代码示例。

developerWorks Java 技术专区:这里有数百篇关于 Java 编程各个方面的文章。

讨论
加入 developerWorks 中文社区。查看开发人员推动的博客、论坛、组和维基,并与其他 developerWorks 用户交流。

作者简介
张羽是一位拥有 6 年从业经验的软件开发人员。在最近的 4 年里,他从事于 Java 和 C++ 的开发工作。
吴长侠工作在国际商业机器(中国)投资有限公司(IBM China)中国系统和技术实验室。中国科学技术大学硕士,目前是 IBM Systems Director 的测试组成员。
分享到:
评论

相关推荐

    UT总结(为完成版)

    #### 一、PowerMock简介及结构 **PowerMock** 是一个强大的Java单元测试工具,它扩展了JUnit的功能,使得开发者可以对静态方法、构造函数、final类/方法、私有方法、删除的方法以及由CGLIB支持的方法进行模拟和重...

    powermock-demo:powermock演示

    5. **README.md**:项目简介和使用指南,可能会有如何运行测试的说明。 在实际使用 PowerMock 进行测试时,我们需要注意以下几点: 1. **使用注解**:在测试类上使用 `@RunWith(PowerMockRunner.class)` 注解,以...

    Mockito Cookbook - Packt

    #### 一、Mockito框架简介 - **定义**:Mockito是一款Java测试框架,用于轻松创建被测系统的协作者(即与被测对象交互的对象)的模拟对象(mocks)。通过这些模拟对象可以更好地在隔离环境下模拟软件的行为模式,并...

    moco-runner-0.10.2-standalone.jar

    - **PowerMock**:如果需要模拟静态方法、final类或构造器,可以结合PowerMock扩展Mockito的功能。 总结,Mockito框架通过提供`moco-runner-0.10.2-standalone.jar`这样的独立运行包,使得Java开发者能够轻松地...

    myeclipse for spring 8.6

    - 支持Mockito、PowerMock等模拟框架,便于编写隔离测试。 #### 5. 其他功能 - 提供了代码质量检查工具,如FindBugs、Checkstyle等,帮助开发者发现潜在的问题。 - 支持Tomcat、Jetty等多种Web服务器的部署,方便...

    软件测试 教材 教程

    - 其他测试框架:除了Junit,还有TestNG、Selenium等自动化测试工具,以及Mockito、PowerMock等模拟框架的应用。 - 集成环境:如何在Eclipse、IntelliJ IDEA等IDE中配置和运行测试,以及持续集成工具如Jenkins的...

    Junit sourcecode

    1. JUnit简介: JUnit是一个开源的、基于Java的单元测试框架,它使得编写和运行测试用例变得简单易行。JUnit4引入了注解(Annotation)和参数化测试,大大增强了其灵活性和可扩展性。后续版本JUnit5更是带来了模块化...

    jmock-2.4.0-jars.zip

    一、jMock简介 jMock是基于Java的测试工具,它的核心思想是通过模拟对象的行为来隔离被测试代码,使测试更为精确。jMock 2.4.0版本是对该框架的一次更新,它提供了更强大的功能和改进,使得开发者能够更好地控制和...

    junit4教程(《Junit4初探》)

    ## 一、JUnit4简介 JUnit4是JUnit系列的第四代产品,它引入了注解(Annotations)和断言(Assertions)等新特性,使得测试代码更加简洁易读。与JUnit3相比,JUnit4的灵活性和可扩展性得到了显著提升,使得测试驱动...

    Junit测试 简单用例

    一、Junit简介 Junit是由Ernst Berg和Kent Beck共同创建的开源项目,其主要目标是提供一个易于使用的接口来编写针对Java代码的测试用例。Junit支持注解(annotations)、断言(assertions)以及测试套件(test ...

    junit4单元测试实例

    一、JUnit4简介 JUnit4相较于之前的版本,引入了注解(Annotation)机制,极大地简化了测试用例的编写。例如,`@Test`注解用于标记测试方法,`@Before`和`@After`则分别表示在每个测试方法前和后执行的设置和清理...

    Junit学习笔记

    一、JUnit简介 JUnit是开源项目,由Ernst Konig和Kent Beck发起,最初是针对Java平台设计的。现在,JUnit已经成为Java开发者进行单元测试的事实标准。通过JUnit,我们可以编写简洁、易于理解的测试代码,并且可以...

    JUnit Recipes

    - **使用测试数据生成器**:如Mockito或PowerMock等工具可以帮助生成测试数据。 - **外部化测试数据**:将测试数据存储在外部文件中,便于管理和复用。 **2.4 运行JUnit测试** - **命令行方式**:可以在命令行下...

    MyProject

    8. **测试框架**:项目可能会使用JUnit、TestNG等进行单元测试,有时还会结合Mockito、PowerMock等库来模拟对象或方法。 9. **持续集成/持续部署(CI/CD)**:可能包含Jenkinsfile、.travis.yml或.circleci/config....

    Software-quality-and-testing:软件质量和测试

    测试,简介 转到文件夹然后按照那里的步骤进行操作 课程2。示例项目设置,第一个单元测试 转到文件夹然后按照那里的步骤进行操作 课程3.示例项目设置,第一个单元测试 转到文件夹并按照那里的步骤 测试 即将公布! ...

    Bartosz_-Switalski-kodilla_tester

    5. **README.md**:提供项目简介、安装指南、使用示例和贡献指南等信息。 6. **LICENSE**:说明项目使用的开源许可协议。 7. **.gitignore**:列出在Git版本控制中忽略的文件和目录。 8. **.travis.yml**或类似的CI/...

    朱尼特

    一、朱尼特简介 朱尼特最初由Ernst Konrad和Kent Beck开发,后来成为开源社区广泛使用的工具,其最新版本已经发展到了JUnit 5,包括JUnit Platform、JUnit Jupiter和JUnit Vintage三个主要部分。JUnit 5的出现使得...

    tdd-fastcampus

    3. **README.md**:可能包含项目简介、安装指南、如何运行测试等信息,帮助用户理解并参与到项目中。 4. **.gitignore**:定义了版本控制系统Git应该忽略的文件或目录,通常会包含编译产生的临时文件和日志等。 5....

Global site tag (gtag.js) - Google Analytics