本书讲解如何用单元测试引领开发工作, 以解决业务领域中的复杂问题。本书把需求划分成多个比较小的功能, 并分别予以实现。无论采用哪种编程语言与编程框架, 你都可以把书里的知识运用到日常的编程工作之中。本书包含下列内容: 用 TDD 把业务领域中的复杂问题划分成多个小的功能, 并分别予以实现; 如何在各种编程语言里面, 用各种测试框架来做测试驱动开发 (TDD)。
本书采用一个完整的项目贯穿各章,作者全程示范了如何把项目拆解成多个小功能,并依次采用测试驱动的方式实现这些功能。每实现一个功能,作者都会演示如何通过重构,把这个功能的代码乃至项目的结构调整得更好。作者采用三种编程语言讲解,这不仅能吸引更多的人来阅读,而且让我们能够观察各种编程语言在设计思路与实现细节方面的异同,进而养成一种超越编程语言的设计思维。大家在书中会看到如何结合具体语言的优势,以清晰的代码将这种思维表达出来。此外,作者还指引我们把这个项目提交到本地的 git 代码库中,以便做版本管理,并且告诉我们如何将本地仓库与远程的 GitHub 库同步,以及如何通过脚本制作 CI(持续集成)管道,让项目代码每次推送到远程库之后都能够自动接受测试。
测试驱动开发(Test-Driven Development,TDD)这种编程手法或许很早就出现了,它后来能够重新得到关注的其中一个原因在于,有一群开发者很乐意通过各种实例向我们介绍该手法的好处。我刚学编程的那几年,依然在用老办法写程序,虽然也做过一些测试,但都是实现完功能之后补写的,直到 2007 年看了 Kent Beck 的《测试驱动开发》(Test-Driven Development: By Example),才发现原来程序可以这样写。
从最基本的流程上说,TDD 可以概括成三个环节:首先,写一项测试,以描述有待实现的某个功能,并在其中做出断言,以表示产品的执行效果必定与期望的效果相符,由于我们还没有编写相关的产品代码,因此这样的测试几乎总是无法通过;其次,用极其简单的方式(甚至可以是硬代码)来编写产品,让这项测试能够通过;最后,重构产品与测试代码,让两者都变得更加准确、清晰。
测试代码明确地描述了需求,促使开发者必须写出满足该需求的产品代码,这确保我们不会走错方向。测试越写越多,这些测试所覆盖的范围也逐渐扩大,这促使我们把代码写得越来越通用,但是,由于我们在每一轮迭代时都力求用最简单的写法让测试通过,因此不会像以前那样总是想一下子就写出极为通用的代码,从而导致过度设计。另外,有这套测试做保障,就不用害怕新编的代码会破坏已经写好的功能了,因为假如出现那样的情况,就会有测试无法通过,从而提醒我们注意该问题。
现在距离《测试驱动开发》一书面世已经20多年了,但仍有一些朋友尚未尝试过TDD,还有一些虽然尝试过,但尚未完全了解它的理念并体会到它的好处。如果说 Kent Beck 重新发现了 TDD,那么 Saleem Siddiqui 的这本书则有可能让 TDD 变得更加流行。
本书采用一个完整的项目贯穿各章,作者全程示范了如何把项目拆解成多个小功能,并依次采用测试驱动的方式实现这些功能。每实现一个功能,作者都会演示如何通过重构,把这个功能的代码乃至项目的结构调整得更好。作者采用三种编程语言讲解,这不仅能吸引更多的人来阅读,而且让我们能够观察各种编程语言在设计思路与实现细节方面的异同,进而养成一种超越编程语言的设计思维。大家在书中会看到如何结合具体语言的优势,以清晰的代码将这种思维表达出来。此外,作者还指引我们把这个项目提交到本地的 git 代码库中,以便做版本管理,并且告诉我们如何将本地仓库与远程的 GitHub 库同步,以及如何通过脚本制作 CI(持续集成)管道,让项目代码每次推送到远程库之后都能够自动接受测试。
总之,这是一本流畅而连贯的教程,能让大家很快喜欢上TDD。当然,TDD 本身仍有一些地方尚待思考,例如怎样测试图形界面以及多线程的代码等,希望大家结合自己的实际工作来探索这些问题。
Saleem Siddiqui是一位软件开发者,他也参与培训、演讲和写作。他具有丰富的技术开发经验,在大大小小的团队中开发过医疗、零售、政务、财务以及制药等方面的软件。Saleem将在本书中分享自己过去的经验与教训,帮助大家避开他以前编写软件时犯的错误。
第0章 简述如何配置开发环境21
0.1 配置开发环境21
0.2 小结28
第一部分 入门
第1章 我们要解决的问题:Money31
1.1 TDD 的基本流程:红-绿-重构循环31
1.2 我们要解决的是什么问题32
1.3 第一个失败的测试33
1.4 让测试通过37
1.5 清理代码41
1.6 提交变更44
1.7 小结45
第2章 通过Money实体支持多种货币48
2.1 开始支持欧元48
2.2 让代码遵循DRY原则50
2.3 刚才不是说要遵循 DRY 原则吗?现在为什么要保留两个相似的测试52
2.4 分而治之(实现除法)53
2.5 清理代码57
2.6 提交变更60
2.7 小结60
第3章 通过Portfolio实体支持投资组合62
3.1 设计下一个测试62
3.2 提交变更71
3.3 小结71
第二部分 模块化
第4章 关注点分离75
4.1 测试代码与产品代码75
4.2 模块化78
4.3 去除冗余(消除重复)79
4.4 小结80
第5章 Go语言的包与模块81
5.1 把代码分割到不同的包中81
5.2 Go 语言的模块82
5.3 创建新包84
5.4 封装86
5.5 消除测试中的重复88
5.6 提交变更88
5.7 小结88
第6章 JavaScript的模块90
6.1 把代码划分成多个模块90
6.2 认识 JavaScript 模块92
6.3 改进测试96
6.4 提交变更104
6.5 小结105
第7章 Python的模块106
7.1 把代码划分成多个模块106
7.2 消除测试中的重复108
7.3 提交变更108
7.4 小结108
第三部分 功能与重新设计
第8章 求Portfolio的值111
8.1 处理币种不同的Money111
8.2 提交变更119
8.3 小结119
第9章 这种钱,那种钱120
9.1 制作映射表以便查询汇率120
9.2 提交变更127
9.3 小结128
第10章 错误处理129
10.1 我们想把错误处理机制实现成什么样子129
10.2 提交变更141
10.3 小结142
第11章 通过Bank实体重新设计143
11.1 依赖注入144
11.2 把所有实体汇聚起来145
11.3 提交变更166
11.4 小结166
第四部分 收尾
第12章 掌握测试顺序171
12.1 修改汇率172
12.2 提交变更180
12.3 小结181
第13章 持续集成182
13.1 核心概念183
13.2 把实现持续集成所需的步骤串起来187
13.3 提交变更198
13.4 小结203
第14章 回顾204
14.1 代码是否具备良好的形象205
14.2 代码是否确切地实现了目标208
14.3 在编写代码的过程中有没有其他路可走210
14.4 从三个维度分析代码211
14.5 TDD 过时了吗224
14.6 全书总结226
附录 A 配置开发环境227
附录B 三种语言简史237
附录C 致谢244