TDD的三条军规 原文最终修订于 2006-04-09 晚上09:45:01

这些年来,我喜欢用下面这三条简单的规则来描述测试驱动开发:

  1. 除非这能让失败的单元测试通过,否则不允许去编写任何的产品代码。
  2. 只允许编写刚好能够导致失败的单元测试。 (编译失败也属于一种失败)
  3. 只允许编写刚好能够导致一个失败的单元测试通过的产品代码。

对于任何功能,一定要从编写它的单元测试开始;但是到了原则2,你就不能再为那个单元测试写更多内容。只要一出现该单元测试代码编译失败,或是断言失败,你就必须停下来开始编写产品代码;但是到了原则3,你就只能编写产品代码,直到让测试编译成功或通过断言为准。

仔细想想,就会发现如果不是去让一些东西编译或是执行,你就根本没办法去写代码。确实,这也正是关键所在。我们所做的任何事情(无论是写测试,写产品代码,或是重构),都要保持系统能够一直运行。跑通测试的时间间隔应该是以秒或是分钟级的,即使那只有10分钟,也都太长了。

想了解实际的操作过程,可以看看“保龄球游戏中的Kata(译注2)一文。

现今有很多程序员,每当他们头一次听到这种技术的时候,会想:“这种做法太愚蠢了!”“这会让我慢下来,这是时间和精力的浪费,让我无法思考,无法设计,还会打乱我的思路。”然而,试想,当你走进一个房间,里面都是用这种方式工作的人们,会发生什么情况?你随便选个时间,随意找个人,一分钟以前,他们所有的代码都跑通过。

让我再重复一遍:一分钟以前每个人的代码都能跑通!不管你找谁,也不管你何时去找,一分钟以前他们所有的代码都跑通了!

如果你所有的代码自始至终都能跑通,那么你会多长时间用一次调试器?答案是,不会太经常。只要敲几下^Z就能很容易的恢复这些代码到跑通时的状态,然后再试着把前几分钟的代码写一遍即可。而如果你不经常调试,那么会省去多少时间呢?而现在你花在调试上的时间又有多少呢?一旦你有调试过,你又要花上多少时间来修复这些bug呢?如果你能够大大减少这些时间的话,又会如何呢?

好处还不只这些。如果你采用这种方法,那么每小时你都会产生好几个测试;每天就有十几个;每个月就几百个;一年下来,你所编写的测试就会有数千个。你可以保留着这些测试而且在任何你希望的时候去运行它们!什么时候运行它们呢?随时!只要你做了改动,就去运行它们!

有些代码已经混乱不堪了,可是我们为何不去清理它们呢?我们担心会破坏它们。但如果我们拥有测试,我们就有理由确信这些代码不会被破坏,或是说我们可以很快的就找到被破坏的地方。如果我们有了这些测试,我们就对代码发生改变无所忌惮。如果我们看到混乱的代码,或是一个不清晰的结构,可以毫无顾虑地清理它。因为有了测试,代码重新变得易修改了;因为有了测试,软件重新变“软”了。

好处还不只这些。如果你想要知道如何去调用一个特定的API,就会有一个测试能够告诉你;如果你想要知道如何创建一个特定的对象,就会有个测试能告诉你。你想知道的任何有关这个系统的,都有一个测试能够去示意。这些测试就像是小型的设计文档,小型的代码示例,描述了系统的工作和使用方式。

你是否曾经整合过一个第三方库到你的项目中呢?你拿到一本厚厚的精致的帮助文档,在结尾处,有一沓薄薄的示例附录。你会选择哪个去阅读呢?当然是示例了!那就是单元测试啊!它们是这份文档中最有用的部分;它们是如何使用代码的鲜活的例子;它们是极其详细、完全没有歧义的设计文档,相当的正规甚至可以执行,而且不会与产品代码相脱离。

好处还不只这些。如果你曾有过增加单元测试到一个可以工作的系统中的经历,你会发现那一点儿都不好玩。你很可能会发现想跑通测试,你一定是要么去改变系统中的部分设计,要么就在测试上作假。这是因为你试图去测试的系统并不是基于可测试设计的。例如,你想要测试某个函数f。然而,f调用了另外一个从数据库中删除记录的函数。在你的测试中,你不希望这条记录被删掉,但却没有办法来阻止它。这样的系统就不是基于可测试设计的系统。

当你遵循TDD的三条规则的时候,你的所有代码天生就可测试!而且另一个能形容“可测试”的词汇就是“可解耦”。为了单独的测试一个模块,你就必须把它解耦。所以TDD强制你去解耦这些模块。确实,如果你遵循这三条规则的话,你会发现你可能比起从前来能做出更多的解藕。这就强制了你去创造更好的,低耦合的设计。

收获了所有的这些好处,这些愚蠢的小规则实际上好像就没那么愚蠢了吧。他们实际上很可能是一些基本而又深刻的原则。确实,在我接触TDD之前我曾做过将近30年的程序员,我不认为曾有过什么人教过我什么非常奏效但又基本的编程实践。毕竟三十年意味着很多的经验。但当我开始了使用TDD,我就被这项技术的有效性所震撼,也沉迷其中。我不会再考虑去敲一大堆代码然后平白指望它们能运行成功。对于那种先将一组模块打散,然后希望能够再将它们整合起来,并且让它们在能下个礼拜五前运行起来的做法,我也不再能忍受。在我写程序的时候,每一个决定都由这种让现在开始写的代码能在一分钟后再次执行这样的基本需求所驱使着。

译注:

1,TDD,测试驱动开发。

2,Kata,可理解为武功招式,详细解释可参见译者的上篇译文

小注:本篇文章由邓辉先生亲自校正,并给予了很多中肯而深刻的建议,译者在此给予感谢!

(原文链接网址:http://www.butunclebob.com/ArticleS.UncleBob.TheThreeRulesOfTdd; Robert C. Martin的英文blog网址:http://www.butunclebob.com/ArticleS.UncleBob

译者注:Robert C. MartinObject Mentor公司总裁,面向对象设计、模式、UML、敏捷方法学和极限编程领域内的资深顾问。他不仅是Jolt获奖图书《敏捷软件开发:原则、模式与实践》(中文版)(《敏捷软件开发》(英文影印版))的作者,还是畅销书Designing Object-Oriented C++ Applications Using the Booch Method的作者。MartinPattern Languages of Program Design 3More C++ Gems的主编,并与James Newkirk合著了XP in Practice。他是国际程序员大会上著名的发言人,并在C++ Report杂志担任过4年的编辑。

版权声明:本文内容由互联网用户自发贡献,该文观点与技术仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 dio@foxmail.com 举报,一经查实,本站将立刻删除。

相关推荐


什么是设计模式一套被反复使用、多数人知晓的、经过分类编目的、代码 设计经验 的总结;使用设计模式是为了 可重用 代码、让代码 更容易 被他人理解、保证代码 可靠性;设计模式使代码编制  真正工程化;设计模式使软件工程的 基石脉络, 如同大厦的结构一样;并不直接用来完成代码的编写,而是 描述 在各种不同情况下,要怎么解决问题的一种方案;能使不稳定依赖于相对稳定、具体依赖于相对抽象,避免引
单一职责原则定义(Single Responsibility Principle,SRP)一个对象应该只包含 单一的职责,并且该职责被完整地封装在一个类中。Every  Object should have  a single responsibility, and that responsibility should be entirely encapsulated by t
动态代理和CGLib代理分不清吗,看看这篇文章,写的非常好,强烈推荐。原文截图*************************************************************************************************************************原文文本************
适配器模式将一个类的接口转换成客户期望的另一个接口,使得原本接口不兼容的类可以相互合作。
策略模式定义了一系列算法族,并封装在类中,它们之间可以互相替换,此模式让算法的变化独立于使用算法的客户。
设计模式讲的是如何编写可扩展、可维护、可读的高质量代码,它是针对软件开发中经常遇到的一些设计问题,总结出来的一套通用的解决方案。
模板方法模式在一个方法中定义一个算法的骨架,而将一些步骤延迟到子类中,使得子类可以在不改变算法结构的情况下,重新定义算法中的某些步骤。
迭代器模式提供了一种方法,用于遍历集合对象中的元素,而又不暴露其内部的细节。
外观模式又叫门面模式,它提供了一个统一的(高层)接口,用来访问子系统中的一群接口,使得子系统更容易使用。
单例模式(Singleton Design Pattern)保证一个类只能有一个实例,并提供一个全局访问点。
组合模式可以将对象组合成树形结构来表示“整体-部分”的层次结构,使得客户可以用一致的方式处理个别对象和对象组合。
装饰者模式能够更灵活的,动态的给对象添加其它功能,而不需要修改任何现有的底层代码。
观察者模式(Observer Design Pattern)定义了对象之间的一对多依赖,当对象状态改变的时候,所有依赖者都会自动收到通知。
代理模式为对象提供一个代理,来控制对该对象的访问。代理模式在不改变原始类代码的情况下,通过引入代理类来给原始类附加功能。
工厂模式(Factory Design Pattern)可细分为三种,分别是简单工厂,工厂方法和抽象工厂,它们都是为了更好的创建对象。
状态模式允许对象在内部状态改变时,改变它的行为,对象看起来好像改变了它的类。
命令模式将请求封装为对象,能够支持请求的排队执行、记录日志、撤销等功能。
备忘录模式(Memento Pattern)保存一个对象的某个状态,以便在适当的时候恢复对象。备忘录模式属于行为型模式。 基本介绍 **意图:**在不破坏封装性的前提下,捕获一个对象的内部状态,并在该
顾名思义,责任链模式(Chain of Responsibility Pattern)为请求创建了一个接收者对象的链。这种模式给予请求的类型,对请求的发送者和接收者进行解耦。这种类型的设计模式属于行为
享元模式(Flyweight Pattern)(轻量级)(共享元素)主要用于减少创建对象的数量,以减少内存占用和提高性能。这种类型的设计模式属于结构型模式,它提供了减少对象数量从而改善应用所需的对象结