在第3章中,我们设计了一个应用程序,并且快速地创建了几个测试来验证你的设计。在我们编写这些测试时,这些测试也帮助改善了最初的设计。随着你编写越来越多的单元测试,正反馈的良性循环会鼓励你尽早地编写单元测试。当你设计并实现时,就自然而然地想要知道你将会如何来测试一个类。基于这一方法论,越来越多的开发者正在从利于测试的设计跃迁到测试驱动开发。
DEFINITION 测试驱动开发(TDD):测试驱动开发是一项编程实践,它要求开发者只有在自
动测试失败的情况下才编写新的代码,并且要消除重复。测试驱动开发的目标是“能正常工作的干净代码”。
下面我们要探讨一下如何调整我们的开发周期,以执行测试驱动开发方法。
当你开发代码的时候,你会设计一个应用程序编程接口(API),然后实现接口所提出的功能。当你进行单元测试的时候,你会通过一个方法的API来验证功能。测试实际上就是方法API的一个客户,正如你的领域代码是方法 API的一个客户。
传统的开发周期是由以下环节组成的:编码、测试、(重复)、提交。开发者们使用TDD后则做出了一个似乎很微小但实际上却惊人有效的调整:测试、编码、(重复)、提交。(在这以后还有很多。)测试推动了设计,并成为了方法的第一个客户。
代码5.7展示了单元测试是如何对设计与实现起到帮助作用的。getBalance0k方法显示了Account的getBalance方法将账户余额作为一个长整型(long )返回,并且这个余额可以在 Account构造函数中设置。在这一点上,Account的实现纯粹是假定的,但是编写单元测试可以让你把注意力集中在代码的设计上。一旦你实现了类,你就可以运行测试来证明这个实现是能正常工作的。如果测试失败了,你则可以继续在实现上埋头苦干,直到它通过测试。当测试通过,你就知道你的代码完全满足了契约的要求。
当你将测试作为方法的第一个客户来使用时,那就很容易把全部注意力集中在API上。优先编写测试会带来以下:
设计代码的方法;
关于代码如何工作的文档;
对于代码的单元测试。
即便是项目的新成员也可以通过学习功能测试集(高层次的UML图也会有所帮助)来理解系统。为了能详细地分析应用程序中的特定部分,新成员可以将它分解成独立的单元测试。
前面我们说过,通过“测试、编码、(重复)、提交”这样一个流程,TDD加快了开发的周期。可是问题在于,这里遗漏了一个很重要的步骤。实际上的流程应该更像这样:测试、编码、重构、(重复)、提交。TDD的核心原则是:
1 在编写新代码之前编写一个失败的自动测试;
2 消除重复。
消除重复这个步骤确保了你编写的代码不仅具备可测试性,还具备可维护性。当你消除了重复时,你就能够增加内聚,减少依赖。这些都是易于长期维护的代码的特征。
别的编码实践鼓励我们通过预知改变来编写可维护的代码。与它们不同的是,TDD鼓励我们通过消除重复来编写可维护的代码。遵循这个实践的开发者发现,有测试作为支撑并且划分良好的代码自然而然地可以简单而安全地承受改变。TDD给了我们信心:今天的问题今天解决,明天的问题明天解决。及时行乐啊!
JUnit最佳实践:首先编写失败的测试
如果你用心学习TDD开发模式,就会发现一件有趣的事情:在你能写任何代码之前,你必须要写一个失败的测试。它为什么会失败呢?那是因为你还没有写出使它成功的代码。
面对这样一种情况,我们中的大多数人会开始写一个简单的实现来使测试通过。那么现在,测试成功之后,你就能止步并转移到下一个问题。为了做得更专业点,你会花几分钟时间来重构实现,以便消除冗余、明确目的,并优化在新代码中的投入。但是,一旦测试成功,从理论上来讲,你就完成任务了。
最后的游戏了?如果你总是测试先行,那么如果没有测试失败,你将永远不会写一行新代码。
现在我们已经介绍完了测试驱动开发的周期——测试、编码、重构、(重复)以及提交,接下来我们将要讨论如何将测试融人到整个开发周期中。