跳过构造函数创建 Java 对象(测试)

如果一个 Java 类在初始化时会有外部依赖,这就给单元测试创建它的实例时造成困难。当然被测试类可以改造为依赖全部构造时注入或创建实例后延迟注入,这里不考虑这种改造。

可以参看我以前一篇类似的日志:使用 JMockit 来 mock 构造函数

来说下面的例子

假如上面的代码是不能改动的,并且在 new PriceInquiry() 时依赖于网络环境,所以单机情况不能创建成功。也就使得测试时试图

new OrderService();

会失败。并且试图用 Mockito 的 @InjectMocks 也不行

会出类似下面的借

org.mockito.exceptions.base.MockitoException:
Cannot instantiate @InjectMocks field named 'testMe' of type 'class cc.unmi.OrderService'.
You haven't provided the instance at field declaration so I tried to construct the instance.
However the constructor or the initialization block threw an exception : xxxxxxxxxxxxxxxx

想要千方百计先创建出实例再转换掉内部的 priceInquiry 属性值的打算也落空了,因为无论是用 new 还是 @InjectMocks 怎么都跳不过构造函数的执行(实例成员的初始化会放到构造函数中去,没有声明构造函数会有一个默认构造函数)

因此上面的需求就是如何在测试类跳过构造函数,初步想到的办法有四

一. 反序列化跳过构造函数

ObjectInputstream.readObject() 反列化出 OrderService 对象,但前提是先要有序列化出的字节数据,所以不好操作,还会有 serialVersionUID 不一致的问题

二. 使用 sun.misc.Unsafe 内部 API 

但是上面的代码编译时会有警告

[WARNING] COMPILATION WARNING :
...........................................sun.misc.Unsafe is internal proprietary API and may be removed in a future release

并且是没法用 SuppressWarnings 抑制住的警告,如果用 Maven 时配置了用 -Werror 编译选项的话将无法构建成功。除非不用 -Werror 选项,否则用他法来通过构建还不容易搞

三. 用 JMockit 来 mock 构造函数

又要体验到 JMockit 比 Mockito 强大之处,我们不是一般问题还不愿意祭出 JMockit 来

通常情况下我只是用 JMockit 来辅助 Mockito, 因为更习惯于 Mockito 流畅的打桩(Stubbing) 和校验(Verifying) API。

四. Deencapsulation.newUninitializedInstance(clazz), JMockit 更直截的方式

写本文之前只想到前面三种方式,借此机会又重新看了一个 JMockit 的 Deencapsulation API,发现一个更直截了当的方式,方法名为 newUninitializedInstance(clazz)。顾名思义,就是构造实例不初始化内部状态,恰恰是我所追求的。于是事情变得更为明了

连设置内部状态的 API Deencapsulation 也提供了,用不着模仿着 Mockito 1 做了一个 Whitebox 类来进行反射操作。

最后,在测试中着重推荐用第四种方式,第三种方式也行,它们都是 JMockit 提供的实现。用 JMockit 来辅助 Mockito 跳过构造函数创建实例,而后替换实例的内部状态,再然后就是 Mockito 的事情了。如果被测试类的外部依赖能够通过构造函数或 setter 方法来注入就更简单了,常规手段而无需跳过构造函数就能创建被测试类的实例了。

以上方式只是实例变量不被初始化,静态变量(即类变量) 不受影响,也就是说如果类中有

logger 静态变量总是会被初始化。

本文链接 https://yanbin.blog/create-java-instance-bypass-constructor/, 来自 隔叶黄莺 Yanbin Blog

[版权声明] Creative Commons License 本文采用 署名-非商业性使用-相同方式共享 4.0 国际 (CC BY-NC-SA 4.0) 进行许可。

Subscribe
Notify of
guest

4 Comments
Inline Feedbacks
View all comments
stanfen
stanfen
6 years ago

总感觉有外部依赖类进行单元测试,面临各种限制。用功能测试覆盖是不是更好

stanfen
stanfen
6 years ago
Reply to  Yanbin

如果UT能覆盖到,当然优先了。只是这个场景不具备通用性。可能的结果性价比不高。