TestableMock试用和单元测试的思考

2,608 阅读5分钟

背景

要测试一个类的private方法,而且这个方法在构造方法中调用,MockitoPowerMock都不适用这种场景(Mockito不支持私有方法,PowerMock不支持Junit5),最后找到了阿里的TestableMock,号称"让天下没有难测的案例",看下究竟能不能解决我的问题

试用

首先我们看下主流Mock工具的比较

工具原理最小Mock单元对被Mock方法的限制上手难度IDE支持
Mockito动态代理不能Mock私有/静态和构造方法较容易很好
Spock动态代理不能Mock私有/静态和构造方法较复杂一般
PowerMock自定义类加载器任何方法皆可较复杂较好
JMockit运行时字节码修改不能Mock构造方法(new操作符)较复杂一般
TestableMock运行时字节码修改方法任何方法皆可很容易一般

这是TestableMock给出的比较,可能有些偏向于自家产品,但我们可以看出基于动态代理的MockitoSpock可做的事情有限,但不支持私有方法等,而PowerMock自定义了类加载器,能够实现很多功能,但会影响jacoco覆盖率,也不支持Junit5(这不能忍),而JMockitTestableMock都是运行时修改字节码,前者维护不太活跃,后者吸收了JMockit的一些设计,出自阿里团队.
在试用之后确实能够满足我测试构造方法调用private的需求,从官方文档也看出十分用心.使用TestableMock需要遵循两个原则:

  1. 同一个测试类里,一个测试用例里需要Mock掉的方法,在其他测试用例里通常也都需要Mock。因为这些被Mock的方法往往访问了不便于测试的外部依赖。
  2. 每个单元测试只关注被测单元内部的逻辑,单元外的无关调用应该被替换为Mock。即需要被Mock的调用应该都在被测类的代码中。

避坑指南

在我的测试过程中踩过一些坑,虽然在官方文档和样例中都有说明,但我觉得还是很容易犯,为了让自己引以为戒,一定要用文字记录下来.

默认情况下,TestableMock假设测试类与被测类的包路径相同,且名称为被测类名+Test(通常采用MavenGradle构建的Java项目均符合这种惯例)。 同时约定测试类关联的Mock容器为在其内部且名为Mock的静态类,或相同包路径下名为被测类名+Mock的独立类。

TestableMock会依次在以下两个位置寻找Mock容器:

  • 默认位置测试类中名为Mock的静态内部类(譬如原类型是Demo,Mock容器类为DemoTest.Mock
  • 同包路径下名为被测类+Mock的独立类(譬如原类型是Demo,Mock容器类为DemoMock

这类问题可以通过@MockDiagnose(LogLevel.VERBOSE)添加排查日志,在日志中必须出现Found mock class xxxFound source class xxxFound test class xxx,即被测类,测试类,容器类三者齐全才能生效
除此之外,Mock替换只会作用在被测类的代码里,很多测试的时候“在测试用例里直接调用了Mock的方法,发现没有替换”,在这个问题上我浪费了好多时间.

单元测试思考

Mockito官方给出的建议是:

Don't mock everything, it's an anti-pattern
Don't mock value objects
Don't mock a type you don't own!

不要mock不属于自己的类,其实是比较难的,有时候我们就想mock第三方接口来方便测试,Mockito希望单测能够只关注自身类的逻辑,这确实是我们想达到的终极目标,但现实经常遇到历史悠久的系统,不得不面对遗留的代码,难以实现这个目标.

阿里巴巴Java开发手册给出单元测试的原则是AIR

  • Automatic: 自动化,单元测试应该是全自动执行的。测试用例通常是被定期执行的,执行过程必须完全自动化才有意义。单元测试中不准使用 System.out 来进行人肉验证,必须使用 assert 来验证
  • Independent: 独立性, 为了保证单元测试稳定可靠且便于维护,单元测试用例之间决不能互相调用,也不能依赖执行的先后次序
  • Repeatable: 重复性, 单元测试是可以重复执行的,不能受到外界环境的影响

对于单元测试,要保证测试粒度足够小,有助于精确定位问题。单测粒度至多是类级别,一般是方法级别.只有测试粒度小才能在出错时尽快定位到出错位置。单测不负责检查跨类或者跨系统的交互逻辑,那是集成测试的领域

很多时候单测不好写是因为代码设计得不好,通常会寻求PowerMockTestableMock的帮助,而他们确实会破坏上面的规则,但面对遗留问题只能不得已为之.
但不管怎么样,我们还是需要定好目标,清楚了解什么样的单测才是合格的单测,在我看来,粒度要足够小,分层测试,白盒测试才是优秀的单测.