10 迭代0_ 启动开发之前,你应该准备什么?

关于“以终为始”,我们已经从各个方面讲了很多。

你或许会想,既然我们应该有“以终为始”的思维,那么在项目刚开始,就把该准备的东西准备好,项目进展是不是就能稍微顺畅一点儿呢?

是这样的,事实上这已经是一种常见的实践了。

今天,我们就来谈谈在一开始就把项目准备好的实践:迭代0

为什么叫迭代0呢?

在“敏捷”已经不是新鲜词汇的今天,软件团队对迭代的概念已经不陌生了,它就是一个完整的开发周期,各个团队在迭代上的差别主要是时间长度有所不同。

一般来说,第一个迭代周期就是迭代1,然后是迭代2、迭代3,依次排列。从名字上你就不难发现,所谓迭代0,就是在迭代1之前的一个迭代,所以,我们可以把它理解成开发的准备阶段。

既然迭代0是项目的准备阶段,我们就可以把需要提前准备好的各项内容,在这个阶段准备好。

事先声明,这里给出的迭代0,它的具体内容只是基本的清单。

在了解了这些内容之后,你完全可以根据自己项目的实际情况,扩展或调整这个清单。

好,我们来看看我为你准备的迭代0清单都包含了哪些内容。

需求方面

1. 细化过的迭代1需求

一个项目最重要的是需求,而在迭代0里最重要的是,弄清楚第一步怎么走。

当我们决定做一个项目时,需求往往是不愁的,哪些需求先做、哪些需求后做,这是我们必须做的决策。迭代0需要做的事,就是把悬在空中的内容落到地上。

在需求做好分解之后,我们就会有一大堆待开发的需求列表。注意,这个时候需求只是一个列表,还没有细化。

因为你不太可能这个时候把所有的内容细化出来。如果你做过 Scrum 过程,你的 backlog 里放的就是这些东西。

然后,我们要根据优先级从中挑出迭代1要开发的需求,优先级是根据我们要完成的最小可行产品(minimum viable product,MVP)来确定的,这个最小可行产品又是由我们在这个迭代里要验证的内容决定的。一环扣一环,我们就得到了迭代1要做的需求列表。

确定好迭代1要做的需求之后,接下来就要把这些需求细化了,细化到可执行的程度。

前面讲用户故事时,我们已经说过一个细化需求应该是什么样子的,这里的关键点就是要把验收标准定义清楚。

所以,我们要在迭代0里,根据优先级来确定迭代1要做的需求,然后进行细化。

2.用户界面和用户交互

如果你的项目是一个有用户界面的产品,给出用户界面,自然也是要在迭代0完成的。

另外,还有一个东西也应该在迭代0定义清楚,那就是用户交互。

我见过很多团队只给出用户界面,然后,让前端程序员或者 App 程序员根据界面去实现。

程序员实现功能没问题,但定义交互并不是程序员这个角色的强项,它应该是需求的一部分。

如何让用户用着舒服,这是一门学问。

我们在市面上看到很多难用的网站或 App,基本上都是程序员按照自己习惯设计出来的。

现如今,我们可以很容易地在市面上找到画原型的工具,某些工具用得好的话,甚至可以达到以假乱真的地步。

如果能再进一步的话,甚至可以用一些模拟服务器的工具,把整个交互的界面都做出来。

作为 Moco 这个模拟服务器的开发者,我很清楚,一个原型可以达到怎样的高度。

所以,一个有用户界面的项目需要在迭代0中给出用户界面和用户交互。

技术方面

1. 基本技术准备

技术方面,需要在项目一开始就准备好的事比较多。其中有一些是你很容易想到的,比如:在进入迭代1开始写代码之前,我们需要确定技术选型,确定基本的技术架构等等。

也许你还能想到,数据库表结构也是这个阶段应该准备的。

确实,这些东西都应该是在一个项目初期准备的,也是大家容易想到的。

接下来,我来补充一些大家可能会遗漏的。

  • 持续集成

对于持续集成,我们通常的第一反应是搭建一个持续集成服务器。没错,但还不够。这里的重点其实是构建脚本。因为持续集成服务器运行的就是构建脚本。

那要把什么东西放在构建脚本里呢?最容易想到的是编译打包这样的过程。感谢现在的构建工具,它们一般还会默认地把测试也放到基本的构建过程中。

但仅有这些还是不够,我们还会考虑把更多的内容放进去,比如:构建 IDE 工程、代码风格检查、常见的 bug 模式检查、测试覆盖率等等。

持续集成还有一个很重要的方面,那就是持续集成的展示。

为什么展示很重要?当你的持续集成失败时,你怎么发现呢?

一个简单的解决方案是:摆个大显示器,用一个 CI Monitor 软件,把持续集成的状态展示在上面。更有甚者,会用一个实体的灯,这样感官刺激更强一些。

在“以终为始”这个模块中,我们提到集成的部分时,只讲了要做持续集成,后面我们还会再次讲到持续集成,和你说说持续集成想做好,应该做成什么样子。

  • 测试

测试是个很有趣的东西,程序员对它又爱又恨。一般来说,运行测试已经成为现在很多构建工具的默认选项,如果你采用的工具没有这个能力,建议你自己将它加入构建脚本。

让你为一个项目补测试,那是一件非常痛苦的事,如果在一开始就把测试作为规范加入进去的话,那么在边开发边写测试的情况下,相对来说,写测试痛苦度就低多了,团队成员也就容易遵守这样的开发规范。

把测试当作规范确定下来的办法就是把测试覆盖率加入构建脚本。

大多数团队提起测试,尤其是开发者测试,多半想到的都是单元测试和集成测试。

把整个系统贯穿在一起的“端到端测试”却基本上交给其他人来做,也有不少团队是交给测试团队专门开发的特定程序来做。

在今天的软件开发中,有一些更适合描述这类测试的方法,比如BDD,再比如Specification by Example。

你可以简单地把它们理解成一种描述系统行为的方式。还有一点做得好的地方是,有一些软件框架很好地支持了这种开发方法,比如Cucumber。如果你有这种测试,不妨也将它加入构建脚本。

2.发布准备

  • 数据库迁移

如果你做的是服务器端开发,多半离不开与数据库打交道。

只要是和数据库打交道,强烈建议你把数据库变更管理起来。

管理数据库变更的方式曾是很多团队面临的困扰。

好在现在已经有了很多工具支持,比如,我最近喜欢的工具是 flyway,它可以把每一次数据库变更都当作一个文件。这样一来,我们就可以把数据库变更放到版本控制工具里面,方便进行管理。

管理变更有几种不同的做法,一种是每个变更是一个文件,一种是每一次发布是一个文件。

各有各的好处,你可以根据需要,自行选择。

  • 发布

技术团队擅长做功能开发,但上线部署或打包发布却是很多团队在前期最欠考量的内容,也是很多团队手忙脚乱的根源。

如果一开始就把部署或发布过程自动化,那么未来的生活就会轻松很多。

如果你采用的是 Docker,就准备好第一个可以部署的 Dockerfile;如果是自己部署,就编写好 Shell 脚本。

其实你会发现,上面提到的所有内容即便不在迭代0做,在项目的各个阶段也会碰到。而且一般情况下,即便你在迭代0把这些工作做好了,后续依然要不断调整。

但我依然建议你在迭代0把这些事做好,因为它会给你的项目定下一个基调,一个自动化的基调

日常工作

最后,我们来看一下,如果在迭代0一切准备就绪,你在迭代1应该面对的日常工作是什么样的。

你从已经准备好的任务卡中选了一张,与产品经理确认了一些你不甚清楚的几个细节之后,准备实现它。你从代码仓库更新了最新的代码,然后,开始动手写代码。

这个任务要在数据库中添加一个字段,你打开开发工具,添加了一个数据库迁移文件,运行了一下数据库迁移工具,一切正常,新的字段已经出现在数据库中。

这个任务很简单,你很快实现完了代码,运行一下构建脚本,代码风格检查有个错误,你顺手修复了它。

再运行,测试通过了,但测试覆盖率不够,你心里说,偷懒被发现了。不过,这是小事,补几个测试就好了。一切顺利!

你又更新了一下代码,有几个合并的问题。修复之后,再运行构建脚本,全过,提交代码。

你伸了一个懒腰,完成任务之后,你决定休息片刻。忽然,持续集成的大屏幕红了,你的提交搞砸了。

你立刻看了一下代码,有一个新文件忘提交了,你吐了一下舌头赶紧把这个文件补上了。不一会儿,持续集成大屏幕又恢复了代表勃勃生机的绿色。

你休息好了,准备开始拿下下一个任务。

这就是一个正常开发该有的样子,在迭代0时,将准备工作做好,后续你的一切工作就会变得井然有序,出现的简单问题会很快地被发现,所有人都在一种有条不紊的工作节奏中。

总结时刻

在这一讲中,我给你介绍了迭代0的概念,它是在正式开发迭代开始之前,进行一些基础准备的实践。

我给了一份我自己的迭代0准备清单,这份清单包含了需求和技术两个大方面,你可以参照它设计你自己的迭代0清单。

总结

根据我的经验,对比这个清单,大多数新项目都在一项或几项上准备得不够充分。

即便你做的不是一个从头开始的项目,对照这个清单,也会发现项目在某些项上的欠缺,可以有针对性地做一些补充

如果今天的内容你只记住一件事,那么请记住:设计你的迭代0清单,给自己的项目做体检。

最后,我想请你思考一下,如果让你来设计迭代0清单,它会包含哪些内容呢?欢迎在留言区写下你的想法。

11 向埃隆·马斯克学习任务分解

这次我们从一个宏大的话题开始:银河系中存在多少与我们相近的文明。

我想,即便这个专栏的读者主力是程序员这个平均智商极高的群体,在面对这样一个问题时,大多数人也不知道从何入手。

我来做一个科普,给大家介绍一下德雷克公式,这是美国天文学家法兰克·德雷克(Frank Drake)于1960年代提出的一个公式,用来推测“可能与我们接触的银河系内外星球高等文明的数量”。

下面,我要放出德雷克公式了,看不懂一点都不重要,反正我也不打算讲解其中的细节,我们一起来感受一下。

公式

不知道你看了德雷克公式做何感想,但对于科学家们来说,德雷克公式最大的作用在于:

它将一个原本毫无头绪的问题分解了,分成若干个可以尝试回答的问题

随着观测手段的进步,我们对宇宙的了解越来越多,公式中大多数数值,都可以得到一个可以估算的答案。有了这些因子,人们就可以估算出银河系内可以与我们通信的文明数量。

虽然不同的估算结果会造成很大的差异,而且我们迄今为止也没能找到一个可以联系的外星文明,但这个公式给了我们一个方向,一个尝试解决问题的手段。

好吧,我并不打算将这个专栏变成一个科普专栏,之所以在这讲解德雷克公式,因为它体现了一个重要的思想:任务分解。

通过任务分解,一个原本复杂的问题,甚至看起来没有头绪的问题,逐渐有了一个通向答案的方向。

而“任务分解”就是我们专栏第二模块的主题。

马斯克的任务分解

如果大家对德雷克公式有些陌生,我们再来看一个 IT 人怎样用任务分解的思路解决问题。

我们都知道埃隆·马斯克(Elon Musk),他既是电动汽车公司特斯拉(Tesla)的创始人,同时还创建了太空探索公司 SpaceX。

SpaceX 有一个目标是,送100万人上火星。

美国政府曾经算过一笔账,把一个人送上火星,以现有技术是可实现的,需要花多少钱呢?

答案是100亿美金。如果照此计算,实现马斯克的目标,送100万人上火星就要1万万亿。

这是什么概念呢?这笔钱相当于美国500年的GDP,实在太贵了,贵到连美国政府都无法负担。

马斯克怎么解决这个问题呢?

他的目标变了,他准备把人均费用降到50万美元,也就是一个想移民的人,把地球房子卖了能够凑出的钱。原来需要100亿美金,现在要降到50万美金,需要降低2万倍。

当然,降低2万倍依然是一个听起来很遥远的目标。

所以,我们关注的重点来了:马斯克的第二步是,把2万分解成20×10×100。这是一道简单的数学题,也是马斯克三个重点的努力方向。

先看“20”:现在的火星飞船一次只能承载5个人,马斯克的打算是,把火箭造大一点,一次坐100人,这样,就等于把成本降低20倍。如果你关注新闻的话,会发现 SpaceX 确实在进行这方面的尝试,

再来看“10”:马斯克认为自己是私营公司,效率高,成本可以降到十分之一。他们也正在向这个方向努力,SpaceX 的成本目前已经降到了同行的五分之一。

最后的“100”是什么呢?就是回收可重复使用的火箭。如果这个目标能实现,发射火箭的成本就只是燃料成本了。这也就是我们频频看到的 SpaceX 试飞火箭新闻的原因。

这么算下来,你是不是觉得,马斯克的目标不像最开始听到的那样不靠谱了呢?

正是通过将宏大目标进行任务分解,马斯克才能将一个看似不着边际的目标向前推进。

软件开发的任务分解

好了,和大家分享这两个例子只是为了热热身,说明人类解决问题的方案是差不多的。

当一个复杂问题摆在面前时,我们解决问题的一个主要思路是分而治之。

一个大问题,我们都很难给出答案,但回答小问题却是我们擅长的。

所以,当我们学会将问题分解,就相当于朝着问题的解决迈进了一大步。

我们最熟悉的分而治之的例子,应该是将这个理念用在算法上,比如归并排序。将待排序的元素分成大小基本相同的两个子集,然后,分别将两个子集排序,最后将两个排好序的子集合并到一起。

一说到技术,大家就觉得踏实了许多,原来无论是外星人搜寻,还是大名鼎鼎的马斯克太空探索计划,解决问题时用到的思路都是大同小异啊!确实是这样。

那么,用这种思路解决问题的难点是什么呢?

给出一个可执行的分解。

在前面两个例子里面,最初听到要解决的问题时,估计你和我一样,是一脸懵的。但一旦知道了分解的结果,立即会有一种“柳暗花明又一村”的感觉。

你会想,我要是想到了这个答案,我也能做一个 SpaceX 出来。

但说到归并排序的时候,你的心里可能会有一丝不屑,这是一个学生级别的问题,甚至不值得你为此费脑子思考。

因为归并排序你已经知道了答案,所以,你会下意识地低估它。

任务分解就是这样一个有趣的思想,一旦分解的结果出来,到了可执行的步骤,接下来的工作,即便不是一马平川,也是比原来顺畅很多,因为问题的规模小了

在日常工作中,我们会遇到很多问题,既不像前两个问题那样宏大,也不像归并排序那样小,但很多时候,我们却忘记了将任务分解这个理念运用其中,给工作带来很多麻烦。

举一个例子,有一个关于程序员的经典段子:这个工作已经做完了80%,剩下的20%还要用和前面的一样时间。

为什么我们的估算差别如此之大,很重要的一个原因就在于没有很好地分解任务,所以,我们并不知道要做的事情到底有多少。

前面我们在“为什么说做事之前要先进行推演?”

文章中,讲到沙盘推演,这也是一个很好的例子,推演的过程就是一个任务分解的过程。上手就做,多半的结果都是丢三落四。

你会发现,真正把工作完全做好,你落掉的工作也都要做,无论早晚。

与很多实践相反,任务分解是一个知难行易的过程。知道怎么分解是困难的,一旦知道了,行动反而要相对来说容易一些。

在“任务分解”这个主题下,我还会给你介绍一些实践,让你知道,这些最佳实践的背后思想就是任务分解。如果你不了解这些实践,你也需要知道,在更多的场景下,先分解任务再去做事情是个好办法。

也许你会说,任务分解并不难于理解,我在解决问题的过程中也是先做任务分解的,但“依然过不好这一生。”这就要提到我前面所说难点中,很多人可能忽略的部分:可执行。

可执行对于每个人的含义是不同的,对于马斯克而言,他把2万分解成20×10×100,剩下的事情对他来说就是可执行的,但如果你在 SpaceX 工作,你就必须回答每个部分究竟是怎样执行的。

同样,假设我们做一个 Web 页面,如果你是一个经验丰富的前端工程师,你甚至可能认为这个任务不需要分解,顶多就是再多一个获取网页资源的任务。

而我如果是一个新手,我就得把任务分解成:根据内容编写 HTML;根据页面原型编写页面样式;根据交互效果编写页面逻辑等几个步骤。

不同的可执行定义差别在于,你是否能清楚地知道这个问题该如何解决。

对于马斯克来说,他的解决方案可能是成立一个公司,找到这方面的专家帮助他实现。

对你的日常工作来说,你要清楚具体每一步要做的事情,如果不能,说明任务还需要进一步分解

比如,你要把一个信息存起来,假设你们用的是关系型数据库,对大多数人来说,这个任务分解就到了可执行的程度。

但如果你的项目选用了一个新型的数据库,比如图数据库,你的任务分解里可能要包含学习这个数据库的模型,然后还要根据模型设计存储方案。

不过,在实际工作中,大多数人都高估了自己可执行粒度,低估任务分解的程度。

换句话说,如果你没做过任务分解的练习,你分解出来的大部分任务,粒度都会偏大。

只有能把任务拆分得非常小,你才能对自己的执行能力有一个更清楚地认识,真正的高手都是有很强的分解能力。

这个差别就相当于,同样观察一个物品,你用的是眼睛,而高手用的是显微镜。在你看来,高手全是微操作。关于这个话题,后面我们再来细聊。

一旦任务分解得很小,调整也会变得很容易。很多人都在说计划赶不上变化,而真正的原因就是计划的粒度太大,没法调整。

从当年的瀑布模型到今天的迭代模型,实际上,就是缩减一次交付的粒度。几周调整一次计划,也就不存在“计划赶不上变化”的情况了,因为我的计划也一直在变。

如今软件行业都在提倡拥抱变化,而任务分解是我们拥抱变化的前提。

总结时刻

我们从外星人探索和马斯克的火星探索入手,介绍了任务分解在人类社会诸多方面的应用,引出了分而治之这个人类面对复杂问题的基本解决方案。

接着,我给你讲了这一思想在软件开发领域中的一个常见应用,分而治之的算法。

虽然我们很熟悉这一思想,但在日常工作中,我们却没有很好地应用它,这也使得大多数人的工作有很大改进空间。运用这一思想的难点在于,给出一个可执行的分解。

一方面,对复杂工作而言,给出一个分解是巨大的挑战;另一方面,面对日常工作,人们更容易忽略的是,分解的任务要可执行

每个人对可执行的理解不同,只要你清楚地知道接下来的工作该怎么做,任务分解就可以告一段落。

大多数人对于可执行的粒度认识是不足的,低估了任务分解的程度,做到好的分解你需要达到“微操作”的程度。

有了分解得很小的任务,我们就可以很容易完成一个开发循环,也就让计划调整成为了可能。软件行业在倡导拥抱变化,而任务分解是拥抱变化的前提。

如果今天的内容你只记住一件事,那么请记住:动手做一个工作之前,请先对它进行任务分解。

最后,我想请你回想一下,你在实际工作中,有哪些依靠任务分解的方式解决的问题呢?欢迎在留言区写下你的想法。

12 测试也是程序员的事吗?

在“任务分解”这个模块,我准备从一个让我真正深刻理解了任务分解的主题开始,这个主题就是“测试”。

这是一个让程序员又爱有恨的主题,爱测试,因为它能让项目的质量有保证;恨测试,因为测试不好写。

而实际上,很多人之所以写不好测试,主要是因为他不懂任务分解。

在上一个模块,我们提到了一些最佳实践,但都是从“以终为始”这个角度进行讲解的。这次,我准备换个讲法,用五讲的篇幅,完整地讲一下“开发者测试”,让你和我一起,重新认识这个你可能忽视的主题。

准备好了吗?我们先从让很多人疑惑的话题开始:程序员该写测试吗?

谁要做测试?

你是一个程序员,你当然知道为什么要测试,因为是我们开发的软件,我们得尽可能地保证它是对的,毕竟最基本的职业素养是要有的。

但测试工作应该谁来做,这是一个很有趣的话题。很多人凭直觉想到的答案是,测试不就该是测试人员的事吗,这还用问?

测试人员应该做测试,这是没错的,但是测试只是测试人员的事吗?

事实上,作为程序员,你多半已经做了很多测试工作。

比如,在提交代码之前,你肯定会把代码跑一遍,保证提交的基本功能是正确的,这就是最基本的测试。

但通常,你并不把它当成测试,所以,你的直觉里面,测试是测试人员的事。

但我依然要强调,测试应该是程序员工作的一部分,为什么这么说呢?

我们不妨想想,测试人员能测的是什么?

没错,他们只能站在系统外部做功能特性的测试。而一个软件是由它内部诸多模块组成的,测试人员只从外部保障正确性,所能达到的效果是有限的。

打个比方,你做一台机器,每个零部件都不保证正确性,却要让最后的结果正确,这实在是一个可笑的要求,但这却真实地发生在软件开发的过程中。

在软件开发中有一个重要的概念:

软件变更成本,它会随着时间和开发阶段逐步增加。也就是说我们要尽可能早地发现问题,修正问题,这样所消耗掉的成本才是最低的

上一个模块讲“以终为始”,就是在强调尽早发现问题。能从需求上解决的问题,就不要到开发阶段。同样,在开发阶段能解决的问题,就不要留到测试阶段。

你可以想一下,是你在代码中发现错误改代码容易,还是测试了报了 bug,你再定位找问题方便。

更理想的情况是,质量保证是贯穿在软件开发全过程中,从需求开始的每一个环节,都将“测试”纳入考量,每个角色交付自己的工作成果时,都多问一句,你怎么保证交付物的质量。

需求人员要确定验收标准,开发人员则要交出自己的开发者测试。

这是一个来自于精益原则的重要思想:内建质量(Build Quality In)。

所以,对于每个程序员来说,只有在开发阶段把代码和测试都写好,才有资格说,自己交付的是高质量的代码。

自动化测试

不同于传统测试人员只通过手工的方式进行验证,程序员这个群体做测试有个天然的优势:会写代码,这个优势可以让我们把测试自动化。

早期测试代码,最简单的方式是另外写一个程序入口,我初入职场的时候,也曾经这么做过,毕竟这是一种符合直觉的做法。

不过,既然程序员有写测试的需求,如此反复出现的东西,就会有更好的自动化方案。

于是开始测试框架出现了。

最早的测试框架起源是 Smalltalk。这是一门早期的面向对象程序设计语言,它有很多拥趸,很多今天流行的编程概念就来自于 Smalltalk,测试框架便是其中之一。

真正让测试框架广泛流行起来,要归功于 Kent Beck 和 Erich Gamma。Kent Beck 是极限编程的创始人,在软件工程领域大名鼎鼎,而 Erich Gamma 则是著名的《设计模式》一书的作者,很多人熟悉的 Visual Studio Code 也有他的重大贡献。

有一次,二人一起从苏黎世飞往亚特兰大参加 OOPLSA(Object-Oriented Programming, Systems, Languages & Applications)大会,在航班上两个人结对编程写出了JUnit。

从这个名字你便不难看出,它的目标是打造一个单元测试框架。

顺便说一下,如果你知道 Kent Beck 是个狂热的 Smalltalk 粉丝,写过 SUnit 测试框架,就不难理解这两个人为什么能在一次航班上就完成这样的力作。

JUnit 之后,测试框架的概念逐渐开始流行起来。

如今的“程序世界”,测试框架已经成为行业标配,每个程序设计语言都有自己的测试框架,甚至不止一种,一些语言甚至把它放到了标准库里,行业里也用 XUnit 统称这些测试框架。

这种测试框架最大的价值,是把自动化测试作为一种最佳实践引入到开发过程中,使得测试动作可以通过标准化的手段固定下来

测试模型:蛋卷与金字塔

在前面的讨论里,我们把测试分为人工测试和自动化测试。

即便我们只关注自动化测试,也可以按照不同的层次进行划分:将测试分成关注最小程序模块的单元测试、将多个模块组合在一起的集成测试,将整个系统组合在一起的系统测试。

有人喜欢把验收测试也放到这个分类里。为了简化讨论,我们暂时忽略验收测试。

随之而来的一个问题是,我们应该写多少不同层次的测试呢?理论上固然是越多越好了,但实际上,做任何事都是有成本的,所以,人们必须有所取舍。根据不同测试的配比,也就有了不同的测试模型。

有一种直觉的做法是,既然越高层的测试覆盖面越广,那就多写高层测试,比如系统测试。

当然,有些情景高层的测试不容易覆盖到的,所以,还要有一些底层的测试,比如单元测试。

在这种情况下,底层的测试只是作为高层测试的补充,而主力就是高层测试。这样就会形成下面这样一种测试模型:冰淇淋蛋卷。

test

听说过冰淇淋蛋卷测试模型的人并不多,它是一种费时费力的模型,要准备高层测试实在是太麻烦了。

之所以要在这里提及它,是因为虽然这个概念很多人没听说过,但是有不少团队的测试实际采用的就是这样一种模型,这也是很多团队觉得测试很麻烦却不明就里的原因。

接下来,要说说另一种测试模型,也是行业里的最佳实践:测试金字塔。

测试金字塔

Mike Cohn 在自己的著作《Succeeding with Agile》提出了测试金字塔,但大多数人都是通过 Martin Fowler 的文章知道的这个概念。

从图中我们不难看出,它几乎是冰淇淋蛋卷的反转,测试金字塔的重点就是越底层的测试应该写得越多。

想要理解测试金字塔成为行业最佳实践的缘由,我们需要理解不同层次测试的差异。

越是底层的测试,牵扯到相关内容越少,而高层测试则涉及面更广

比如单元测试,它的关注点只有一个单元,而没有其它任何东西。所以,只要一个单元写好了,测试就是可以通过的;而集成测试则要把好几个单元组装到一起才能测试,测试通过的前提条件是,所有这些单元都写好了,这个周期就明显比单元测试要长;系统测试则要把整个系统的各个模块都连在一起,各种数据都准备好,才可能通过。

这个模块的主题是“任务分解”,我必须强调一点:小事反馈周期短,而大事反馈周期长。小事容易做好,而大事难度则大得多。所以,以这个标准来看,底层的测试才更容易写好。

另外,因为涉及到的模块过多,任何一个模块做了调整,都有可能破坏高层测试,所以,高层测试通常是相对比较脆弱的。

此外,在实际的工作中,有些高层测试会牵扯到外部系统,这样一来,复杂度又在不断地提升。

人们会本能地都会倾向于少做复杂的东西,所以,人们肯定不会倾向于多写高层测试,其结果必然是,高层测试的测试量不会太多,测试覆盖率无论如何都上不来。

而且,一旦测试失败,因为牵扯的内容太多,定位起来也是非常麻烦的。

而反过来,将底层测试定义为测试主体,因为牵扯的内容少,更容易写,才有可能让团队得到更多的测试,而且一旦出现问题,也会更容易发现。

所以,虽然冰淇淋蛋卷更符合直觉,但测试金字塔才是行业的最佳实践。

当测试金字塔遇到持续集成

测试金字塔是一个重要实践的基础,它就是持续集成。

当测试数量达到一定规模,测试运行的时间就会很长,我们可能无法在本地环境一次性运行所有测试。

一般我们会选择在本地运行所有单元测试和集成测试,而把系统测试放在持续集成服务器上执行。

这个时候,底层测试的数量就成了关键,按照测试金字塔模型,底层测试数量会很多,测试可以覆盖主要的场景;而按照冰淇淋蛋卷模型,底层测试的数量则有限。

作为提交代码的防护网,测试数量多寡决定着得到反馈的早晚。所以,金字塔模型与持续集成天然就有着很好的配合。

需要特别注意的是,不是用单元测试框架写的测试就是单元测试。很多人用单元测试框架写的是集成测试或是系统测试。

单元测试框架只是一个自动化测试的工具而已,并不是用来定义测试类型的

在实际工作中,区分不同测试有很多种做法,比如,将不同的测试放到不同的目录下,或是给不同类型的测试一个统一的命名规范。

区分不同类型测试主要目的,主要是在不同的场景下,运行不同类型的测试。就像前面提到的做法是,在本地运行单元测试和集成测试,在持续集成服务器上运行系统测试。

总结时刻

测试是软件开发重要的组成部分,测试应该是软件开发团队中所有人的事,而不仅仅是测试人员的事。因为软件变更成本会随着时间和开发阶段逐步增加,能在早期解决的问题,就不要将它延后至下一个阶段。

在测试问题上,程序员有着天生的优势,会写代码,于是,程序员拥有了一个突出的强项,自动化测试。写测试应该是程序员工作完成的重要组成部分。

随着人们对于测试理解的加深,各种各样的测试都出现了,也开始有了测试的分类:单元测试、集成测试、系统测试等等。

越在底层测试,成本越低,执行越快;越在高层测试,成本越高,执行越慢。

人的时间和精力是有限的,所以,人们开始思考不同的测试如何组合。在这个方面的最佳实践称之为测试金字塔,它强调的重点是,越底层的测试应该写得越多。只有按照测试金字塔的方式写测试,持续集成才能更好地发挥作用。

如果今天的内容你只能记住一件事,那请记住:多写单元测试

最后,我想请你分享一下,你的团队在写测试上遇到哪些困难呢?欢迎在留言区写下你的想法

参考资料

http://learn.lianglianglee.com/%e4%b8%93%e6%a0%8f/10x%e7%a8%8b%e5%ba%8f%e5%91%98%e5%b7%a5%e4%bd%9c%e6%b3%95/10%20%e8%bf%ad%e4%bb%a30_%20%e5%90%af%e5%8a%a8%e5%bc%80%e5%8f%91%e4%b9%8b%e5%89%8d%ef%bc%8c%e4%bd%a0%e5%ba%94%e8%af%a5%e5%87%86%e5%a4%87%e4%bb%80%e4%b9%88%ef%bc%9f.md