不觉间,采用测试驱动开发(Test Driven Development)半年有余,自从看了Robert Martin的《敏捷软件开发:原则、模式与实践》, 就忍不住想实践一下,亲身体会书中描述的美妙景象。恰逢项目中一个全新功能交由我负责,开发周期也不是十分急迫,就拿这个新功能当回小白鼠,遵循书中的实 践方法开始使用测试驱动开发。

  随着开发的不断深入,测试驱动开发的实践渐入佳境,对其认识也从开始时的顶礼膜拜逐渐回归理性,在为其优长欢欣鼓舞的同时,更理解了其不足或者说是不具备的能力。

  测试驱动开发意味着不再是从需求分析与概要设计/详细设计后直接进入到实现代码的编写,而是转而根据需求分析和概要设计进行测试用例的设计与测 试代码的编写,通过测试代码硬性规定了实现代码所必须满足的功能需求、容错能力。编写实现代码的唯一目的就是使所有测试用例成功运行,任何测试用例的失败 都意味着实现代码存在功能缺陷或者逻辑错误。之后就是不断重复--修改或增加代码再运行所有测试用例检查结果--的迭代过程。

  看上去很简单的一个过程,却与传统的开发格格不入,惯性的力量导致开始时很难从传统过程的思维方式中摆脱出来。在做完需求分析与概要设计,开始 设计测试用例与编写测试代码时手足无措,只能摸着石头过河,试着去做去写,然后通过测试驱动开发的迭代过程去观察,去体会,去修改,去适应,然后再重复迭 代过程。就这样,渐渐理解了测试驱动开发这种“进化->测试->反馈->再进化->…”迭代循环的自然与强大,从测试中得到的反 馈不仅说明实现代码的完备和健全与否,也给人不断进步之感,似乎总是在脚踏实地的前进着。因为在迈开每一步之前,都知道已有的工作经受了测试的检验,就具有相应的信心。即使发现有更好更优秀的设计实现,也可以放心大胆的彻底重构,因为有测试用例和测试代码作监督作守候。

  此外,测试驱动开发也改变了原先开发的视角,不再是直接编写实现代码,而是转而从旁观者和使用者的角度设计测试用例和测试代码,总是能够发现很多原先忽略的因素和条件。

  测试驱动开发的优势在《敏捷软件开发:原则、模式与实践》已有详述,我所感受到的有:

  1. 有助于设计简单清晰而易用的接口。因为总是先有测试代码,才编写实现代码,意味着总是从使用者的角度设计接口,只有简单易用的接口才方便测试时调用,所以我几乎是“被迫”去努力设计简单易用的接口,因为我就是第一个使用者。

  2. 模块切分的足够小但是模块间保持极低的耦合度。为了方便测试,我总是尽力把重复的逻辑剥离出来,单独构建模块进行测试;并且尽量减少模块间的耦合,保持模 块相对独立和功能完备。如果模块过大,或者模块间强耦合,写测试用例与代码时就会困难重重,笨重复杂。因为总是先写测试代码,眼前的利益高于一切,将来的 实现代码必然要迁就目前测试的需要。于是,我又“不知不觉”设计出小粒度模块,且模块间耦合度低的实现。

  3. 肆无忌惮的重构。因为有测试用例和测试代码作担保,我终于能够从小心翼翼心惊胆战的重构中解脱出来,只要是更好的实现和设计,我都愿意尝试,管它呢,反正 多跑几遍测试就知道重构的结果如何了。可以说,测试驱动开发鼓励代码的不断进化,即使测试已经全部通过,也可以通过大胆重构来改进设计与实现。尽管此时是 否还需要再重构见仁见智,至少提供了一个可能,我是很喜欢这一点。

  4. 测试代码是“活”的软件文档,它硬性规定了实现代码必须满足的需求,达不到就报错。传统的文本文档比之就苍白无力多了,“应该”,“必须”,这些字眼对程 序员有多少约束力?而且测试代码总是能与实现代码保持新鲜同步,传统文档写完后经常被上传服务器束之高阁,很少人问津,随着开发组内人员的变动,往往最后 就湮没在服务器的故纸堆之中了。

  测试驱动开发毕竟不是软件银弹,也不存在这样的银弹,它也有力不能及的地方:

  1. 测试驱动开发不可能让人立即具有设计出优美解决方案的能力,或者说是优秀的分析与解决问题的能力。TDD不是Test Driven Design。它只是一个过程,也许可以帮助你发现并帮助你实现优美的解决方案,但是它不能变魔术一样,只要学会了就变出一个优美的设计出来,优秀的分析 问题与解决问题的能力还是要靠不断地学习与借鉴他人成就才能得到提高。

  2. 测试驱动开发不能节省开发投入,也很少能够节省开发周期。测试开发所编写的大量测试代码都是要投入时间与精力的,我现在的代码统计显示,测试代码与实现代 码的比例基本在3:2,即使因为测试驱动开发能得到一个简洁的设计,也不能弥补测试代码的工作量。当然,测试代码可以一定程度保证高质量的实现代码,从而 减少后期软件测试与修正缺陷的工作周期,并进一步在软件发布后减少代码修正维护的工作量。但至少在开发阶段,两相抵消,开发周期并不能有明显改善,如果是 第一次采纳测试驱动开发,甚至会延长开发周期。

  3. 测试驱动开发不能杜绝所有的软件缺陷。尽管测试驱动开发通过测试约束,减少了程序员犯错和遗忘的可能,但是这只是把问题从实现代码部分地转移到了测试代 码。测试用例的完备与否,测试代码本身逻辑的正确与否都依赖于程序员,糟糕的测试用例设计和测试代码实现可能自顾不暇,也就失去了监督实现代码的能力。我 就见过有程序员在测试代码中读取实现代码生成的数据,再直接拿之来验证实现代码生成的数据,x必然恒等于x,这样的测试逻辑必然成功,但是毫无意义。

  对测试驱动开发认识的深入,让我更能合理运用它,扬其长避其短,充分享受其带来的便利。

  测试驱动开发带给我前所未有的软件开发体验,人们都说TDD是传染病,一旦接触就无法自拔。我想说,是的,但我心甘情愿被传染,无怨无悔,无忧无虑。借用一句英文:There’s nothing to fear, TDD is with us, amen.