本书采用通俗易懂的比喻,众所周知的编程语言,短小精悍的工作实例,深入浅出的分析处理——仿佛在和几位世界级的编程高手一边喝茶,一边聊天,他们循序渐进地让读者在不知不觉中进入一个编程的最高境界。
即使是刚刚入门的初学者,也会从中找到读书的乐趣,因为可以从一开始就找到开启面向对象开发的钥匙;随着经验的积累,编程水平的提高,再来看这本书,又会体会到更深层的编程哲学,然后用不同的视角重新审视您的程序。
本书是编程爱好者的启蒙指南,更是作为系统分析人员、测试人员、程序设计人员、软件开发人员以及面向对象程序研究人员等专业人士革新编程思想的必备手册。
测试驱动的面向对象软件开发
Growing Object-Oriented Software, Guided by Tests
(美)Steve Freeman
Nat Pryce 著 王海鹏 等译
● 旧代码带来的新思维
● Robert C. Martin高度评价
● Kent Berk作序推荐
“终于有一本书用丰富的代码揭示TDD和OOD之间的共生关系。本书值得保存。”
——Robert C. Martin
“如果您想成为当前TDD领域的一名专家,就需要理解本书中的思想。”
——Michael Feathers
对于更快交付更好的软件,测试驱动开发(TDD)现在是一种已经确立的技术。TDD基于一个简单的思想:在写产品代码之前先写它的测试代码。但是,这个“简单”的思想需要一些技能和判断才能做得好。现在有了一本TDD实践指南,让您深入理解那些基本概念,并向您展示了如何让测试来引导开发,“培育”出一致的、可靠的、可维护的软件。
作者描述了他们使用的过程,努力实现的设计原则,以及完成工作的一些工具。通过一个详细实现的例子,您可以看到TDD如何在各个层次上工作,如何利用测试来驱动特征开发和代码的面向对象结构,如何利用模拟对象来发现并描述对象之间的关系。在这个过程中,本书系统地讨论了开发团队在使用TDD时遇到的挑战——从过程中集成TDD到测试最难的特征。
本书包括以下内容:
如何有效实现TDD:启动,然后在整个项目中保持你们的冲劲。
如何创建更干净的、更有表现力的、更可维护的代码。
如何利用测试,对可持续的品质保持最严格的关注。
理解在真实软件开发的环境中,TDD、模拟对象、面向对象设计如何交织在一起。
如何利用模拟对象来指导面向对象设计。
如何在TDD困难的地方取得成功:管理复杂的测试数据,测试持久层和并发。
作者简介
Steve Freeman和Nat Pryce 是英国的敏捷软件开发先驱。他们都曾在一些行业和组织机构中工作过,例如电信业系统开发、金融业、体育新闻报道、市场传播、IBM的零售版软件开发、工业和学术研究机构等。他们是伦敦XpDay的创始人和组织者,并且是几个有影响的支持TDD的开源项目的贡献者。在2006年,他们共同获得了敏捷联盟的Gordon Pask奖。
这本书是讲什么的
这是一本实践指南,介绍了我们发现的编写面向对象软件的最好方式:测试驱动开发(testdriven development,TDD)。它描述了我们遵循的过程、追求的设计原则,以及使用的工具。它以我们数十年的经验为基础。在这些年里,我们与世界上一些最好的程序员共事,向他们学习。
本书讨论了一些问题与困惑,它们是我们在一个个项目中所见到的。如何将测试驱动开发应用到软件项目中?我从哪里开始?为什么我应该既编写单元测试,又编写用户场景测试?测试“驱动”开发是什么意思?如何测试某个难弄的特征?
本书同时也着重讨论了设计,以及设计方式对TDD方式所产生的影响。如果说我们从中学到了什么,那就是测试驱动开发作为一个整体时,效果最好。我们曾看到过一些团队虽然采用了一些基本实践(编写并运行测试),但他们仍不得要领,因为他们并没有采用背后更深层次的过程。
为什么“培育”面向对象软件
本书使用“培育”(Growing)这个词是因为它让人们感觉到开发是增量式的。我们在任何时候都有可以工作的软件,确保代码总是尽可能地具备良好的结构,并经过全面的测试。任何事情都不比交付可以工作的系统更有效。正如John Gall在[Gall03]中所写的,“能工作的复杂系统总是从能工作的简单系统演进而来的。”
“培育”也暗示了一个在好的软件中发现的生物学特点,即在每一层结构中一致的感觉。它将我们的方式与面向对象结合起来,正好与Alan Kay的观点一致,即对象像生物细胞一样彼此发送消息。
Alan Kay是Smalltalk的创始人之一,发明了“面向对象”这一术语。
为什么要以测试为“指导”
我们先写测试,因为我们发现这有助于写出更好的代码。先写一个测试迫使我们澄清自己的意图,只有无二义地描述了应该做什么以后,我们才会开始下一步的工作。先写测试的过程帮助我们发现设计是否太僵硬或没有关注要点。然后,当我们希望继续下一步工作并修复设计缺陷时,测试可以提供回归覆盖测试构成的安全网。
本书使用“指导”(Guided)这个词,是因为此项技巧仍然需要使用者的技能和经验。我们发现测试驱动开发是一种有效的设计支持工具——只要我们学会如何增量式地开发并“聆听测试”。像其他正规的设计活动一样,TDD需要理解和持续的努力才有效。
我们曾发现一些团队同时编写测试和代码(甚至有一些团队先写测试),他们的代码仍然一团糟,测试只是增加了维护的成本。他们已经起步,但还没有明白这种技巧是要让测试来指导开发。利用测试的内容,将关注点放在取得进展上,并利用测试的反馈来提升系统的品质。
什么是模拟对象
编写本书最初的动机是完整解释使用模拟对象(Mock Object)的技术,我们看到这种技术常被人误解。随着写作的深入才意识到,我们社区对模拟对象的发现和使用实际上体现了我们写软件的方式。这是大局观的一部分。
模拟对象是一些替代的实现,目的是测试对象如何与相关的对象进行交互。
本书将利用jMock库来展示模拟对象是如何工作的。更具体地说,我们将展示模拟对象应该用于TDD过程的什么位置,以及它在面向对象开发的环境中具有怎样的意义。
这本书为谁而写
这本书是为“有一定知识的读者”而写的。它的目标读者是具有专业经验的开发者,他们至少曾看过测试驱动开发。在编写时,我们假设是向一些以前未接触过这些技术的同事进行解释。
为了留出篇幅以介绍更深入的内容,我们假定读者具备有关基本概念和工具的一些知识。别的一些书对TDD进行了很好的介绍。
这是一本Java书吗
本书从头到尾使用Java语言,因为它非常普及,我们希望读者至少能理解这些例子。也就是说,本书实际上是在介绍任何面向对象环境都适用的一组技巧。
如果您目前不用Java,在许多其他语言中也有与我们使用的测试和模拟库(JUnit和jMock)等价的类库。这些语言包括C#、Ruby、Python、Smalltalk、ObjectiveC,以及(令人印象深刻的) C++。甚至还有更少见的语言的版本,如Scala。在Java中,也有其他的测试框架和模拟框架。
为什么您要听信我们
本书汇集了我们几十年的经验,包括近十年的测试驱动开发。在这段时间里,我们在各种项目中使用TDD。这些项目包括:面向消息的大型企业集成系统(具有交互式Web前端和多处理器计算网格后端)、微型嵌入式系统(必须运行在几十KB的内存中)、用作关键业务系统广告的免费游戏、后端中间件和网络服务(支持高度交互的图形桌面应用)。而且,我们为世界各地的会议和公司讲授这些内容。
我们也从同事的经验中获益,他们来自伦敦的TDD社区。我们花了不少工作时间和业余时间让思想接受挑战和磨炼。我们很感谢能有机会与这样活跃的(善于争论的)同事一起工作。
本书有些什么内容
这本书有五个部分:
第一部分“简介”,是在软件开发项目背景下,对测试驱动开发、模拟对象和面向对象设计的高层次的介绍。同时也介绍了本书其他部分中用到的一些测试框架。即使您已经熟悉TDD,仍然建议您通读第1章和第2章,因为它们描述我们进行软件开发的方法。如果您熟悉JUnit和jMock,您也许愿意跳过简介的其他部分。
第二部分“测试驱动开发过程”,描述了TDD过程,展示了如何开始开发,并让开发进行下去。我们深入探讨了测试驱动开发和面向对象编程之间的关系,说明了这两种技术的原理是如何相互支持的。最后,我们讨论了如何处理外部代码。这一部分介绍了概念,下一部分将这些概念投入实战。
第三部分“工作的例子”,是一个扩展的例子,让您初步体验一下以测试驱动的方式来开发面向对象应用。在整个过程中,我们讨论了一些折中和做决定的动机。这个例子相当长,因为我们希望说明TDD的某些特征是怎样随着代码规模的扩大变得更为重要的。
第四部分“可持续的测试驱动开发”,描述了一些保持系统可维护的实践。我们现在非常注意保持代码整洁和富有表现力,因为这些年来,我们已经了解了代码变差的代价。这部分描述我们采用的一些实践,并解释了为什么这么做。
第五部分“高级主题”,探讨了TDD更难的一些方面:复杂的测试数据、持久性和并发性。我们展示了处理这些问题的方法,并讨论了这对代码的设计和测试所产生的影响。
最后,附录包含了关于jMock和Hamcrest的一些支持材料。
本书不包含什么内容
这是一本技术书籍。我们不讨论使项目成功的其他主题,如团队组织、需求管理和产品设计。采用增量式的、测试驱动的方法来开发显然与项目运作的方式有着密切的关系。TDD支持一些新的活动,例如频繁交付;它也会被一些组织环境破坏,例如早期设计冻结或利益相关人之间缺乏沟通。同样,有许多其他书籍讨论这些主题。
计算机\软件工程
“本书的作者领导了编程手艺的一场革命,他们控制了软件培育的环境。”
—Ward Cunningham
“终于有一本书用丰富的代码揭示了TDD和OOD之间的共生关系。这本书值得保存。”
—Robert C. Martin
“如果您想成为当前TDD领域的一名专家,就需要理解这本书中的思想。”
—Michael Feathers
对于更快交付更好的软件,测试驱动开发(TDD)现在是一种已经确立的技术。TDD基于一个简单的思想:在写产品代码之前先写它的测试代码。但是,这个“简单的”思想需要一些技能和判断才能做得好。现在有了一本TDD实践指南,让您深入那些基本概念。两位TDD的先锋拥有10年的真实世界系统开发经验,向您展示了如何让测试来引导开发,“培育”出一致的、可靠的、可维护的软件。
Steve Freeman和Nat Pryce 描述了他们使用的过程,他们努力实现的设计原则,以及帮助他们完成工作的一些工具。通过一个详细实现的例子,您可以看到TDD如何在各个层次上工作,如何利用测试来驱动特征开发和代码的面向对象结构,如何利用模拟对象来发现并描述对象之间的关系。在这个过程中,本书系统地讨论了开发团队在使用TDD时遇到的挑战—从在您的过程中集成TDD到测试最难的特征。内容包括:
l 有效实现TDD:启动,然后在整个项目中保持你们的冲劲。
l 创建干净的、更有表现力的、更可维护的代码。
l 利用测试,对可持续的品质保持最严格的关注。
l 理解TDD、模拟对象、面向对象设计如何在真实软件开发的环境中交织在一起。
l 利用模拟对象来指导面向对象设计。
l 在TDD困难的地方取得成功:管理复杂的测试数据,测试持久层和并发。
(美)Steve Freeman; Nat Pryce著:Steve Freeman和Nat Pryce是独立的软件顾问,他们是英国的敏捷软件开发先锋。他们都曾在一些行业和组织机构中工作过:电信业系统开发、金融业、体育新闻报道和市场传播、IBM的薄膜包装应用、工业和学术研究机构等。他们是伦敦XpDay的创始人和组织者,经常出席和组织国际会议。Steve和Nat是几个有影响的开源项目的贡献者,这些项目支持TDD。在2006年,他们共同获得了敏捷联盟的Gordon Pask奖。他们住在英国伦敦。
王海鹏 等译:暂无简介
这是一本关于利用模拟对象进行测试的书。类因职责而存在,对象根据契约与它的协作者进行交互。用模拟对象进行测试的要点就是检查对象之间的契约。这些契约要明确无误,没有二义性,所以用模拟对象进行测试对类的设计有着很高的要求。
这是一本关于测试与面向对象设计如何相互影响的书。易测试的设计就是好设计,难测试的设计是不好的设计,测试成本高的设计是昂贵的设计,无法测试的设计是不可行的设计。当我们用心聆听测试代码的反馈,它就会告诉你产品代码的功能正确性、运行效率、可靠性、API的易用性、可维护性、可移植性和可伸缩性。
这是一本关于增量式软件开发的书。我们在写程序的过程中学习,而不是学习如何写程序。软件开发者与领域专家的合作是一个学习的过程。软件一开始可能只有一个特征、一些模糊的概念,然后逐渐发展出许许多多更清晰的特征和概念,并且在使用过程中会不断发展。在我看来,增量式的开发是唯一的软件开发方式。
这本书的目的在于,它告诉我们如何进行增量式的软件开发。爱因斯坦说,例子不是一种教学方式,而是唯一的教学方式。书中提供的拍卖狙击者的例子,提供了在真实世界软件开发者所需要的细节,一个特征接一个特征地教读者利用面向对象的思想,增量式地开发灵活的、面向对象的程序。
过程为我们带来乐趣,成就只是副产品。开发的过程是否有趣?是否像打怪升级的游戏一样?每一个已实现的特征就是被你杀死的怪,你不希望被杀死的怪经常复活,向你寻仇。不好的程序是地狱,因为那里面经常复活的怪太多。这本书教我们写下清晰的特征列表,然后用验收测试来确保这些特征的实现。
本书给我最大的感悟是:如果软件是孩子,程序员就是家长,需要用心血去培育。如果软件是诗歌,程序员就是炼字师,可以用编程语言来准确反映自己的思想。值得去写的程序,就值得写好。我们应该做得更好,也可以做得更好。
“知之不如好之,好之不如乐之。”对于软件开发,作者有一种乐此不疲的态度。编程是一门艺术,在追求艺术的道路上,没有止境。
本书改变了我对代码(尤其是对测试代码)的看法。像所有好书一样,它可以改变人们对事物的看法。开卷有益,因此,我郑重地向大家推荐它。
除封面署名外,参加本书翻译工作的人员还有:王海燕、李国安、周建鸣、范俊、张海洲、谢伟奇、林冀、钱立强、甘莉萍。
王海鹏
庚寅年初春于上海
对本书的赞誉
译者序
序
前言
作者简介
致谢
第一部分简介
第1章测试驱动开发的要点
11软件开发是一个学习过程
12反馈是基本工具
13支持变化的实践
14测试驱动开发简介
15大局
16用户场景测试
17测试的级别
18外部品质与内部品质
第2章测试驱动开发与对象
21对象之网
22值与对象
23对象通信
24吩咐,不要问
25但有时要问
26对协作的对象执行单元测试
27用模拟对象支持TDD
第3章工具介绍
31如果您已了解这些框架,可以跳过本章
32JUnit 4简介
321测试用例
322断言
323预期异常
324测试装置
325测试执行者
33Hamcrest匹配器和assertThat()
34jMock2: 模拟对象
第二部分测试驱动开发过程
第4章启动测试驱动循环
41简介
42先测试一个可行走的骨架
43决定行走的骨架的形状
44创建反馈源
45尽早暴露不确定性
第5章保持测试驱动循环
51简介
52每个特征都从一个验收测试开始
53分离测量进度的测试和捕捉回归
错误的测试
54从最简单的成功场景开始测试
55编写您愿意读的测试
56看着测试失败
57从输入开发到输出开发
58针对行为进行单元测试,而非针对
方法
59聆听测试
510调整循环
第6章面向对象风格
61简介
62为可维护性而设计
63内部与同级的比较
64没有“与”、“或”、“但是”
65对象同级构造型
66组合比它的部分之和更简单
67上下文无关性
68正确地隐藏信息
69固执己见的观点
第7章实现面向对象设计
71先写测试怎样有助于设计
72通信比分类更重要
73值类型
74对象来自何处
741分解
742萌芽
743打包
75利用接口确定关系
76接口也要重构
77组合对象以描述系统行为
78迈向更高层的编程
79关于类
第8章基于第三方代码构建
81简介
82只模拟您拥有的类型
821不要模拟您不能修改的类型
822编写一个适配层
83在集成测试中模拟应用对象
第三部分工作的例子
第9章委托开发一个拍卖狙击者
91从头开始
92与一次拍卖通信
921拍卖协议
922XMPP消息
93安全实现目标
94这不是真的
第10章可行走的骨架
101从壁橱中取出骨架
102我们的第一个测试
103一些初始选择
1031用户场景测试
1032准备开始
第11章通过第一个测试
111构建测试的装配
1111应用执行者
1112伪造的拍卖
1113消息代理
112测试失败和通过
1121第一个用户界面
1122显示狙击者状态
1123连接到拍卖
1124从拍卖接收回应
113必需的最小实现
第12章准备竞拍
121对市场的介绍
122针对竞拍的测试
1221从测试开始
1222扩展伪造的拍卖
1223令人吃惊的失败
1224由外至内开发
1225对细节的无限关注
123AuctionMessageTranslator类
1231提取出一个新类
1232第一个单元测试
1233完成用户界面循环
1234我们实现了什么
124解析价格消息
1241引入消息事件类型
1242第二个测试
1243发现进一步的工作
125完成工作
第13章狙击者发出竞拍出价
131引入AuctionSniper
1311一个新类及其依赖关系
1312关注、关注、关注
132发送竞拍出价
1321Auction接口
1322AuctionSniper发出竞拍出价
1323利用AuctionSniper成功竞拍
1324用户场景测试通过了
133整理实现
1331提取出XMPPAuction
1332提取用户界面
1333整理翻译者类
134延迟决定
135自然发生的设计
第14章狙击者赢得拍卖
141先写一个失败的测试
142谁知道竞拍者
143狙击者还有话要说
144狙击者需要某种状态
145狙击者获胜
146取得稳定的进展
第15章迈向真正的用户界面
151更现实的实现
1511接下来我们该做什么
1512替换JLabel
1513还是很丑
152显示价格细节
1521先写一个失败的测试
1522狙击者送出状态
1523展现竞拍狙击者
153简化狙击者事件
1531跟着感觉走
1532重新确定sniperBidding()的目标
1533填入数字
154更进一步
1541转换胜利和失败
1542修整表模型
1543面向对象的列
1544缩短事件路径
155最后润色
1551针对列标题的测试
1552实现TableModel
1553目前已足够
156短评
1561单一职责
1562软件微创手术
1563程序员过敏症
1564庆贺思维转变
1565这不是唯一的解决方案
第16章狙击多项物品
161针对多项物品的测试
1611两件物品的故事
1612ApplicationRunner类
1613偏离主题,改进失败信息
1614重新设计Main的结构
1615扩展表模型
162通过用户界面添加物品
1621更简单的设计
1622更新测试
1623添加一个动作条
1624设计时刻
1625另一层次的测试
1626实现UserRequestListener
163短评
1631取得稳定的进展
1632TDD的秘密
1633发布它
第17章分解Main
171发现角色
172提取Chat
1721分离Chat
1722封装Chat
1723编写一个新测试
173提取Connection
174提取出SnipersTableModel
1741狙击启动者类SniperLauncher
1742狙击组合
175短评
1751增量式架构
1752三点不动
1753动态设计的同时也进行静态设计
1754对notToBeGCd的另一种修复方法
第18章填充细节
181更有用的应用
182适可而止
1821引入落后状态
1822第一个失败的测试
1823输入停止价格
1824传送停止价格
1825约束AuctionSniper
183短评
1831增量式设计用户界面
1832其他建模技术也有用
1833领域类型比字符串好
第19章处理失败
191如果它不能工作
192检测失败
193显示失败
194断开狙击者
195记录失败
1951填充测试
1952翻译者中的失败报告
1953生成日志消息
1954完成这次开发循环
196短评
1961 “切香肠的逆过程”式开发
1962用一些小方法来表达意图
1963日志也是一项功能
第四部分可持续的测试驱动开发
第20章聆听测试
201简介
202我需要模拟一个不能替换的对象
2021单例是依赖关系
2022从过程到对象
2023隐式依赖也是依赖
203记日志是一项功能
2031通知而不是记日志
2032但这种想法很疯狂
204模拟具体的类
205不要模拟值类型
206膨胀的构造方法
207令人困惑的对象
208太多依赖关系
209太多预期
2010测试会告诉我们什么
第21章测试可读性
211简介
212测试名称描述功能
213规范的测试结构
214精简测试代码
2141用结构来解释
2142利用结构来共享
2143强调正面
2144代理给从属对象
215断言和预期
216具体值和变量
第22章构造复杂的测试数据
221简介
222测试数据建造者
223创建一些类似的对象
224组合建造者
225利用工厂方法强调领域模型
226从使用的角度消除重复
2261首先,消除重复
2262然后,让游戏升级
227沟通第一
第23章测试诊断
231要的就是失败
232小、专注、良好命名的测试
233解释性断言消息
234利用匹配器对象来突出细节
235自描述的值
236明显的预装值
237跟踪者对象
238明确断言预期得到满足
239诊断是一级功能
第24章测试的灵活性
241简介
242针对信息测试,而非针对表示方法
243准确断言
244准确预期
2441准确的参数匹配
2442允许和预期
2443忽略不相关的对象
2444调用次序
2445jMock States的威力
2446更为自由的预期
245“豚鼠”对象
第五部分高 级 主 题
第25章测试持久性
251简介
252隔离影响持久状态的那些测试
253明确测试的事务边界
254测试一个执行持久操作的对象
255测试对象能够持久
2551来回转换持久对象
2552来回转换相关的实体
256但数据库测试很慢
第26章单元测试与线程
261简介
262分离功能和并发策略
2621并发地搜索拍卖
2622引入Executor
2623实现AuctionSearch
263对同步进行单元测试
2631针对AuctionSearch的压力测试
2632两次修复竞争条件
264对被动对象进行压力测试
265同步测试线程和后台的多线程
266单元压力测试的局限性
第27章测试异步代码
271简介
272取样或监听
273两种实现
2731捕获通知
2732轮询变更
2733超时
2734改进探测类
274轻易成功的测试
275错过更新
276测试没有效果的活动
277区分同步和断言
278事件源外部化
后记模拟对象简史
附录AjMock2速查手册
附录B编写Hamcrest Matcher
参考文献