TDD的三条军规 软件测试
这些年来,我喜欢用下面这三条简单的规则来描述测试驱动开发:
◆ 除非这能让失败的单元测试通过,否则不允许去编写任何的产品代码。
◆ 只允许编写刚好能够导致失败的单元测试。 (编译失败也属于一种失败)
◆ 只允许编写刚好能够导致一个失败的单元测试通过的产品代码。
对于任何功能,一定要从编写它的单元测试开始;但是到了原则2,你就不能再为那个单元测试写更多内容。只要一出现该单元测试代码编译失败,或是断言失败,你就必须停下来开始编写产品代码;但是到了原则3,你就只能编写产品代码,直到让测试编译成功或通过断言为准。
仔细想想,就会发现如果不是去让一些东西编译或是执行,你就根本没办法去写代码。确实,这也正是关键所在。我们所做的任何事情(无论是写测试,写产品代码,或是重构),都要保持系统能够一直运行。跑通测试的时间间隔应该是以秒或是分钟级的,即使那只有10分钟,也都太长了。
想了解实际的操作过程,可以看看“保龄球游戏中的Kata”(译注2)一文。
现今有很多程序员,每当他们头一次听到这种技术的时候,会想:“这种做法太愚蠢了!”“这会让我慢下来,这是时间和精力的浪费,让我无法思考,无法设计,还会打乱我的思路。”然而,试想,当你走进一个房间,里面都是用这种方式工作的人们,会发生什么情况?你随便选个时间,随意找个人,一分钟以前,他们所有的代码都跑通过。
让我再重复一遍:一分钟以前每个人的代码都能跑通!不管你找谁,也不管你何时去找,一分钟以前他们所有的代码都跑通了!
如果你所有的代码自始至终都能跑通,那么你会多长时间用一次调试器?答案是,不会太经常。只要敲几下^Z就能很容易的恢复这些代码到跑通时的状态,然后再试着把前几分钟的代码写一遍即可。而如果你不经常调试,那么会省去多少时间呢?而现在你花在调试上的时间又有多少呢?一旦你有调试过,你又要花上多少时间来修复这些bug呢?如果你能够大大减少这些时间的话,又会如何呢?
好处还不只这些。如果你采用这种方法,那么每小时你都会产生好几个测试;每天就有十几个;每个月就几百个;一年下来,你所编写的测试就会有数千个。你可以保留着这些测试而且在任何你希望的时候去运行它们!什么时候运行它们呢?随时!只要你做了改动,就去运行它们!
有些代码已经混乱不堪了,可是我们为何不去清理它们呢?我们担心会破坏它们。但如果我们拥有测试,我们就有理由确信这些代码不会被破坏,或是说我们可以很快的就找到被破坏的地方。如果我们有了这些测试,我们就对代码发生改变无所忌惮。如果我们看到混乱的代码,或是一个不清晰的结构,可以毫无顾虑地清理它。因为有了测试,代码重新变得易修改了;因为有了测试,软件重新变“软”了。
好处还不只这些。如果你想要知道如何去调用一个特定的API,就会有一个测试能够告诉你;如果你想要知道如何创建一个特定的对象,就会有个测试能告诉你。你想知道的任何有关这个系统的,都有一个测试能够去示意。这些测试就像是小型的设计文档,小型的代码示例,描述了系统的工作和使用方式。
你是否曾经整合过一个第三方库到你的项目中呢?你拿到一本厚厚的精致的帮助文档,在结尾处,有一沓薄薄的示例附录。你会选择哪个去阅读呢?当然是示例了!那就是单元测试啊!它们是这份文档中最有用的部分;它们是如何使用代码的鲜活的例子;它们是极其详细、完全没有歧义的设计文档,相当的正规甚至可以执行,而且不会与产品代码相脱离。
好处还不只这些。如果你曾有过增加单元测试到一个可以工作的系统中的经历,你会发现那一点儿都不好玩。你很可能会发现想跑通测试,你一定是要么去改变系统中的部分设计,要么就在测试上作假。这是因为你试图去测试的系统并不是基于可测试设计的。例如,你想要测试某个函数f。然而,f调用了另外一个从数据库中删除记录的函数。在你的测试中,你不希望这条记录被删掉,但却没有办法来阻止它。这样的系统就不是基于可测试设计的系统。
当你遵循TDD的三条规则的时候,你的所有代码天生就可测试!而且另一个能形容“可测试”的词汇就是“可解耦”。为了单独的测试一个模块,你就必须把它解耦。所以TDD强制你去解耦这些模块。确实,如果你遵循这三条规则的话,你会发现你可能比起从前来能做出更多的解藕。这就强制了你去创造更好的,低耦合的设计。
收获了所有的这些好处,这些愚蠢的小规则实际上好像就没那么愚蠢了吧。他们实际上很可能是一些基本而又深刻的原则。确实,在我接触TDD之前我曾做过将近30年的程序员,我不认为曾有过什么人教过我什么非常奏效但又基本的编程实践。毕竟三十年意味着很多的经验。但当我开始了使用TDD,我就被这项技术的有效性所震撼,也沉迷其中。我不会再考虑去敲一大堆代码然后平白指望它们能运行成功。对于那种先将一组模块打散,然后希望能够再将它们整合起来,并且让它们在能下个礼拜五前运行起来的做法,我也不再能忍受。在我写程序的时候,每一个决定都由这种让现在开始写的代码能在一分钟后再次执行这样的基本需求所驱使着。
译注:
1,TDD,测试驱动开发。
2,Kata,可理解为武功招式