预处理的接入点
上面第二点通常要求提供同名的头文件,而使用不同的构建设置
而第三点不要求同名,只要使用相同的防卫宏,并保证把包含替代声明的头文件放在真正的头文件前面包含进来就可以,使用同一构建设置即可
编译链接的接入点
-
构建脚本中的库搜索路径可被用来控制接入真正的实现还是假的实现
-
针对接口编程,可以控制接入真正的实现还是假的实现
-
使用函数封装数据访问,static访问,new 操作符,可以子类化以接入假的实现
-
改造为模板,可以传入不同的模板实参来控制接入真正的实现还是假的实现
-
利用名称作用范围,同名局部变量可遮挡其它名字等,来控制接入真正的实现还是假的实现
-
利用对象的内存布局,直接将某几个假的函数地址组成的 vtable,强转为被测类型
运行时的接入点
基本上两个原则
-
寻找,引入,利用一切可能的接入点
-
在一切依赖具体实现的地方,插入一层间接
例子: 测试使用了 static 函数,new 操作符的客户代码
在 TDD 流行之后,关于 Singleton,static 函数,new 操作符等依赖具体实现的代码被推荐避免使用. 遗留系统中则不可避免的保留着. 它们被批评的原因是使它们的客户代码难以测试. 我们可以利用 "使用函数封装数据访问,可以子类化以接入假的实现" 来进行测试.
public class SingletonClient {
public void doSomething(){
SingletonObject service = SingletonObject.instance();
service.execute();
}
}
上面的这个SingletonClient的doSomething是无法测试的,因为引用了一个静态函数. 可以把对静态函数的引用抽取到一个可覆写的函数中来引入接入点:
class SingletonClient {
public void doSomething(){
SingletonObject service = getServiceObject();
service.execute();
}
protected SingletonObject getServiceObject() {
return SingletonObject.instance();
}
}
这样测试时我们便可以子类化SingletonClient,只重写getServiceObject,便可以针对这个子类进行测试,效果与针对基类测试是一样的(因为其它的函数实现都相同,只是依赖的第三方对象被替换为了假的实现):
class SingletonClientInTestEnvironment extends SingletonClient {
protected SingletonObject getServiceObject() {
return new SingletonObjectStub();
}
}
例子: C++ 测试,用遮盖技术(定义同名服务类),当同时需要测真正的服务类本身时如何解决链接问题? (重定义)
那就不要定义同名的类,#define 一个宏加一层间接,恰当的时候 #undef. 如果是函数,可以定义一个函数对象来封装想假冒的函数,生成该函数对象类型的一个变量时,使用与函数相同的名字,利用名字的作用域规则来遮盖原来的函数.