JUnit单元测试5—PowerMock

当被测方法中存在依赖具体环境或上下文的外部方法调用(例如创建数据库链接),而这些上下文条件又很难预设时,该方法往往难以进行单元测试。
PowerMock可以通过修改类字节码,并使用自定义的ClassLoader加载运行,以模拟类或实例的方法,进而隔离被测方法对外部的依赖。

使用PowerMock的基本步骤:

  1. 添加对powermock开源类库的引用。
  2. 使用@RunWith(PowerMockRunner.class)注解测试类。
  3. 使用@PrepareForTest(使用模拟类的被测试类)注解测试类。
  4. 测试代码中使用PowerMockito工具设置需要被模拟的类及其行为。

软件准备

我们以Maven构建Java程序为例,只需在pom.xml中添加对PowerMock的依赖即可:

    <dependencies>
        <dependency>
            <groupId>org.powermock</groupId>
            <artifactId>powermock-module-junit4</artifactId>
            <version>2.0.2</version>
            <scope>test</scope>
        </dependency>
        <dependency>
            <groupId>org.powermock</groupId>
            <artifactId>powermock-api-mockito2</artifactId>
            <version>2.0.2</version>
            <scope>test</scope>
        </dependency>
    </dependencies>

本文后续以如下FileUtil作为被测试类,演示如何使用PowerMock:

import java.io.File;
import java.io.FileNotFoundException;

public class FileUtil {

    private static final String LOG_FILE = "root.log";

    public boolean mkdirs(String path) {
        log("start to mkdirs:" + path);
        File fileDir = new File(path);
        if (fileDir.exists()) {
            throw new IllegalArgumentException(path + "alreay exists!");
        } else {
            return fileDir.mkdirs();
        }
    }

    public File getLogFile() throws FileNotFoundException {
        log("start to get log file");
        String logPath = System.getenv("LOG_PATH");
        if (null == logPath) {
            throw new IllegalArgumentException("System env:LOG_PATH not exist!");
        }
        if (isExists(logPath)) {
            return new File(logPath + LOG_FILE);
        }
        throw new FileNotFoundException("File " + logPath + LOG_FILE + "not exist!");
    }

    private boolean isExists(String logPath) {
        return false;
    }

    public void log(String log) {
        System.out.println(log);
    }
}

模拟类

PowerMock支持在测试代码中模拟一个类的实例,当这个类的创建需要难以构造的条件时,模拟类将会很有帮助。
如下代码可获取一个模拟FileUtil类的实例,模拟公有方法部分也有演示如何使用fileUtil模拟类:
FileUtil fileUtil = PowerMockito.mock(FileUtil.class);

模拟构造方法

模拟构造函数同样可以创建一个模拟的类的实例,一般用于覆盖被测方法中的类的创建,使得被测方法中new出的对象可以被随意定制。
以下testMkdirs测试方法模拟了File类的new操作,并且模拟待创建目录不存在、目录创建操作能正确返回的场景。
这使得被测方法可以根据测试代码中设置模拟条件,按照测试人员期望的方式执行指定代码的条件分支。

import static org.junit.Assert.assertTrue;

import java.io.File;

import org.junit.Before;
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;

@RunWith(PowerMockRunner.class)
@PrepareForTest(FileUtil.class)
public class FileUtilMkdirTest {
    @Before
    public void init() throws Exception {
        File fileDir = PowerMockito.mock(File.class);
        PowerMockito.whenNew(File.class).withAnyArguments().thenReturn(fileDir);
        PowerMockito.when(fileDir.exists()).thenReturn(false);
        PowerMockito.when(fileDir.mkdirs()).thenReturn(true);
    }

    @Test
    public void testMkdirs() {
        assertTrue(new FileUtil().mkdirs("testDir"));
    }
}

说明:

  • FileUtilMkdirTest中只有FileUtil被测试类中使用到模拟类,只需将FileUtil类添加到注解中:
    @PrepareForTest(FileUtil.class)
  • 如果被测试类中还调用其他类型,且都使用到模拟类,需要将这些被测试类都添加到注解中:
    @PrepareForTest({FileUtil.class, FileUtil2.class})

模拟静态方法

Java仅提供获取环境变量的方法System.getenv,但不提供设置环境变量的方法System.setenv
本示例模拟了System.getenv方法,使得测试人员可以设置需要的环境变量。

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;

@RunWith(PowerMockRunner.class)
@PrepareForTest(FileUtil.class)
public class FileUtilGetLogFileTest {
    @Test
    public void testGetLogFileWithException() {
        PowerMockito.mockStatic(System.class);
        PowerMockito.when(System.getenv("LOG_PATH")).thenReturn("./");
        try {
            new FileUtil().getLogFile();
            fail();
        } catch (FileNotFoundException e) {
            assertTrue(true);
        }
    }
}

模拟公有、私有方法

本示例为测试类FileUtilGetLogFileTest添加了两个测试方法,用于模拟FileUtil的公有和私有方法。

  • 有返回值
    如下testGetLogFile方法模拟了FileUtil类的私有方法isExists,并让其返回true
    @Test
    public void testGetLogFile() throws Exception {
        PowerMockito.mockStatic(System.class);
        PowerMockito.when(System.getenv("LOG_PATH")).thenReturn("./");

        FileUtil fileUtil = PowerMockito.spy(new FileUtil());
        PowerMockito.when(fileUtil, "isExists", "./").thenReturn(true);

        File logFile = fileUtil.getLogFile();
        assertTrue(logFile != null);
    }
  • 无返回值
    如下testGetLogFileWithoutLog方法额外模拟了FileUtil类的无返回值的公有方法log,让其什么都不做(不打印日志):
    @Test
    public void testGetLogFileWithoutLog() throws Exception {
        PowerMockito.mockStatic(System.class);
        PowerMockito.when(System.getenv("LOG_PATH")).thenReturn("./");

        FileUtil fileUtil = PowerMockito.spy(new FileUtil());
        PowerMockito.when(fileUtil, "isExists", "./").thenReturn(true);
        PowerMockito.doNothing().when(fileUtil, "log", Mockito.anyString());

        File logFile = fileUtil.getLogFile();
        assertTrue(logFile != null);
    }

说明:
如果需要模拟方法抛出异常,需要被测方法声明抛出异常,否则不能实现。
针对有、无返回值,可以采用以下两种方式:

  • 有返回值:PowerMockito.when(fileUtil, "getLogFile", "./").thenThrow(new FileNotFoundException());
  • 无返回值:PowerMockito.doThrow(new Exception()).when(fileUtil, "xxx");

上一篇:JUnit单元测试4—模拟Web服务器
下一篇:JUnit单元测试6—@Rule注解

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 215,012评论 6 497
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 91,628评论 3 389
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 160,653评论 0 350
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 57,485评论 1 288
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 66,574评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,590评论 1 293
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,596评论 3 414
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,340评论 0 270
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,794评论 1 307
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,102评论 2 330
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,276评论 1 344
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,940评论 5 339
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,583评论 3 322
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,201评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,441评论 1 268
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,173评论 2 366
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,136评论 2 352

推荐阅读更多精彩内容