“叕”谈 TDD(三)--- 如何TDD

背景:

之所以要 “叕” 谈 TDD, 除了上述背景,也是因为自己工作4年来,虽然经常听到 TDD,但着实没有“完整” 的在项目上实践过它。直到最近打算在当前的交付项目上实践,才又重新审视 这项实践,以求回答下列问题:

在逐一回答这些问题之前,先说我对 TDD 这种实践的 观点

  • TDD 是确保 Dev 在编写代码时,处于 对需求保持 “清醒(Obvious)” 状态 的方式之一,但并非 唯一 方式
  • TDD 中的测试(T)要面向业务需求,而非代码实现
  • TDD 是一种 快速, 可复用 的 反馈获取 方式,而非唯一方式
  • 如果能 不用 TDD 并做到上述 3 点,那么不 TDD 也没问题。

如何 TDD

其实 TDD 实践方式的大体轮廓在上一篇《为何TDD》中的代码示例已经提及一二,本篇不过是增添细节,将其更加完整、详尽地展示出来。
简单来说,TDD 可以有两种实践模式:

  • 单人模式(Solo)
  • 双人模式(Pair)

它们会给你完全不同的体验。从整体来看,我将 TDD 的实践归纳为 3个阶段

  • 准备阶段
  • 实施阶段
  • 收尾阶段

每个阶段都有明确的目标和需要掌握的辅助技能,下面我会基于 单人模式 进行详细介绍。双人模式如无专门说明,则与单人模式的实践相同。

准备阶段

1. 理解需求
这是一个非常容易遗漏的步骤,因为需求“理应”已经完全标明在故事卡(User Story)中了,只要看完卡片不就好了吗?
其实不然。
只是读完卡片,并不能说明需求被完全理解了。在这一步,要做到明白当前卡片的价值何在,同时还需清楚卡片上的验收条件是否足够完整验证卡片价值的达成。在此之后,如果卡片上的验收条件还不包含了可以使用的具体样例(数值),而只是抽象的公式或逻辑,那么我们需要将这些抽象的公式或逻辑具象化,形成后续写测试时可以直接使用的测试数据

例如 有👇🏻的需求描述

作为 话费充值用户,
我想 在查询话费时,能够看到当前账户余额,
以便 我能够了解何时应当进行话费充值 

可能的验收条件

验收条件1:
给定 一个非欠费用户,
当 该用户查询账户余额时,
即 给出当前实际的账户金额,账户金额应大于等于 0
----------------------------------------
验收条件2:
给定 一个欠费用户,
当 该用户查询账户余额时,
即 给出当前的欠费金额,欠费金额用负数表示,
并 提示友好信息通知用户充值

在理解需求的过程中,需要具象化上述验收条件,于是可以发现 友好信息的内容尚不明确,于是通过进一步沟通和确认,明确了内容:欠费大于 50 元,则停机,友好信息的内容也会有所差别,即

未停机: “您的余额不足,为了避免停机造成的影响,请尽快缴存话费”
已停机: “您当前处于停机中,缴费后恢复服务”

于是,基于边界条件,可以整理出如下的具象化用例(Exapmle)

对于 验收条件1:
Example 1:
给定 一个账户余额为100的用户
当 该用户查询账户余额时,
即 给出当前余额为 100.00

Example 2:
给定 一个账户余额为0的用户
当 该用户查询账户余额时,
即 给出当前余额为 0.00
--------------------
对于 验收条件2:
Example 3:
给定 一个欠费10元的用户,
当 该用户查询账户余额时,
即 给出当前余额为 -10.00,
并 提示“您的余额不足,为了避免停机造成的影响,请尽快缴存话费”

Example 4:
给定 一个欠费50元的用户,
当 该用户查询账户余额时,
即 给出当前余额为 -50.00,
并 提示“您的余额不足,为了避免停机造成的影响,请尽快缴存话费”

Example 5:
给定 一个欠费50.01元的用户,
当 该用户查询账户余额时,
即 给出当前余额为 -50.01,
并 提示“您当前处于停机中,缴费后恢复服务”

注: Example 2 和Example 4 用于验证验收条件边界处的满足与否。

至此,才算需求被理解了。

2. 明确当前系统的测试策略
通常,每个系统都会有自己的架构,而这些架构也都会分成不同的层级,每个层级都会有相应的一些组件。那么在开始TDD 之前,我们一定要先弄清楚针对当前工作的系统架构,它的测试策略是什么?

这就需要用到测试金字塔理论测试四象限理论

先弄明白针对目标系统的测试策略,就可以消除TDD 过程中,对于测试粒度不清楚的问题,即我是该写单元测试呢?还是该写组件测试?又或者其他什么测试类型?

通常,测试策略很难有定式,需要“因地制宜”,结合测试目的,成本,具体问题具体分析,具体制定,这里就不多作说明了。

基于上述理念,特别强调我们要避免认为TDD 就一定要从单元测试(Unit Test)做起.

3. 拆分任务
有了一个个具象化的用例,和明确的测试策略,“任务”的目标就很明确了:

将具现化的用例转化为符合测试策略的测试代码,并通过测试

但这只是一个非常宏观的“任务”,它并不是我们在在任务拆分时需要完成的任务。因此,在拆分任务前,我们需要思考任务的应具备的特征:

  • 可达成:任务最终是一定能够完成的
  • 可验收:每个任务的完成与否都有明确的衡量标准
  • 可估时间:完成任务所需的时间是可以被大概估计的,可以使用TimeBox追踪
  • 目标相关:完成了这些任务,那么这些任务所对应的目标就能被实现

每一个在时限内完成的任务,都是一次“正向反馈”,它会为开发者提供成就感,从而使开发者进入一种“节奏”,有时通过这种节奏,开发者可以更容易地进入“流”(Flow,一种注意力高度集中的状态)。
而每一个时限内未能完成的任务,则都是一次“负向反馈”,它为开发者提供反思的入手点,从而归纳总结出可以进一步提升的知识、技能等个人能力。

我通常在 TDD 的实践中,会将任务拆分到可以在15 - 30 分钟内完成的大小。如果利用需求理解部分的例子具象化这样的一个任务,那么在一个传统的 MVC 分层架构的后端系统中,我的任务拆分结果会是这样:

任务1: 完成 验收条件 1 中的功能,通过 Example 1 和 Example 2 的验证,并通过后端 API 返回期待的结果(20 分钟)
任务2: 完成 验收条件 2 中的功能,通过 Example 3, Example 4, Example 5 并通过后端 API 返回期待的结果(30 分钟)

至此,我们的准备阶段就结束了,可以进入接下来的实施阶段了。

注:双人模式下,准备阶段会增加更多的讨论,这些讨论在一定程度上是有助于探索遗漏的边界条件,但同时也需要控制讨论的效率,求同存异,以防对整体工作造成影响。

实施阶段

进入实施阶段后,我们就可以带上一顶名为“实现功能”的帽子,专注业务功能实现,开始代码编写了。

《为何TDD》中,我有贴过一些 TDD 方式产出的代码。这里,就不再贴出额外的代码了。但是,可以尝试利用基于传统的 MVC 分层架构中的核心业务层(Service),单元测试的作为该层组件的测试策略的场景,总结如下的 TDD 实践步骤以供参考:

1. 定义目标 Service 类,**简单设计**类中所需方法的签名
2. 构造目标 Service 类的测试,根据测试需要,Stub/Mock/Spy/Fake/Dummy(测试替身)当前Service 的依赖项(可以是 Repository 接口,HttpClient 接口,Config接口等)
3. 利用具象化的 Example 中的内容,目标方法的签名和已声明的测试替身,编写测试用例,并运行
4. 调整 Service 类中定义的方法逻辑,通过测试
5. 重复 3,4 步骤,直到之前所有的列出的 Example 都被“翻译”为测试代码,并被运行通过

在实施阶段的工作完成后,新添加的代码理应已经可以完全满足业务需求,并且所有的业务需求,也都已经被“翻译”为测试代码。这意味着,无论如何调整代码,只要已有的测试用例能够全部通过,当前的业务功能就不会受到任何影响。
那么,我们就可以放心的拿下“实现功能”的帽子,进入最后的收尾阶段。

注: 双人模式下, 实施阶段需要合理分配工作,可以采用工作经验较丰富或对当前业务更熟悉的一人来“翻译”测试(编写测试用例),另一人则专注于通过测试。并在合适的时机进行角色交换,平衡两人的参与感。更多细节,可以参照《沉思录---结对编程篇》

收尾阶段

在收尾阶段中,开发者需要带上一顶名为“重构”的帽子。此时,有了充足的测试覆盖的保证,开发人员可以“为所欲为,大刀阔斧,随心所欲”的使用学到的设计模式,技巧,基于整洁代码的规范,优化代码,消除“实现功能”过程中遗留的“坏味道”,使其更易读、易维护。

总结

如何TDD?

  1. 理解需求,将需求通过验收条件,转化为具象化的Example
  2. 明确测试策略,结合测试金字塔与测试四象限,设计与测试意图、成本匹配的测试策略
  3. 拆分任务,基于需求和任务的特性,对业务需求目标进行拆分
  4. 利用具象化的Example,测试策略,以“翻译”需求为目的编写测试,并以通过测试为目的实现功能
  5. 通过重构,优化代码,完成收尾工作
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 206,126评论 6 481
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 88,254评论 2 382
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 152,445评论 0 341
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 55,185评论 1 278
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 64,178评论 5 371
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,970评论 1 284
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,276评论 3 399
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,927评论 0 259
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 43,400评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,883评论 2 323
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,997评论 1 333
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,646评论 4 322
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,213评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,204评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,423评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,423评论 2 352
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,722评论 2 345

推荐阅读更多精彩内容