假冒的艺术

预处理的接入点

  • 构建脚本中的宏定义可以控制将文本解释为真正的实现还是假的实现

  • 构建脚本中的头文件搜索路径可被用来控制接入真正的声明还是假的声明

  • 头文件中的防卫宏可被用来接入假的声明以遮挡真正的声明被包含进来

上面第二点通常要求提供同名的头文件, 而使用不同的构建设置

而第三点不要求同名, 只要使用相同的防卫宏, 并保证把包含替代声明的头文件放在真正的头文件前面包含进来就可以, 使用同一构建设置即可

 

编译链接的接入点

  • 构建脚本中的库搜索路径可被用来控制接入真正的实现还是假的实现

  • 针对接口编程, 可以控制接入真正的实现还是假的实现

  • 与针对接口编程类似, 使用函数指针/函数对象等封装函数, 可以接入假的实现

  • 使用函数封装数据访问, static访问, new 操作符, 可以子类化以接入假的实现

  • 改造为模板, 可以传入不同的模板实参来控制接入真正的实现还是假的实现

  • 利用名称作用范围, 同名局部变量可遮挡其它名字等, 来控制接入真正的实现还是假的实现

  • 利用对象的内存布局, 直接将某几个假的函数地址组成的 vtable, 强转为被测类型

 

运行时的接入点

  • 解释型动态语言, 直接在测试前重定义

  • 编译型静态语言, 运行时修改函数地址

 

基本上两个原则

  • 寻找, 引入, 利用一切可能的接入点

  • 在一切依赖具体实现的地方, 插入一层间接

 

例子: 测试使用了 static 函数, new 操作符的客户代码

在 TDD 流行之后, 关于 Singleton, static 函数, new 操作符等依赖具体实现的代码被推荐避免使用. 遗留系统中则不可避免的保留着. 它们被批评的原因是使它们的客户代码难以测试. 我们可以利用 "使用函数封装数据访问, 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. 如果是函数, 可以定义一个函数对象来封装想假冒的函数, 生成该函数对象类型的一个变量时, 使用与函数相同的名字, 利用名字的作用域规则来遮盖原来的函数.

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值