本书并不是一本有关测试的书籍。
这是一本可以使代码变得简单、清晰、健壮的编程方法。它可以使代码易于设计、编写、阅读、理解、扩展和维护。
这是一本有关思考、设计、沟通以及编程的书。作为一种非常好的副作用,我们最终将会得到一套详尽的(1)测试集。
本书探讨的内容是测试驱动开发(Test-Driven Development)或测试优先编程(Test-first Programming),不管你怎么称呼它,它都是在指一种先编写测试,然后再编写让测试获得通过的代码的方法。具体而言就是以尽可能小的步伐推进:编写足以失败的测试代码,编写足以让测试通过的产品代码,通过重构来整理为使测试通过而引入的混乱代码。
本书着重使用Java编程语言,通篇都使用用Java编写的例子。我们假定读者至少对Java(如果想要亲自动手尝试一下书中的例子的话,还要有一套能够工作的Java系统)有中等程度的了解。大家可以从我的Web站点[URL 54]获得范例代码以及其他一些辅助资料。
尽管我们将重点放在Java上,但是本书的第四部分还是查看了其他一些重要的针对几种流行编程语言的xUnit家族成员。我们选取第10章中的第一项任务,分别用各种语言重新予以实现,这样就各种不同的测试框架而言提供了很好的比较。
(1)究竟有多么详尽要看你对TDD掌握的有多好。
极限编程
测试驱动开发(Test-Driven Development)是由Kent Beck形式化(formalized)的一种称做极限编程(eXtreme Programming - XP)的敏捷软件开发过程(agile process)的核心部分。XP很可能是敏捷软件开发过程中最“敏捷”的部分了,它开销极底,繁缛极少,然而却是一种高度自律,非常高效的软件开发方法,对变化有着难以置信的适应能力。
也就是说,为了运用测试驱动开发方法(TDD)进行开发并从中获益,您不必采用极限编程过程(XP)。单就测试驱动开发本身而言也是值得的,你的产品代码质量一定会改善。当然如果采用极限编程过程的话,那么真正熟练地使用测试驱动开发方法对于选用这种开发过程来说才是值得的。
测试驱动开发是极限编程中一项主要的设计工具(2)。如前所述,产品开发终了所拥有的整套测试是一种非常令人愉快的副产品。因为有了那些测试,当我们在代码修改前后运行测试并且通过的话,就可以确信自己在此期间没有意外地引入使产品无法正常运行的错误。反之,如果某项测试在代码修改以后无法运行通过,那么我们就可以知道究竟是哪个地方出了问题,而能够方便的找出问题并且修正它。唯一可能造成错误的位置就是在上次测试运行通过以后修改的地方。
(2)另一项是重构。
所有这一切都意味着由于测试的存在,我们可以安全地使用极限编程的另一种方法:重构(Refactoring)。正如我们将要在第12章看到的,重构就是在不改变代码外部行为的情况下对代码的结构进行调整。我们有测试可以保证行为没有被改动。这样你就有了修改(有时这种改动是翻天覆地的)工作代码所必需的自信,而我们最终的代码也会更清晰,可扩展性更好,可维护性更强而且更易于理解。
附录A更多的谈到了有关极限编程的内容。要想更多的了解有关这方面的知识,您可以浏览一下参考文献或访问附录C中所列出的极限编程(XP)联机资源。
谁应该阅读本书?
. 你应当读这本书吗?我写这篇前言的目的就是要问答这个问题。以前在雅虎极限编程讨论组(XP Yahoo Group)中有一项关于图书的前言应当服务于什么目的的调查。大家总体的意见是通过阅读前言应当能够比较清楚的知道自己是否应当购买或阅读某一本书。希望我在此很好的履行了这一种义务。
如果你想采用极限编程,那么就请阅读本书。如前所述,要想能够较好地掌握极限编程,那么花些时间和精力来较好的掌握测试驱动开发是值得的。测试驱动开发在极限编程中处于核心位置,因此较好的掌握了测试驱动开发就可以使整个极限编程过程的效率大为提高。
如果你想编写出更加清晰、健壮、扩展性强而又尽可能精练(而不是臃肿)的代码,那么请阅读本书。
如果你对在开始编写代码之前花费数周或数月时间绘制各种设计图例心存异议的话,那么请阅读本书。
最后,如果你想知道如何才能使编程工作再度充满乐趣的话,那么就请阅读本书。
在阅读本书之前,就所应当了解的知识而言,如果你对Java至少有中等程度以上的了解,那么会更好一些。如果你以前对其他几种面向对象编程语言(如Smalltalk、C++、Python或Ruby)有较好的掌握的话,那么就可以从本书中汲取到更多有用的知识。
在本书付印的时候,市面上还有另外一本有关测试驱动开发的书(我知道大家对这本书肯定趋之若骛)。在本书编写大半的时候,我了解到还有这样一本书也正在编写,所以作为对那本书的补充则一直是本书所要达到的部分目标。通过阅读那本书,你能够领悟测试驱动开发本质和抽象层面的东西,那本书也包含了足够的实用范例。如果你倾向于那本书,那么我鼓励你先阅读它。现在你手里拿的这本书,正如书名所讲的,是一本有关测试驱动开发的实用指南。本书把重点放在一种语言(虽然不是最好的语言,但无可争辩的是一种非常流行的同时也是对测试驱动开发支持良好的语言)上,不仅阐述了测试驱动开发的概念和原则,同时也对各种工具和技术进行了清晰的阐述。
本书的结构
本书分为四个部分:
I 背景知识介绍 在第一部分我们考察了一些与书中的主要内容(即利用Java来进行测试驱动开发)有关的话题。我们开始先对测试驱动开发作一介绍。然后分章节讲述有关重构(refactoring)和意图导向编程(programming by intention)的知识。这两种技术在极限编程中也是非常重要的,是测试驱动开发所要求的,同时又是测试驱动开发才使之成为可能的。
II 工具与技术 在第二部分我们深入探讨了各种对使用Java来进行测试驱动开发有用的工具以及如何来使用这些工具。我们先是提供了一篇介绍JUnit的教程,JUnit是Java测试驱动开发框架事实上的标准。接着我们探讨了一些JUnit的标准或非标准扩展。随后探讨了一些支持使用JUnit的工具以及其他一些完全独立于JUnit但是能够与之很好配合的工具。这一部分的最后一章考察了某些特定的技术问题和其相关工具。
III一个彻头彻尾的采用TDD开发的Java软件项目 这是一本重在亲身实践的实用指南。所以在第三部分,我们围绕如何开发一个真实的系统来展开。这可不是一个随随便便的例子。我们将采用测试驱动开发的方法来开发这个项目。在此过程中,我们将会用到书中前几部分所讲的内容。
IV xUnit一族JUnit只是大的正在成长中的程序员测试框架(programmer test framework)中的一个成员。我们将在第四部分查看一下这个家族中的其他一些成员。我们不会把所有的成员讲解一遍,但是会讲到几种较为流行的编程语言的测试框架。我们针对每种测试框架,根据同一组描述(即需求),使用不同的语言重新编写了程序,这样它们之间也就有了一个很好的比较。具体而言,这几个描述就是Java项目中开始的那几个描述。同时这也可以使我们拿各种测试框架与JUnit作一比较。
本书有四个附录:
A 极限编程 这个附录简明扼要的介绍了有关极限编程的知识。
B 敏捷建模 该附录概括描述了有关敏捷建模的内容。
C 联机资源 在本书讲解过程中我引用了一些Web站点,大家可以在这些站点上找到一些相关的信息或下载资源。该附录提供了附带注解的分类资源列表。
D 练习答案 书中的许多章节都包含有让读者完成的练习。该附录包含了书中的全部练习和参考答案。
为了便于大家阅读,我在书中使用了不少可视化的约定表达方式,用以区分不同种类的信息。
源代码 本书包含了大量的源代码,代码行缩进并以sans-serif字体来表示, 如下所示: public int getAverageRating() { return totalRating / numberOfRatings;
当在正文中引用一行代码的部分内容时,仍然使用同样的字体来表示。这些内容通常包括类名(Movie),方法名(equals())和常量(true, “a string”, 42)。
总体而言,在引用某个方法的时候,我们没有把参数也包括在内,而是使用空括号,这样就可以清楚的表示这是一个方法而不是某种其他类型的标识符,例如aMethod()。
我们一般会省略成块代码中的package和import语句。
文件系统及主控I/O 我们使用等宽serif字体来表示指代文件系统的内容。这些内容包括文件名(filter.properties)、命令及其输出等: P-xviii 建议与旁注 我使用了多种不同的表现形式来强调那些值得注意或是有趣但因为某种原因不适合在正文中叙述的重要信息。
这本书中穿插了一些小经验,也许您会觉得特别有用,所以我们把这些内容单独分出来,本段内容就是出于这个目的。
这是一段非常简短的旁注的例子;大多数旁注都只有半页或一页的篇幅。
我使用旁注来把那些与正文没有直接关系的小段文字分离出来。这些旁注的位置是由LaTeX自由设定的,通常位于每一页的顶端。这里就有一个旁注的例子。
术语 我是在Smalltalk下学习有关面向对象的知识的,所以从开始我所使用的是Smalltalk的术语。我列出了几个我所使用的术语以及它们在Java和C++对应的含义,以免您对Smalltalk不熟悉:
实例变量(instance variable)这种变量的作用域是对象。每个对象都有该变量的独立拷贝。(Java:域或成员变量, C++:数据成员。)
类变量(class variable)这种变量的作用域是类。类的所有实例都共用该变量的一个拷贝。(Java:静态域, C++:静态数据成员。)
方法(method)类的函数成员。(Java:方法, C++:成员函数。)
把消息发送给一个对象 这是一种更为抽象的指代调用对象方法的表达。
发送者 发送特定消息的方法,也就是调用特定的方法(通常称做方法的引用)。