6 软件架构
6.14 测试边界
和程序代码一样,测试代码也是系统的一部分。甚至,测试代码有时在系统架构中的地位还要比其他部分更独特一些。
测试也是一种系统组件。
从架构的角度来讲,所有的测试都是一样的。不论它们是小型的TDD测试,还是大型的FitNess、Cucumber、SpecFlow或JBehave测试,对架构来说都是一样的。
究其本质而言,测试组件也是要遵守依赖关系原则的。因为其中总是充满了各种细节信息,非常具体,所以它始终都是向内依赖于被测试部分的代码的。事实上,我们可以将测试组件视为系统架构中最外圈的程序,它们始终是向内依赖的,而且系统中没有其他组件依赖于它们。
另外,测试组件是可以独立部署的,事实上,大部分测试组件都是被部署在测试环境中,而不是生产环境中的,所以,即使是在那些本身不需要独立部署的系统中,其测试代码也总是独立部署的。
测试组件通常是一个系统中最独立的组件,系统的正常运行并不需要用到测试组件,用户也不依赖于测试组件。测试组件的存在是为了支持开发过程,而不是运行过程。然而,测试组件仍然是系统中不可或缺的一个组件。事实上,测试组件在许多方面都反映了系统中其他组件所应遵循的设计模型。
由于测试代码的独立性,以及往往不会被部署到生产环境的特点,开发者常常会在系统设计中忽视测试的重要性,这种做法是极为错误的。测试如果没有被集成到系统设计中,往往是非常脆弱的,这种脆弱性会使得系统变得死板,非常难以更改。
当然,这里的关键之处就是耦合。如果测试代码与系统是强耦合的,它就得随着系统变更而变更,哪怕只是系统中组件的一点小变化,都可能会导致许多与之相耦合的测试出现问题,需要做出相应的变更。这种修改一个通用的系统组件可能会导致成百上千个测试出现问题的情况,称为脆弱的测试问题(fragile tests problem)。
脆弱的测试问题往往会让系统变得非常死板,当开发者意识到一些简单的修改就会导致大量的测试出错时,他们自然就会抵制修改。要想解决这个问题,就必须在设计中考虑到系统的可测试性,软件设计的第一原则,就是不要依赖于多变的东西。
设计这样一个系统的方法之一就是专门为验证业务逻辑的测试创建一个API。这个API应该被授予超级用户权限,允许测试代码可以忽视安全限制,绕过那些成本高昂的资源(例如数据库),强制将系统设置到某种可测试的状态中,总而言之,该API应该成为用户界面所用到的交互器与接口适配器的一个超集。
设置测试API是为了将测试部分从应用程序中分离出来。换句话说,这种解耦动作不只是为了分隔测试部分与UI部分,而是要将测试代码的结构与应用程序其他部分的代码结构分开。
结构性耦合是测试代码