3.1 名词定义
存根 (stub) 是对系统中存在的一个依赖项的可控制的替代物。通过使用存根,在测试代码的时候无需直接处理这个依赖项。
重构 (refactoring) 在不改变代码功能的前提下对代码的修改。
接缝 (seam) 代码中可以插入不同功能的地方。一个类是开放给外部的,而源代码是封闭给自身的,所以根据这个开闭原则,实现的代码中就会出现接缝。
3.2 发现代码的依赖项
一个测试过程中发现方法需要使用文件系统才能正常测试,那么这就是集成测试了。这时候就出现了抑制测试设计:代码对外部资源有某种依赖,尽管代码本身逻辑完全正确,但这种依赖可能导致测试失败。
如果出现这种因为外部依赖无法测试的代码,我们可以添加一个间接层封装对这段代码的调用,然后在测试中模拟这个间接层的实现;或者使这段代码可替换(这样代码本身就成了间接层)。
要破除依赖,可以在代码中引入一个或多个接缝,但需要保证重构后的代码和之前的功能完全一样。
3.3 打破依赖的重构方法
A型 :把具体类抽象成接口或委托
把和文件系统相关的代码分离到一个单独的类里面,这样容易识别,以便将来在代码中替换掉对这个类的调用。
B型:重构代码,从而能够对其注入这种委托和接口的伪实现
- 在被测试的单元中注入一个伪实现的依赖注入
- 在构造函数层注入一个伪对象的构造函数注入
- 用伪对象模拟异常
- 用get或set注入伪对象(表明这个依赖项是可选的)
- 在方法调用前注入伪对象
变种:使用抽取和重写生成假结果
从被测试类派生一个新类,以便重写一个虚函数使其返回存根。
3.4 构造函数注入
如果被测试代码需要多个存根才能在没有依赖项的情况下工作,加入越来越多的构造函数就容易降低代码的可读性和可维护性。解决的方式:创建一个特殊的类包含初始化一个类所需的所有值,这个类就是构造函数的唯一参数即可。这种方式叫参数对象重构。如果依赖项过多导致这个特殊类有很多属性,这种方法可能也会失控。这种方式会使代码变得笨拙,但是它对于API的可读性影响是最小的。
3.5 封装问题的讨论
开放代码设计,进行重构使其更容易测试是一件坏事?
面向对象的原则的目的是为了让你的用户(使用你的对象的程序)对对象的行为被限制,保证它不影响你的对象的功能,使对象模型得到正确使用。
在编写单元测试的时候其实给对象增加了另一个用户(测试),这个用户有着不同的需求和目的,它需要像使用代码功能一样使用这些外部依赖,这种可测试的设计需要保证两个用户的目的性