测试驱动开发 -TDD

测试驱动开发TDD-Test Driven Development是敏捷开发中的一种实践开发模式,简单来说即:测试代码优先于产品代码编写,通过测试不断驱动编写完善的产品代码,实现所需的功能。
测试驱动开发与通常开发模式在角度和思考方式上都有着本质的区别,并且刚接触TDD会让人难以接受。
在通常模式下,在拿到一个需求后,经过简单的思考设计(通常是纸上画符)就直接进入功能实现的开发。在开发完成后再编写测试用例并运行测试,修复bug让代码能工作。而测试驱动开发则正好相反,在拿到需求后开始写的第一个代码时测试用例。本质上是通过测试用例来描述需求,并编写产品代码使得测试通过。

一、 TDD原则

测试驱动开发所遵循的三个基本原则如下:

  1. 除非能让失败的单元测试通过,否则不允许去编写任何的产品代码。
    对于任何功能需求,都是先从写测试用例入手,为满足测试用例才能去写产品代码。

  2. 只允许编写刚好能够导致失败的单元测试。 (编译失败也属于一种失败)
    通常在开发完成后写的测试用例都是希望能通过的测试用例,很可能因先入为主导致不能正确覆盖测试。相反,TDD编写新的测试用例是为了覆盖不同的需求,导致失败。

  3. 只允许编写刚好能够使一个失败的单元测试通过的产品代码。
    编写的生产代码只能是为了使一个失败的单元测试通过,不应编写多余的实现代码。如果过多编写了实现其他功能业务的代码,则违反了TDD的原则。

测试驱动开发的基本流程

  • 编写单元测试 --> 运行单元测试-失败
  • 编写生产代码 --> 运行单元测试
  • 重构代码 -->运行单元测试保证通过
    从流程还可以看出,测试驱动开发将持续的重构纳入流程之重,并通过测试保证了重构的正确性。因为单元测试运行失败为红色,通过为绿色,因此TDD流程也可以简单描述为:
    红、绿、重构。

二、 TDD实践

下面通过经典的Bob大叔关于保龄球计分的例子来进行说明,如何来实践TDD。
首先简单介绍下保龄球积分规则:

  • 每一局总共有十轮,每轮一开始会有十支球瓶,球手可以扔两次球,目标就是用尽量少的球把全部球瓶击倒。
  • 如果第一球就把全部的球瓶都击倒了,也就是STRIKE-全中,就算完成一轮了,本轮分数是10分再加奖励(bonus):即后面两球的倒瓶数,
  • 如果第一球没有全倒,就要再打一球,如果第二球将剩下的球瓶全都击倒,也就是SPARE-补中,也算完成一轮,本轮分数为10分再加奖励(bonus)即:下一球的倒瓶数,
  • 如果第二球也没有把球瓶全部击倒的话,那分数就是第一球加第二球倒的瓶数,没有奖励(bonus),再接着打下一轮。依此类推。
  • 如果在第十轮出现STRIKE或者SPARE,则球手可再加打第三球,最多只能打三球。
  • 全部十轮的得分相加就等于这一局的总得分。

我们的需求是
提供一个Game类,并且包含2个方法

  • roll(int pins):用于玩家每次扔球后击倒的瓶子数,参数即为击倒的瓶子数
  • score():每局结束后调用计算这一局的总分数。

根据TDD的原则,拿到需求后我们第一反应不是去设计并实现上述规则,而是应当先编写测试用例。

1. 第一个测试用例

先编写一个最简单的测试用例,即每次投球都没有击倒瓶子,很明显最终得分应该是0。


image.png

很明显因为Game类不存在,测试是不会通过的,因此我们需要去创建Game类使测试通过(在这里不应当写更多实现代码)。


image.png

接下来,我们增加投球的代码,假如每次都没击中,即倒瓶数都是0:因为roll方法不存在,因此需要我们在Game类中增加roll方法(此时不用关心具体的实现)。


image.png

同样的增加一局结束后算分方法(同样不关心实现),此时测试不通过。


image.png

需要修改产品代码使测试通过,此时我们不考虑具体实现,简单返回一个0使测试通过。


image.png

2. 第二个测试用例

第一个测试用例仅为每轮都没击倒瓶子的情况,下面我们编写测试用例每次投球都只击倒1个瓶子。即没有全中也没有补中,最终得分应该是20。


image.png

编写测试用例并运行,很显然测试不会通过,我们需要修改产品代码。很明显最简单的实现是需要我们累加每次击球后的分数。通过一个成员变量score记录分值并累加,测试通过。


image.png

同时我们发现测试用例中的循环投球代码出现了重复,我们可以进行代码的提取重构。我们将投球代码提取为独立的方法给测试用例调用。并再次运行测试,保证测试通过。


image.png

3. 第三个测试用例

现在我们来考虑下出现补中SPARE的情况,并编写对应的测试用例。为了简单,我们先只考虑出现一次全中:第一轮投球为5,5;第三球为3,其他全是没中-0。这样一局得分应该是:第一轮10分+(奖励-第三球得分+3)+第二轮得分3+其他得分0 =16。运行测试用例-失败。


image.png

我们需要修改产品代码满足出现补中的情况。这时我们发现代码上有些问题:roll方法本来应当用来记录每次击倒的瓶子数,实际却用来计算分数,而score方法本来应用来计算分数,但却没有实现,代码需要重构。
这里我们回退一步,回到第二个测试用例通过的情况,并对代码进行重构。
因为要记录每次击倒的瓶子数,所以我们引入一个整型数组,并通过一个索引来指示当前是第几球。在roll方法中对击倒数进行记录。在score方法中根据击倒数进行分值计算。通过测试用例保证我们的重构是正确的。


image.png

现在我们回到第三个测试用例,它依然是未通过状态。因为补中为2球击倒10球,因此在计算得分时需要按照2球一轮来进行考虑,同时来处理补中的情况。修改产品代码使测试用例通过。


image.png

现在来会看我们的代码,可以发现为了解释代码我们加了一些注释(spare),说明代码不能自解释。同时我们代码中有随意定义的变量i和很长的条件语句,我们对代码先进行一些重构,提取方法,并修改变量名为有意义的名字。测试用例保证了我们的修改不会破坏当前代码。


image.png

进入下一个红、绿、重构循环,完成全中STRIKE的支持和代码优化

三、 总结

上面就是一个TDD的实践过程,从中我们可以看出这样几点:

  1. 单元测试保证了编码过程中持续重构的正确。在完成代码的编写后,我们会得到很多的测试代码,而这些测试代码可以用来保证对代码后续的维护修改的正确性。单元测试对代码的覆盖率同样可以增强我们对代码的信心。
  2. 丰富的单元测试完全可以替代接口文档,更能清楚的描述代码实现的功能。
  3. TDD的开发模式促使了代码的抽取和解耦,利于代码复用。

TDD在现实中的情况

  1. 在实际情况中真正应用TDD模式来进行开发的公司和团队少之又少,具体分析大概有以下原因:
  2. 公司或团队没有推动支持。部分公司和团队甚至不能很好的推行单元测试,TDD更无从谈起。另外虽然TDD能保证产出更高质量的代码,但会被认为可能需要耗费更多时间。
  3. TDD模式跟通常的思维习惯相反,让开发人员很难接受。
  4. 某些项目运行单元测试耗时太长。即使有很多辅助工具的使用,在一个复杂项目里运行单元测试仍然不是一个随手操作的事情。

参考文档:
Bobo大叔保龄球例子 可以下载讲解PPT
简书上一个更详细的文章

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 195,653评论 5 462
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 82,321评论 2 373
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 142,833评论 0 324
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 52,472评论 1 266
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 61,306评论 4 357
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 46,274评论 1 273
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 36,658评论 3 385
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 35,335评论 0 254
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 39,638评论 1 293
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 34,697评论 2 312
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 36,454评论 1 326
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 32,311评论 3 313
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 37,699评论 3 299
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 28,986评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,254评论 1 251
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 41,647评论 2 342
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 40,847评论 2 335

推荐阅读更多精彩内容