2.2.2 “接口提取”和“Virtual and Override”
“接口提取”手法是对CollaboratorClass提取出一个抽象接口CollaboratorService,然后让CollaboratorClass和FakeCollaboratorClass都去实现这个接口,而ClassUnderTest则由直接依赖CollaboratorClass转向依赖于抽象接口CollaboratorService, 如下图所示。
实际上,“接口提取”手法是一种非常好的手法,它使得我们的代码遵循“依赖抽象原则”,遵循这个原则的软件具有较好的灵活性,这是具有可测试性的软件也具有较好的设计的一个佐证。
而“Virtual and Override”手法则是使CollaboratorClass中的被依赖方法成为virtual,然后让FakeCollaboratorClass去公有继承CollaboratorClass,并且override那些虚函数,从而替换掉 CollaboratorClass中的行为。这种手法如下图所示。
总体上讲,我们推荐优先使用“接口提取”手法,因为这将使代码遵循“依赖抽象原则”,从而使软件更好地应对今后的变化。但是,“Virtual and Override”方法也是有其一席之地的,我们后面会看到例子。
2.2.3 “参数注入”和“Extract and Override”
在ClassUnderTest这边,对CollaboratorClass的依赖的产生方式也可以划分成两类:
依赖是通过方法参数传入的,这种形式的依赖被称为“参数注入”式依赖(parameter injection dependency)。参数注入式依赖是一种耦合度较低的依赖产生形式,因此对ClassUnderTest的影响不大,一般最多只需要把方法的签名由直接依赖CollaboratorClass改成依赖接口CollaboratorService。
依赖是在被测方法的方法体内部产生的,这种依赖被称为“隐藏式”依赖(hidden dependency)。隐藏式依赖有多种表现形式:
● 直接创建CollaboratorClass对象作为局部变量或成员变量。
● 通过一个工厂方法来产生CollaboratorClass对象。
● 通过一个工厂类来产生CollaboratorClass对象。
隐藏式依赖是一种耦合程度较高的依赖,因此是我们着重需要“打破”的依赖。一种方法是把隐藏式依赖转变成参数注入式依赖,我们将在后面的小节中看到这种方法的应用。而另一种方法,则是使用“Extract and Override”手法,即:我们给ClassUnderTest引入一个virtual的工厂成员函数,来返回一个CollaboratorClass对象的引用,然后对ClassUnderTest派生出一个子类,在该子类中override这个工厂成员函数,让它返回一个FakeCollaboratorClass对象的引用,如下图所示。
这里的TestingClassUnderTest被称为“测试用子类”(Testing Subclass)。这时,在Test Fixture中被实例化的其实就是测试用子类,而不是被测类本身。
以上我们研究了进行单元测试所需要的核心技术,以及4种最常用的核心手法,这些手法足以应付绝大多数的情况,但是,仍然有一些特殊情况需要我们特别注意,我们从下一节开始,以Q&A的形式,讨论这些特殊情况。