在诊断 Java 代码的这一部分中Eric Allen 暂停了对具体错误模式的讨论转而选择讨论关于设计易于甚至我们乐于测试的软件的问题他概述了七条设计原则这些原则能大幅提高您编写测试代码的效率并因此提高结果代码库的健壮性请在讨论论坛与作者和其它读者共享您关于本文的心得
谨以本文献给周二攻击的受害者向灾难挑战的英雄们和美国人民的钢铁意志
当设计大型程序的时候您必须时刻留心不同设计选项对诸如性能和可扩展性这样的特征的影响随着软件产品的日渐复杂及其无所不在的部署软件的可测试性也成了更重要的考虑事项
彻底测试代码的重要性是显然的花在编写测试和测试代码上的时间和精力给您带来的回报是维护成本的大幅降低
然而除非您很小心否则您花在测试代码上的精力可能会首先达到花在编写代码上的精力的几倍!我曾看到程序员们齐心协力地对他们的全部代码进行单元测试结果花在上面的时间使大多数人都以沮丧而告终
幸运的是没有必要这样在您设计软件的时候应用一些基本原则编写易于测试甚至使测试成为乐趣的代码是可能的
跟其它编码原则一样这些原则也不是不容置疑或不可改变的教条有时候打破这些规则也是必要的因此理解每条原则背后的动机和判断何时这些动机不适用(或应让位给更关心的问题)的能力是很重要的
原则 到 GUI 视图的外面去
尽可能把代码移到 GUI 视图的外面然后各种 GUI 动作就能成了模型上的简单方法调用为什么您需要这样做呢?
对 GUI 测试者来说通过方法调用测试功能比间接地测试功能容易的多
另一个好处是它使修改程序功能而不影响视图变的更容易
当然视图中也可能存在错误在理想情况下对程序的测试将同时检查模型和视图(想更多了解测试视图请参阅我关于 Liar View 错误模式的文章或 Jeffries 等人的 Extreme Programming Installed这两个链接都在参考资料部分)
原则 使用类型进行错误检查
类型是您的朋友 — 尽可能多地用类型系统自动检查错误
类型能在程序运行之前自动捕捉程序中的错误没有静态类型检查的话类型错误将作为破坏者逗留在您的程序中直到恰当的执行路径碰巧把它揭露出来为止
最大限度地发挥使用类型的长处是棘手的通常一组数据结构可以在一个抽象级别上一起使用或者被分出成为一个单一的更高抽象级别的一个新的相关数据类型
事实上编程语言自身的历史可以看成是可以编程的抽象级别的逐渐提高汇编语言提供了比特到整数和浮点数的抽象接下来是记录和函数抽象然后又是诸如对象类线程以及异常这样的抽象
在每一抽象级别上达到与更高级别抽象一致的功能是可能的但那实质上仅仅是耗费更多精力冒更多的错误风险
在面向对象语言(其它现代语言也一样)中一个程序员在设计抽象上有很大的灵活性在哪个抽象级别上设计程序就成了基于折衷的决定比如由抽象级别提供的更多的健壮性和由于不能在更低抽象级别上工作而带来的表达性(有时是性能)的损失
通常高级别抽象带来的健壮性和简单性的价值很少被其它考虑事项超过(要了解对这个问题的更多讨论请参阅我关于 Impostor Type 错误模式的文章在参考资料部分有它的链接)
原则 使用调节器避免故障线路(fault line)
我用故障线路来指独立组件之间的接口独立组件之间和组件与其相应子组件之间相比很少有交互这种故障线路的一个典型示例是 GUI 视图和它的模型之间的接口其它示例包括在编译器中处理的不同阶段之间的接口或操作系统的内核和用户界面之间的接口
找出程序的故障线路然后用具有转发功能的调节器快速访问聚合组件
沿着故障线路隔离测试每个组件通常更容易但如果每个组件暴露的对象有很多或者组件中您想测试的一些对象只有通过多个嵌套引用才能访问那么测试就会变的很乏味
不用隔离测试而是拥有您在它上面调用您想测试的各种方法的单个调节器对象通常是有帮助的这个对象然后能把这些方法调用转发到适当的地方
沿着相同线路设计和自己的测试代码串联在一起的程序组件接口是有益的这将使您把注意力集中在使这些接口尽可能简单上
原则 方法小型签名和缺省参数
使用小型方法说明和重载带缺省方法参数的方法将使您在测试中调用这些方法变的愉快的多否则在测试这些方法时您将不得不构造额外参数如果参数很大那么将很快导致代码膨胀更糟的是它会诱使您编写比在其它情况下更少的测试
原则 访问器不应修改内存状态
请在您的测试中使用不修改内存状态的访问器来检查对象状态
在某些方面测试和实验室试验相似它们都想证明特定假设有效如果特定检查动作改变了该领域的状态那么要这样做会变得困难的多
与量子力学领域不同计算机进程的状态可以不修改就被检查使用这种原则对您有好处
原则 用接口说明外部程序组件
用接口说明外部程序组件使得我们可以容易地在测试案例中模拟这些组件
这条原则能节省大量时间特别是当外部组件的实现还未完成时通常大多数基本组件都不能准时可用如果这些组件不在适当位置您就不能测试您自己的代码的话那么您就在朝灾难走去您的客户不会关心您只有两个小时来集成迟到了两周的组件他们知道的全部就是整套产品被延期了和这是违约的
原则 优先编写测试代码
优先编写测试代码这是标准的 XP 方法但却总有一种忽视它的诱惑
每次我屈服于这种诱惑时我都感到后悔假设您正努力生产正确的代码那么您 好象能从推迟编写测试代码中节省的时间其实只是一个幻想
注意这不是说您应该一次性编写全部测试代码后再一次性全部实现编写一些测试代码实现它们再编写一些测试代码再实现它们等等是个更好的办法设计以这种方式得以进展在实现阶段捕捉错误并在下一组测试中改正它以这种方式编写测试也更少会使人畏缩
代码比您需要的还多?
只需一点点努力就可能容易地对任何程序进行彻底的测试当然不可避免存在这些原则不适用的情况于是看起来好像不可能对功能进行测试
当出现这些情况时我尽力退一步地看这个问题我怎样才可能测试这种代码?相反地我问自己我怎样才能以可测试方式编写这些代码呢?这种想法上的改变的结果经常是增加了大量 仅仅服务于简化测试的功能
什么?别担心出现这种情况完全正常
就象很多现有的设计模式它们只是为了增加程序的可扩展性就往程序中添加很多类(例如 visitordecorator 等等)开发简化测试的新模式是可以接受的实际上面向对象语言的很多特征都是为了简化扩展而包含进去的为什么语言的未来版本(或全新的语言)不应包含简化测试的特征
对 Java 语言来说这已经开始人们计划在未来版本中包含很多更强大的类型系统断言(assertion)等等就象面向对象的语言已经增加了我们重用和扩展现有代码的程度将来面向测试的设计和特征将帮助我们增强新老代码的健壮性
参考资料
参加本文的讨论论坛
DePaul 大学的软件工程系在检测 Java 空指针异常的自动化法则方面做了一些工作
JUnit 主页提供讨论程序测试方法的很多有趣文章的链接并提供 JUnit 的最新版本
如果您正用 VisualAge for Java 开发代码请看看这篇文章 VisualAge for Java 中的调试和单元测试
查阅 iContract 一个方便您往 Java 代码中添加断言的工具
Roy Miller 和 Chris Collins 提供的文章XP distilled(developerWorks 年 月)说明了极端编程方法如何为您的 Java 工程带来更大的成功
查阅 Ex 上的官方的 XP 编程规则
想了解如何测试视图的一些观点看看 Eric Allen 关于 Liar View 错误模式的文章(developerWorks 年 月)
想更多了解测试视图的观点JeffriesAnderson 和 Hendrickson 的 Extreme Programming Installed(AddisonWesley 年)会很有用
想了解为什么使用更高级别的抽象是更有益的(即使它只是在逐步走向前台)请参阅 Eric Allen 关于 Impostor Type 错误模式的文章(developerWorks 年 月)
在 developerWorks Java 技术专区上查找更多的 Java 参考资料
关于作者
Eric Allen 从 Cornell 大学获得计算机科学及数学的学士学位并且是 Rice 大学 Java 编程语言小组的博士候选人在回 Rice 完成学位前Eric 是 CycorpInc 的 Java 开发者带头人他还在 Javaworld 主持Java 初学者讨论论坛他的研究包括在源程序和字节码级别上 Java 语言的语义模型和静态分析工具开发Eric 还帮助开发 Rice 的 NextGen 编程语言编译器NextGen 是一个支持泛运行时类型的 Java 扩展可通过 eallen@csriceedu 与 Eric 联系