导言
在过去两年里,我们公司发起、指导、培训极限编程(eXtreme Programming,XP)并提供咨询。我们相信XP是开发高质量软件的最佳技术,因为它强调沟通、简单、测试和快速反馈。通过许多业已被证实的实践,XP取得了应有的成就。在这篇文章里,我们只打算集中讨论这些实践之一 — 测试驱动的开发(Test Driven Development,TDD)。
很多公司害怕因为这样或那样的原因无法做到“eXtreme”,但我们难以想象一个公司或过程(process)提倡不对其软件进行测试。TDD技术可以(也应该)应用于任何软件开发环境。实施TDD甚至不需要你的老板允许,因为它是作为编程活动的一部分而不是作为项目单独的一个阶段而完成的。尽管测试、设计和编程是三种截然不同的活动,但我们并不以“时间”来区分这些活动 — 它们是并行、持续地完成的。本文展示的例子示范了其运作方法。
几乎所有关于XP或TDD的出版物都使用支持面向对象开发的编程语言。这是可以理解的,因为XP出身于Smalltalk社群并且许多(但不全是)公司事实上都在使用某种OO语言。
我们最近的一个客户希望实施TDD。实际上,他们希望实施所有XP实践。问题在于他们公司使用的是C语言,并且在可以预见的将来将一直使用C语言,因此我们不得不卷起袖子来合计如何在C语言中受益于TDD。不幸的是,C语言不(直接地)支持多态,而后者正是TDD所使用的技术的一个基础性的概念。
在TDD中,一次开发一个模块。为了正确地测试一个给定的模块,它必须与它所交互的其他大多数模块相隔离。在像C++和Java这样的OO语言中,我们可以使用多态接口来隔离测试模块,同时这些接口还为模块提供了一个测试环境。我们需要在C语言中具有同样的能力。
我们利用在嵌入系统项目中的工作经验。其中,要么硬件还没有做好,要么硬件还没有到我们手上。在这些环境中,我们不得不“根除(stub out)”访问硬件的函数调用。为了能够像受益于支持多态的语言所提供的东西那样获益,这种“根除”技术正是我们需要的。实际上,“根除”就是多态,我们称之为连接期多态。
TDD
TDD是这样的一种编程风格:先于被测试的代码编写测试。一开始测试甚至连编译也通不过,因为被测试代码都还没写呢。然后我们编写足够的代码让所有东西通过编译,但是故意编码致使测试失败。然后我们编写代码使这个测试通过。对于TDD过程来说,这每一步都非常关键。
每当通过测试,我们就对代码进行重构,这样,代码将变成我们能够生产的质量最佳的代码。我们努力使代码对于迄今为止已经实现的功能而言具有最可能佳的设计 — 但不会超过该功能哪怕一点点。只有当完成朝着这些目标的重构,我们才继续前进去编写另外一个测试。在整个开发日里,我们快速持续地重复这个“测试—编码—重构”周期。
你用一个小时编写代码,然后花费一整天使它通过编译和调试,想一想这样的情况发生有多么频繁?我们可不喜欢那么做,那样一天的大多数时间里都有一种失败感(因为没有在第一时间把代码写对)。不是编写一段(可能是很大的一段)功能代码,TDD要求我们工作于非常小的问题,解决之,然后继续前进到下一个非常小的问题。毕竟,每一个大而复杂的问题都是由一串小问题构成的。这么做,我们发现可以整天都保持成功。一个一个地解决小问题,让人可以忍受、感觉快乐并具有非常高的生产力。
这一系列的成功的关键在于快速反馈。只要我们编写了足够的代码,有那么编译一次的希望,我们就运行编译器。每写一行代码就编译一次也许并不切合实际,但正如你可能想象的那样,这种做法离那也不远了。我们尽可能频繁地运行测试。我们的规则是不超过10分钟就运行所有测试一遍,5分钟更好,1分钟也