大家好,我是君哥。
最近在工作中遇到一个不太好解决的问题,我负责的系统单元测试跑的非常慢,有时候甚至超过 2 个半小时。
公司要求上线前流水线里面的单测必须全部跑成功。跑流水线的时候如果有单测跑失败,需要修改后重新跑,又得跑 2 个多小时。极端情况下得反反复复来几次,真的让人感到煎熬。有时候发现测试用例跑失败的原因竟然是 OOM。
今天就来聊一聊造成单测跑的慢的罪魁祸首,PowerMock。
1.PowerMock 基础
要说 PowerMock 怎么样,那是真的非常好用。下面列给出几个示例,先上一段业务代码,然后我们通过 3 个测试用例把这段代码单测覆盖率写到 100%。
这段代码涉及到读文件、依赖注入、异常处理,我们写单测也从这三个方面来完成。
1.1 文件不存在
我们先来模拟一下文件不存在,这个用例覆盖到上面文件不存在的判断。测试用例如下 :
这里使用 PowerMock 方便地模拟了第 11 行代码文件不存在,用例成功。
1.2 循环跳出
这段用例要模拟按行读文件、dao 层查询用户、跳出循环这三个代码,测试用例代码如下:
这段用例跑完后,已经覆盖到源代码的第 17行和 19 行。
1.3 模拟异常
源代码中有一个异常处理,用例要达到 100% 覆盖,必须把这个异常用测试用例模拟出来。下面看一下测试用例:
至此,单测覆盖率达到 100%。
2.PowerMock 进阶
下面再来使用几个 PowerMock 的功能。再来一段示例代码:
这次我们也要增加 2 个用例的 mock,一个是 Scanner 这个 final 类,第二个是 StringUtils 这个静态类。
2.1 final 类
虽然是一个 final 类,但使用了 PowerMock 框架,我们就像普通类一样就可以用例。
除了 final 类,抽象类、接口都可以 mock,确实很方便。
2.2 静态类
PowerMock 可以方便地模拟静态类,下面这个测试用例对 StringUtils 这个静态类进行了 mock,每次 equals 方法都是返回 false。
因为 equals 方法一直返回 false,所以 getUser 方法没有执行到,测试用例中 verify getUser 方法被调用 0 次。需要注意的是,模拟静态类需要在类定义上面加上一个注解,然后对静态类要做一次 mockStatic。看下面的 @Before 注解。
3.原因分析
PowerMock 因为使用了 @PrepareForTest、@PowerMockIgnore、@SuppressStaticInitialzationFor 这三个注解,这三个注解的参数值不一样,会导致每个单测类执行的时候不能复用公有类加载器,而是需要创建一个自己独有的类加载器。这导致类加载过程十分耗时。
在单测类数量比较少的情况下,单测耗时问题是不会出现的,但是如果一个工程中的单测类数据猛增,比如我们的单测类在 600+,问题就暴露出来的。最难的是不太好做优化,因为如果要去掉 PowerMock 框架,要改造的东西太多了。
4.最后
PowerMock 写单测对开发人员来说确实很方便,但是如果工程中的代码量比较大,团队又要求单测覆盖率高,那单测类的数量确实会很多,最终结果就是单测耗时时间很长。这种情况并不适合使用 PowerMock 框架。
图片
同时我们也要看到,PowerMock 最近一次核心代码更新已经是 4 年前了,单测类数据量多导致的内存问题、耗时问题并没有解决。所以选型的时候一定要慎重。