在单元测试时,不免会碰到一些外部依赖,外部依赖是指在系统中代码与其交互的对象,并且没法对其作人为控制,好比文件系统、线程、内存、时间、数据库结果集等,这时可使用伪对象(fake)来替代外部依赖,桩对象(stub)即是其中之一数据库
一 桩对象函数
a) 桩对象是对系统中现有依赖项的一个替代品,可人为控制。经过使用桩对象,无需涉及依赖项,便可直接对代码进行测试。使用桩对象能够轻松地控制模拟依赖项的返回值会行为(好比模拟内存溢出异常)。单元测试
b) 使用桩对象的前提是要找到原系统中的接缝(Seam)。接缝是指代码中能够插入不一样功能(如桩对象类)的地方。有时须要经过重构来制造接缝并解除依赖的方法,能够抽取接口来制造接缝,这样底层实现就能够被替换了。测试
二 替换底层实现的方法线程
经常使用的替换底层实现的方法有:对象
在构造函数中接收一个接口实现(构造函数注入)接口
在属性中接收一个接口实现(属性注入)内存
在方法的参数中接收一个接口实现(参数注入)it
使用工厂方法产生接口实现io
a) 在构造函数中接收一个接口实现
若是要使用类LogAnalyzer中的IsValidLogFileName方法来判断文件名是否有效,在生产环境下,IsValidLogFileName方法要读取配置文件,而后根据配置内容来进行判断,这个方法依赖文件系统。重构过程以下:
抽取IExtensionManager
经过构造函数传递IExtensionManager的实现,判断文件名是否有效交给IExtensionManager.IsValid来作
在测试代码中,构造伪对象FakeExtensionManager并传递给LogAnalyzer
伪对象FakeExtensionManager是可控的
b) 属性注入和参数注入与构造函数注入的实现方法是相似的,但经过构造函数或参数注入意味着参数是必须的,而属性注入倒是可选的,因此使用属性注入时要注意默认注入的处理。
c) 使用工厂产生接口实现
在工厂类中,Create()方法默认返回生产环境的FileExtensionManager,测试代码能够经过SetManager传递伪对象
LogAnalyzer经过Create方法取得IExtensionManager的接口实现
在测试代码中,调用ExtensionManagerFactory.SetManager(**)注入FakeExtensionManager伪对象
d) 除了上述方法,还有不少方法能够达到解除依赖的目的,好比基类、重写等方式。但无论哪一种方法,为了测试而重构可能会破坏一些面向对象的基本原则,好比封装。这就须要努力在可测试性和封装之间取得平衡,虽然这比较困难,但保证可测试性也是很重要的。
参考资料:
The Art of Unit Testing with examples in C#, 2nd Edition by Roy Osherove