TDD(Test-driven development),也就是我们常说的“测试驱动开发”,是由 Kent Beck 在1996年提出的概念。TDD这个术语,经常被人挂在嘴边,然而真正在项目实施,却寥寥无几。

是TDD对开发者要求太高?还是TDD根本就不值得去做?
非也。为了让大家对TDD有一个具体而亲切的认识,我先给大家举一个在编程中使用TDD进行开发的实际例子。

Bob大叔的保龄球训练

这是一道计算保龄球比赛一局总得分的编程题,保龄球的计分规则非常简单:

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

题目要求我们提供一个名字为Game的类,这个类有两个方法:

  • roll(pins : int):每次球员扔球后执行这个方法,入参是此次扔球击倒的球瓶数量。
  • score():每局比赛结束时执行的方法,返回这局比赛的总得分。

下面开始使用TDD来完成这个编程训练。

如果此时你已经在开始构思要如何实现,请打住!因为这不是TDD的风格
记住,先别想着怎么去实现,先写测试用例,也就是先把你调用这个Game类的代码写下来。

首先,我们创建一个BowlingGameTest类:

import junit.framework.TestCase;public class BowlingGameTest extends TestCase {}

接着添加第一个测试用例:

当我们刚刚new了一个Game对象时,编译器就提示错误了,此时暂停测试用例的编写,开始编写产品代码!(这么做似乎有点过于耿直,不过对于加深对TDD的印象还是很有帮助的)
我们创建了Game类,此时编译通过,执行所有单元测试,绿条!

接着我们在第一个单元测试中调用roll方法和score方法,同样的,我们遇到编译不通过的问题,再依次给Game加上对应方法后,我们得到了下面这段代码:

我们心里很清楚,这个代码是经不住考验的,我们随便添加一个单元测试,都可以让测试用例不通过。比如我们让一个保龄球世界排名倒数第一的球手去比赛,每轮他都只击倒一个球瓶,测试用例毫不犹豫地失败了:

于是我们要修改一下逻辑,在每次roll的时候,加上分数。细心的读者可能还发现了,下面这段代码还对测试代码进行了重构,把每个单元测试都要做的new Game()操作抽取到了setUp方法中:

接着我们又发现我们经常要模拟很多次击倒相同数量球瓶的操作,因此我们把这个操作抽取成一个rollMany方法:

我们的代码到这里还完成不到1/3,但是我们已经做了两次重构,没错,TDD的过程,也是不断小步重构的过程

接下来,我们测试一下Spare的场景,这一次测试用例又理所当然的失败了(不要担心一次次失败会打击自信心,因为这些都是我们刻意制造的失败,人们面对意料之中的失败往往更有激情)。而当我们准备动手修改产品代码时,却发现了一个代码设计层面的问题,那就是我们在roll方法里面做了roll不应该做的事情,roll意味着扔球,而我们却在里面修改了得分:

此时我们需要把新增的用例暂时屏蔽掉,然后对产品代码进行重构,roll专心做它的事,把计算得分的活交给score来做:

接下来,就是继续放开我们之前屏蔽掉的测试用例,继续修改产品代码,让测试用例通过,然后再添加STRIKE的测试场景、添加更多的测试场景…… 这些过程就不再赘述了,因为作为一个TDD的例子,前面这几个步骤,已经足够让大家对TDD有一个比较深刻地理解了。

关于这道题目的完整解答过程,大家可以到Bob大叔的TheBowlingGameKata去下载对应的PPT。

TDD的三项法则

上面的保龄球训练中,我们一直在遵循着TDD的三项法则:

  • 在编写好失败的单元测试之前,不要写任何产品代码。
  • 只要有一个单元测试失败了,就不要再写测试代码。无法通过编译也是一种失败。
  • 产品代码恰好能够让当前失败的单元测试成功通过即可,不要多写。

遵循着三项法则,我们开发的过程就是下面这五个步骤不断循环、小步迭进的过程:

  1. 添加测试用例。
  2. 运行所有测试用例,如果新用例失败了,执行下一步,否则返回上一步。
  3. 编写产品代码。
  4. 运行所有测试用例,如果通过,执行下一步,否则返回上一步,直到写出满足测试用例的代码。
  5. 重构。
  6. 返回第一步,继续循环。

TDD的优势

TDD带来的最大好处是提高了单元测试的覆盖率。 传统的先写产品代码,再写单元测试,有两个弊端:

  • 一方面是由于产品代码已经成型,生米已经煮成熟饭,你再来写,很容易就会陷入思维定式中,起不到发现Bug的作用;
  • 另一方面,这往往会让单元测试成为一项政治任务,产品发布前几天,发现单元测试覆盖率不足,来一场全员写测试用例的运动,这样写出来的代码质量肯定不好高。

那么提高了单元测试的覆盖率对产品有什么好处呢?健康的单元测试覆盖率是在90%以上,为什么要那么高?高覆盖率带来的好处主要有以下三点:

  • 确定性。每轮迭代我们的产品都会新增不少代码,这些代码对之前的功能有没有影响?如果我们有一套覆盖率达到90%的单元测试,那么我们只需执跑一遍测试用例,如果全部通过,那么我们至少就有90%的把握可以交付。反之,如果覆盖率越低,我们也就越心虚,越需要更多的人力去进行手动验证。
  • 让你有勇气重构代码。当你看到糟糕的代码时,你的第一反应是:WTF!!! 接着,你会说,我才不去碰它,万一碰出问题了,还不都是我的锅! 但是如果你能够确信自己对代码进行的大刀阔斧的修改,不会破坏任何东西,那么你是不是就更有勇气去重构它了?这就是TDD最强大的地方,它让你拥有一套值得信赖的测试,打消你对修改代码的恐惧。Martin Flower在他的《重构》中也指出,完善的单元测试是他进行重构的基石。
  • 单元测试即是文档。同事离职了,他之前负责的模块交到了你手上,你要尽快熟悉这个模块的业务逻辑。看文档?程序员写的文章一般都不太容易看,而且文档经常会和代码不同步,代码修改了文档没跟着改的事情经常发生。看源码?看完也不一定知道为什么要这么实现呀。如果这时候有一套非常完整的单元测试,那绝对是所有接手别人代码的程序员的福音!首先,代码不会撒谎,其次,测试用例明确告诉了你这个函数是做什么的,什么输入对应的都有什么预期输出。单元测试就是最好的底层文档,哪个专业人士不想提供这样一份文档呢?

除了提高单元测试的覆盖率,TDD还能够促成良好的代码设计。由于你先写测试代码,你会尽可能的让代码调用起来更加简单方便,这也就促使你去考虑如何更好的设计代码。如果不先写测试,最后很有可能就会出现一个函数里实现的功能过多,或者和其他代码过于耦合而无法测试的情况。

TDD的局限

在学习一项技术时,总是要提醒自己——“没有银弹”,任何技术都有其局限性。然而,由于用TDD的人实在不多,在网上搜了很久,也看不到什么特别有建设性的观点。下面是我找到的一些关于TDD局限性的看法:

  • 对开发者有较高要求。要想做好TDD,必须掌握好单元测试、重构等技能,还要能够写出整洁代码,不然如果写出来的单元测试都很糟糕,那只会加重维护的负担。 —— 评论:那我更要用了,这才能显得我技术过硬啊…..
  • 习惯的转变。对于一直习惯上来就写代码的程序员,现在要他们先写测试用例,难免会让他们有些不习惯。 —— 评论:这不跟戒烟一个道理么,改掉坏习惯、养成好习惯的过程总是痛苦的,等我练成TDD大法之后,哼哼…
  • 过多的测试用例使得构建变得缓慢。 —— 评论:这就要求我们减少没有意义的测试用例了,比如一些简单的get和set方法,就不需要写测试用例啦。

行动起来

回到这篇文章的主题,“如何说服你的同事使用TDD”,首先,你被我说服了么?
如果你的回答是Yes,你被我说服了,你打算开始使用TDD,好,下面我给出一些练习和使用TDD的建议。

1)TDD Katas 训练
先不要急着在工作中去使用TDD,Bob大叔的网站上还有很多跟保龄球训练类似的题目,你可以去练习一下。而且,相同的练习可以反复训练,Bob大叔在他的《程序员的职业素养》里是这么说的:

和习武者一样,程序员应该懂得很多种不同的卡塔,并定期练习,确保不会淡化或遗忘…
真正的挑战是把一个卡塔练习到炉火纯青,你可以窥见其中的规律。要做到这一点可不容易。

下面是Bob大叔推荐的卡塔:

  • 保龄球
  • 素因子
  • 自动换行

Bob大叔的主页上还有很多其他的Kata,大家可以上去探索探索。

2)编程题训练
网上TDD的例子确实有限,但编程训练题却是一大把,很多网站也都收录了许多经典的算法和数据结构的题目,我们完全可以使用TDD来对付这些题,把代码提交上去,如果没有通过,就说明自己的测试用例不全。
使用TDD来对付编程题,非但不会影响你的答题时间,反而让你一小步一小步的完成题目,而不是像以前一样,思考了很久,却一行代码都没写出来。
网上提供在线编程练习的网站很多,我自己现在在用的是LintCode,大家也可以去自己喜欢的网站上练习。

3)面试时使用TDD
面试时,如果面试官让你在纸上写代码,那就给面试官show一下TDD吧,将纸张一分为二,一半写测试用例,一半写实际代码,当然,你可以先写伪码,因为总免不了要重构,全部写完再用实际代码写一遍,交给面试官。
同样的,TDD可以缓解你一行代码都写不出来的紧张心情。

4)运用到实际项目中
实际项目中运用TDD,通常不像做编程题那么轻松,你可能需要使用Mock/Stub之类的东西,不过这些都有现成的代码库,比如Spring就提供了一套很方便的测试库,你只需要花费一点时间了解一下如何使用即可。
如果你有个开明的领导,不妨在做TDD之前跟他说一下,说不定他和你有一样的想法,并且会给你一些支持;如果你的领导看起来并不那么开明,你觉得有很大可能性他会禁止你TDD,那就别告诉他,悄悄执行,如果最后确实有成效,跟他说,并且要求在组内做一次技术分享,如果最后没什么成效,也不要紧,领导看到的只是你测试用例非常完善的代码。

好了,我似乎又走题了,到底“如何说服你的同事使用TDD”,很简单,用实际行动告诉他们!如果你在使用了TDD之后,确实提升了代码质量,降低了代码缺陷率,那么请理直气壮地和他们分享你使用TDD的心得。

TDD是专业人士的选择,它是一项能够提升代码确定性,给程序员激励、降低代码缺陷率、优化文档和设计的原则。对TDD的各项尝试表明,不使用TDD就说明你可能还不够专业。 —— Bob,《程序员的职业素养》

参考内容

  • 维基百科 - TDD
  • 《程序员的职业素养》
  • UncleBob.TheBowlingGameKata
  • 《重构》
  • 《测试驱动开发》
  • 保龄球规则
  • The Pros and Cons of Test-Driven Development

如何说服你的同事使用TDD相关推荐

  1. 如何在Spring Boot中使用TDD写出高质量的接口

    本文发布于专栏Effective Java,如果您觉得看完之后对你有所帮助,欢迎订阅本专栏,也欢迎您将本专栏分享给您身边的工程师同学. 之前在<如何说服你的同事使用TDD>中介绍了为什么要 ...

  2. 程序员如何更好的获取自己的学习书单

    有同学在评论里留言,说让我分享一下读过的书和感受.事实上,每个人的目标都不一样,适合我的书不一定适合其他人,因此我觉得分享一下我挑选书籍的方法,顺带分享几本好书,可能对读者来说更有价值.  所以在分享 ...

  3. 怎样成为一名专业的程序员

    "专业",在百科里有下面这三种解释: 学校里的学科 生产部门里的业务部门 有知识修养的专门职业者;职业画家[演员等],职业选手;内行,专家 标题中的"专业",指 ...

  4. Marty Cagan:怎样寻找出色的产品经理

    <程序员杂志>的文章,原帖位于http://www.programmer.com.cn/7760/ 写的很好,自己转贴存储一下,也符合Product Owner的要求,就是--要求太高了! ...

  5. 定制AjaxControlToolkit:给CalendarExtender增加清除功能

    其实像AutoCompleteExtender之类的其它扩展也是与此类似,这里只是用CalendarExtender举例子了. AjaxControlToolkit确实是个好东西,但是我实在不明白设计 ...

  6. 定制AjaxControlToolkit(1):使一个CalendarExtender对应N个TextBox

    其实像AutoCompleteExtender之类的其它扩展也是与此类似,这里只是用CalendarExtender举例子了. AjaxControlToolkit确实是个好东西,但是我实在不明白设计 ...

  7. 现代软件工程讲义 5 项目经理 Program Manager

    在一个软件团队里, 不同的人有不同的投入, 我们在 猪,鸡和鹦鹉 的故事里已经说明了. 不同的人还要在团队中担负不同的任务, 我们也要讲一下. 开发人员 (大部分内容在: 现代软件工程讲义 2 工程师 ...

  8. 上线随想之2011-03-30

    31日切换系统.现在的工作主要是增量同步一下这段时间所更新的主数据信息. 心开始平静下来,不再那么忐忑不安. 曾经以为没有SAP顾问支持就做不到的东西,现在一步步走来,虽然曾经比较狼狈,但总体上讲,1 ...

  9. 怎样才能算是在技术上活跃的小公司

    今天和小伙伴们聊了一个晚上,也得益于最近经常面试.所以对怎样判定一家小公司在技术上是有活跃的公司,有了一些自己的领悟. 作为一个程序员,不管在什么样的环境中,一定要认清自己的本质,不断的提高自己的技术 ...

  10. 现代软件工程 第九章 【项目经理】练习与讨论

    9.5.1  PM们的故事 讲了这么多条条框框,我们还是来讲几个故事吧. A)是不是所有的好功能都是由PM主导,一步一步根据用户需求,按照用户场景设计,然后进行可用性测试等等步骤之后得来的呢? 功能本 ...

最新文章

  1. 2021年大数据ELK(五):Elasticsearch中的核心概念
  2. 怎么设计计算机网络共享,如何设置网络共享 网络共享设置方法【详解】
  3. P3733 [HAOI2017]八纵八横(线性基/线段树分治)
  4. java程序员常用查询和学习的网站
  5. Java核心类库篇2——lang
  6. 解决:Error during artifact deployment. See server log for details.问题
  7. 2021年美容师(初级)报名考试及美容师(初级)模拟考试题
  8. H5移动应用的发布优化(四)图片优化
  9. 家用洗地机怎么选?2023高性价比家用洗地机推荐
  10. 【PS】海报设计,滤镜
  11. Python入门习题大全——猫和狗
  12. 【PM】软件项目管理流程和方法
  13. cmd-ping命令
  14. 计算机研究生怎么研究黑洞,麻省理工学院研究生的工作使黑洞的事件视野成为可能...
  15. sqlsession生命周期
  16. FreeSWITCH 与 Asterisk(译)
  17. 关于Python中的lambda,这篇阅读量10万+的文章可能是你见过的最完整的讲解
  18. 新浪微博开发之授权用户登录的实现
  19. mybatis报错There is no getter for property named '***' in 'class ***'问题
  20. java 获取邮编_JAVA爬取邮编信息,生成XML

热门文章

  1. 台式计算机关机后自行重启,台式电脑点关机后又自动重启怎么处理
  2. 教你轻松获取windows10锁屏壁纸
  3. 计算机主板 安装系统,Intel NUC迷你电脑安装win7 BIOS设置(适用英特尔主板)
  4. mysql数据库管理-sql_mode参数(严格和非严格模式)
  5. [渝粤教育] 西南科技大学 英语(B)1 在线考试复习资料
  6. 短消息类新旧服务代码对应表
  7. 软件测试-mocha入门
  8. 趣拼图——项目演示及代码
  9. 金山词霸笔试题目笔记
  10. 关于穿透冰点还原等还原软件的方法