TestableMock的设计和原理 --- 这篇文档主要介绍`TestableMock`中Mock功能的设计思想和实现原理。 与常见的Mock工具在每个测试用例里写Mock定义不同,`TestableMock`让每个业务类直接提供自己的Mock方法集合,描述自身在测试时需要被Mock的调用以及相应替代逻辑(即每个业务类有自己的独立Test类和独立Mock类)。采用约定优于配置,降低Mock学习理解成本、减少冗余信息。 这种设计基于两项基本假设: 1. 同一个测试类里,一个测试用例里需要Mock掉的方法,在其他测试用例里通常也都需要Mock。因为这些被Mock的方法往往访问了不便于测试的外部依赖。 2. 需要Mock的调用都来自被测类的代码。(此假设是符合单元测试初衷的,即单元测试只应该关注当前单元的内部行为,单元外的逻辑应该被替换为Mock) 据此通过约定来简化符合该假设的单元测试场景,通过配置来支持其余复杂的使用场景。 `TestableMock`的原理可以用一句话概括:利用JavaAgent动态修改字节码,把被测的业务类中与所有与Mock方法定义匹配的调用在单元测试运行时替换成对Mock方法的调用。 最终达到的效果则是,不论代码用什么服务框架、什么对象容器,不论要Mock的目标对象是注入的、new出来的、全局的还是局部的,不论要Mock的目标方法是私有的、外部的、静态的、继承来的或者重载过的,全部无差别通吃,让单元测试回归简单。 > 划重点:Mock的目标是**被测类**中的**方法调用**。测试用例里的代码不会被Mock,方法的定义本身没有变化,只是发起调用的代码被替换了。 具体来说,在单元测试启动时,`TestableMock`会对加载到内存中的类进行预处理,同时分别建立“被测类”、“测试类”、“Mock容器类”之间的关联关系(可以是一对一,也可以是多对一)。这个关联一方面是为了在测试用例执行时能够正确匹配Mock调用并进行替换,另一方面则是为了能控制Mock方法的生效范围。 对于被测类,将匹配到的调用换成对Mock容器方法的调用。 对于测试类,在每个测试用例开头插入Mock上下文初始化代码。 对于Mock容器类,增加`testableIns()`方法变成单例类,在每个Mock方法开头插入记录调用的代码。 以上是整个Mock的核心逻辑,更多实现细节,请参考源码。若有任何问题、建议、改进提议,都欢迎通过Github Issue和Pull Request参与讨论、贡献😃