"任何面向对象的问题都可以通过添加一个间接层来解决,除非有太多的间接层。"我很喜欢这个观点(引自我的一个朋友),因为单元测试中的很多种"艺术"就是找到一个正确的地方,添加或者使用一个间接层,以此来测试代码。
有些代码无法测试吗?那么添加一个层来封装对这些代码的调用,然后在测试中模拟这个层,或者使这些代码变得可替换(这样其本身就是一个间接层)。当 然,这种艺术也包括避免重复新建一个已经存在的间接层,也包括当这种做法使事情变得过于复杂时,停止使用它。不过,让我们一步步来。
事实上,测试这段代码的唯一途径,是在文件系统中建立一个配置文件。因为我们正尝试着避免这种依赖,所以,我们需要代码不必借助于集成测试也可轻松测试。
回头看看本章开篇提到的宇航员的类比,我们发现,消除这种依赖有一个明确的模式:
1. 找到被测对象所对应的接口或者API。在宇航员的例子中,就是在航天飞机中的操纵杆和控制器,如图3.2所示。
![]() |
图3.2 航天飞机模拟器中有真实的操纵 杆和模拟外部世界的屏幕(感谢NASA提供照片) |
2. 将接口的底层实现替换为你能控制的东西。它涉及各种各样的飞船监视器、操纵杆和按钮等,将其连接到控制舱,这样,测试工程师就能够控制航天飞机的接口向宇航员显示什么内容。
将这种模式引入代码,还需要以下更多步骤。
1. 找到被测方法所对应的接口。(在这个例子中,"接口"并不是纯粹的面向对象中的概念,是指需要一起协同工作的方法或者类。)在LogAn项目中,是指文件系统配置文件。
2. 如果接口和我们测试的方法直接相关(在这个例子中,我们是直接调用文件系统),则添加一个间接访问层来调用接口,以便能够测试代码。在我们的 例子中,添加间接访问层的方式之一,是将对文件系统的直接调用迁移到一个单独的类(比如FileExtensionManager)。后文将看看其他的方 式。(图3.3显示在这一步骤后系统设计的样子。)
3. 将交互接口的底层实现替换为你能够控制的东西。在我们的例子中,用一个可以控制的桩类(SubExtensionManager)来替换方法所调用的类的实例(FileExtensionManager),以便测试代码可以控制外部依赖。
![]() |
图3.3 引入一个间接层来解除对文件系统 的直接依赖。调用文件系统的代码剥离出来,放入 FileExtensionManager类。这个类在我们后 面的测试中将被替换为一个桩类 |
![]() |
图3.4 引入一个桩类来打破依赖 |
图3.4增加了一个新的接口。这个新的接口将使对象模型能够把FileExtensionManager类所做的事情抽象出来,从而允许测试可以新建一个类似于FileExtensionManager的桩对象。这个方法的介绍参见后文。
我们已经见识过在代码中引入可测试性的方式之一--新建一个新的接口。现在让我们来了解代码重构,和在代码中引入接缝的概念。