mockito 已经很强大,能帮我们完成大部分 mock 工作,但是对于一些特殊方法来说,还是无能为力。

例如,当我们使用系统获取当前时间戳的时候,可能会调用 System.currentTimeMillis()。对于这个方法来说,我们无法 mock。往往就会遇到一个有趣的现象,一些测试过了一段时间就通不过了,项目中可能有对时间进行检查的逻辑。

我们有一个项目是做财务报销单,当费用产生后的几个月后报销就会失败,我们的模拟数据是一个固定的时间,因此几个月后重新运行这个项目的单元测试就不通过了。

另外,项目中不可避免的需要 mock 系统静态方法、私有方法;以及对一些私有方法进行测试,虽然不推荐测试私有方法,但是如果遇到遗留系统,public 方法有时候巨大,测试的成本非常高。

配合 mockito 使用的另外一个框架是 powermock。它采用了字节码技术,可以增强测试,解决 mock 静态、私有方法,以及必要时测试静态、私有方法。

为了便于演示,我给上一部分的例子中的 User 对象增加了 createAt 字段,createAt 字段在 register 方法内被填充,然后进行持久化。

更新后的 User 对象如下:

public class User {

private String email;

private String username;

private String password;

private Instant createAt;

public User(String email, String username, String password, Instant createAt) {

this.email = email;

this.username = username;

this.password = password;

this.createAt = createAt;

}

...

}

并给 user 添加对应的值,Instant.now() 调用的系统方法。

user.setCreateAt(Instant.now());

按照我们前面的测试,这样会给测试带来不变,因此需要想办法 mock 掉 Instant.now 这个方法。

assertEquals("", argument.getValue().getCreateAt());

首先,引入 powermock 的相关依赖。powermock 有两个模块,一个是封装 junit、另外一个是封装 mockito,间接的依赖了 junit 和 mockito。因此可以先把原来的测试依赖移除,添加下面的两个依赖即可。

org.powermock

powermock-module-junit4

2.0.2

test

org.powermock

powermock-api-mockito2

2.0.2

test

然后,使用 PowerMockRunner 代替 mockito 的 runner。并使用 @PrepareForTest 对用到该静态方法的地方进行初始化。

@RunWith(PowerMockRunner.class)

@PrepareForTest(UserService.class)

在测试过程中,我们就可以 mock Instant.class 中的静态方法,并且会影响 UserService 中使用它的地方。

Instant moment = Instant.ofEpochSecond(1596494464);

PowerMockito.mockStatic(Instant.class);

PowerMockito.when(Instant.now()).thenReturn(moment);

Instant.now() 就会按照我们期望的值返回结果,当然也可以按照时间戳 1596494464 来断言了,完整的测试如下:

@RunWith(PowerMockRunner.class)

// 比如使用 PrepareForTest 让 mock 在被测试代码中生效

@PrepareForTest({UserService.class})

public class UserServiceAnnotationTest {

@Mock

UserRepository mockedUserRepository;

@Mock

EmailService mockedEmailService;

@Spy

EncryptionService mockedEncryptionService = new EncryptionService();

@InjectMocks

UserService userService;

@Test

public void should_register() {

// mock 前生成一个 Instant 实例

Instant moment = Instant.ofEpochSecond(1596494464);

// mock 并设定期望返回值

PowerMockito.mockStatic(Instant.class);

PowerMockito.when(Instant.now()).thenReturn(moment);

// given

User user = new User("admin@test.com", "admin", "xxx", null);

// when

userService.register(user);

// then

verify(mockedEmailService).sendEmail(

eq("admin@test.com"),

eq("Register Notification"),

eq("Register Account successful! your username is admin"));

ArgumentCaptor argument = ArgumentCaptor.forClass(User.class);

verify(mockedUserRepository).saveUser(argument.capture());

assertEquals("admin@test.com", argument.getValue().getEmail());

assertEquals("admin", argument.getValue().getUsername());

assertEquals("cd2eb0837c9b4c962c22d2ff8b5441b7b45805887f051d39bf133b583baf6860", argument.getValue().getPassword());

assertEquals(moment, argument.getValue().getCreateAt());

}

}

除了入门的基本使用外,powermock 还有一些需要特别注意的点,可以避免在实际项目中碰到的问题。

@PrepareForTest 中的参数,也就是一个需要被准备的类。这个类不是被 Mock 的类本身。例如上面一个例子中,我们需要 Mock 的是 Instant.now(),这个方法在 UserService 中被使用,我们需要 prepare UserService 而不是 Instant。这是 powermock 在使用中最常见的一个错误,就其原因是 静态方法是类级别的方法,需要在类被加载前准备完毕。

具体的实现是在 PowerMockRunner 中完成的,其中用了很多字节码级别的技术,可以不用关心具体实现。

上面的例子中,我们不需要验证 Instant.now 的调用情况。在有些情况下,对静态方法也需要验证。

可以使用 PowerMockito 的 verifyStatic 方法,然后直接调用被 verify 的静态方法即可。

PowerMockito.verifyStatic(Static.class);

Static.thirdStaticMethod(Mockito.anyInt());

需要注意的是 verifyStatic 需要每次验证都调用,这两句代码为成对出现的。

有时候被测试的代码中可能会直接使用 new 关键字创建一个对象,这种情况下就不太好隔离被创建的对象。如果不使用 powermock 甚至这段代码不能被测试,有两个途径来解决。一个是使用使用工厂方法或者依赖注入代替直接使用 new 关键字,进行解耦;另一种方式是,使用 powermock 对构造方法进行 mock。

在 powermock 中使用 whenNew 这个方法,可以拦截构造方法的调用,而直接返回其他对象或者异常。构造方法的 mock 是 powermock 中最常用的特性之一。

@RunWith(PowerMockRunner.class)

@PrepareForTest(X.class)

public class XTest {

@Test

public void test() {

whenNew(MyClass.class).withNoArguments().thenThrow(new IOException("error message"));

X x = new X();

x.y(); // y is the method doing "new MyClass()"

..

}

}

和前面的问题类似,在做一些重构时,我们发现类中有一些特别长的私有方法,这些私有方法比较复杂带来的测试成本很高。

一种方式是,重构这些私有方法到另外一个类中,保持类的私有方法处于较少的状态。另外一种是通过 powermock 对私有方法进行 mock。使用 powermock mock 私有方法非常简单,只需要 PowerMockito.when() 方法即可,因为私有方法直接调用会有 java 语法报错,因此when 方法提供了通过方法名 mock 的重载。

假如 doSendEmockedEmailService 对象中有一个私有方法 doSendEmail,下面的示例代码演示了 powermock mock 私有方法的例子。

PowerMockito.when(mockedEmailService, "doSendEmail").thenReturn(true));

在使用 Spring 等一些框架时,会使用 @Value 为对象属性注入值,但是往往这个属性是私有的。为了测试方便,mockito 提供了响应的工具。

早期的工具使用了 whitebox 类,提供统一的反射操作方法,因为 whitebox 过于开放,在后期的版本中不推荐使用了。

一般我们在项目中用的比较多的是 FieldSetter.setField 这个 API,可以比较优雅的解决这个问题。

@Value

private String template;

FieldSetter.setField(

mockedEmailService,EmailService.class.getDeclaredField("template"),

"test template"

);

一般来说,单元测试会测试 public 方法,如果被测试代码不合理时,通常的做法是修改被测试代码。让代码具备可测试性非常重要,也能让代码变得更加整洁。

如果我们遇到私有方法,但是想要测试有两个方法。

一种比较好的方法是将私有方法修改为包级别私有,然后将测试代码放到同一个包下,但是处于 test 目录下 (src/main/java 和 src/test/java 的关系),这样测试代码就能访问到该方法。

另外的方法是,使用一些辅助工具,例如 mockito 的 Whitebox 类,可以提供对私有方法、属性的访问。使用 Whitebox 可以在必要时增强测试能力。

Whitebox.invokeMethod(testObj, "method1", new Long(10L));

如果使用了 SpringTest 还可以使用 ReflectionTestUtils 类来完成:

ReflectionTestUtils.invokeMethod(student, "saveOrUpdate", "From Unit test");

实际工作中,被测试代码不一定能非常容易的被 mock 和测试。让代码具有很好的测试性,实际开发过程中非常重要的一件事。

当我们确实需要对私有方法做 mock 和 测试时,可以借助其他方法和工具:使用 powermock 对私有方法、属性进行 mock 和验证

使用包级别可访问的策略对私有方法进行改造,使其可被测试

使用反射工具,例如 Whitebox、ReflectionTestUtils、FieldSetter 访问私有属性和方法

powermockito测试私有方法_03 增强测试: 静态、私有方法处理相关推荐

  1. 【错误记录】Groovy 扩展方法调用报错 ( 静态扩展方法 或 实例扩展方法 需要分别配置 | 没有配置调用会报错 groovy.lang.MissingMethodException )

    文章目录 一.报错信息 二.解决方案 一.报错信息 定义 Thread 扩展方法 , 下面的扩展方法 class ThreadExt {public static Thread hello(Threa ...

  2. 【Groovy】Groovy 扩展方法 ( 静态扩展方法配置 | 扩展方法示例 | 编译静态扩展类 | 打包静态扩展类字节码到 jar 包中 | 测试使用 Thread 静态扩展类 )

    文章目录 一.扩展方法示例 二.静态扩展方法配置 三.编译静态扩展类 四.打包静态扩展类字节码到 jar 包中 五.测试使用 Thread 静态扩展类 一.扩展方法示例 为 Thread 扩展 hel ...

  3. 如何设计高效测试用例_高效的企业测试-单元和用例测试(2/6)

    如何设计高效测试用例 在本系列的第一部分中,我们看到了有效测试应满足的一些普遍适用的原则和约束. 在这一部分中,我们将仔细研究代码级单元测试和组件或用例测试. 单元测试 单元测试验证单个单元(通常是类 ...

  4. java 静态缓存示例_Java 9 JShell示例:集合静态工厂方法

    java 静态缓存示例 这篇文章继续从My My Java 9 Features博客文章中探索Java9功能. 在这里,我们在List,Set和Map接口中试验Java9 Collections静态工 ...

  5. 高效的企业测试-单元和用例测试(2/6)

    在本系列的第一部分中,我们看到了有效测试应满足的一些普遍适用的原则和约束. 在这一部分中,我们将仔细研究代码级单元测试以及组件或用例测试. 单元测试 单元测试验证单个单元(通常是类)的行为,而忽略或模 ...

  6. Java 9 JShell示例:集合静态工厂方法

    这篇文章继续了My My Java 9 Features博客文章中对Java9功能的探索. 在这里,我们在List,Set和Map接口中试验Java9 Collections静态工厂方法. 集合静态工 ...

  7. 使用静态工厂方法而不是构造器

    注意:静态工厂方法不是设计模式中的工厂方法. 一个类向客户端提供静态工厂方法有如下好处: 有名称,不用根据参数类型和顺序区分重载方法,让代码更易读 是否每次调用都需要新对象是可控制的,对于不可修改的对 ...

  8. Java 静态工厂方法详解

    Java 静态工厂方法详解 本文章主要是对<Effective Java>对静态工厂方法的理解 第一次使用静态工厂方法是在HIT<Software Construction>课 ...

  9. 考虑使用静态工厂方法替代构造方法

    转自 https://www.cnblogs.com/chenpi/p/5981084.html 阅读目录 创建对象 注意区分静态工厂方法和工厂方法模式 使用静态工厂方法的优势 使用静态工厂方法的缺点 ...

最新文章

  1. php use as是什么意思,use关键字在PHP中的几种用法
  2. 滴滴与能链杀红眼的加油市场,究竟有多大?
  3. NutchServer的安全层
  4. 计算机服务哪些不能关闭,Win7系统下哪些系统服务不能关闭
  5. P1855 榨取kkksc03
  6. php添加用户信息进入数据库,dedecms用户将信息插入数据库
  7. mac下hive-2.2.0-src版本的编译
  8. js 使用replace替换、全部替换、替换动态数据方法
  9. ArcGIS Desktop 10.5 安装实录
  10. 经典排序算法(十八)--Proxmap Sort
  11. 谷歌地球看不了街景_谷歌街景车意外拍到的沙雕动物们……也是意想不到的快乐源泉啊!...
  12. js动态生成表格实例
  13. 【PI调节】对PI调节的一些认识
  14. alsa 调试工具 aplay 的 编译与 使用
  15. 档案查询系统php,档案信息管理系统 v2.0
  16. 理解深度负反馈,线性失真与非线性失真线性现象
  17. 准备半年,面试2个月,上岸快手拿个35K应该不算高吧?
  18. windows 安装达梦数据库Python 报错:fatal error C1083: Cannot open include file: ‘DPI.h‘: No such file or direc
  19. 【编译原理】LR语法分析器的设计与实现
  20. 分布式爬虫系统的设计与实现(SourceForge.net数据爬取)

热门文章

  1. linux怎么打出管道命令这个符号,linux 管道命令 竖线 ‘ | ’
  2. 如何 接收消息服务器url,(读书笔记)网络是怎样连接的——浏览器生成消息...
  3. 建兴固态硬盘垃圾/LITEON SSD SUCKS
  4. IPUS SQPI PSRAM为STM32单片机提供RAM扩展方案
  5. 借助Jackson的JsonTypeInfo注解实现多态类的解析
  6. OS学习笔记-5(清华大学慕课)计算机体系结构与内存层次
  7. 冠词,a/an/the --- 元音字母
  8. 世界在变化刷脸支付一直奋进
  9. HTML5期末大作业:仿唯品会购物网站设计——仿唯品会购物商城(5页) HTML+CSS+JavaScript 学生DW网页设计作业成品 商城网站设计
  10. 木兰词·拟古决绝词柬友(引用)