参考链接:

Joe.Dev的博客:http://joe-dev.blogspot.com/2014/06/tdd-is-dead.html

Martin Fowler的视频讨论总结:https://martinfowler.com/articles/is-tdd-dead

David Heinemeier Hasson的博客:https://dhh.dk/ (这家伙居然还玩赛车?!)

Kent Beck的《RIP TDD》:https://www.facebook.com/notes/kent-beck/rip-tdd/750840194948847

前段时间刷了一下uncle bob的博客,有篇文章标题叫TDD-Harms-Architecture,打开一看开篇先安利了好多友情链接,于是进去了解了一下,发现这底下居然有场大佬云集的论战。虽然全英文但是核心观点不难理解,于是顺着热心网友提供的整理资料耐心去看了一下。

1.概念汇总

这里是Martin Fowler对TDD几个相关概念的精炼与整理。

1.1TDD

测试驱动开发。Martin Fowler博客中有博主对其定义。简单地讲TDD是一种三步骤形成循环的开发实践:

  1. 测试先行
  2. 编码直至测试通过
  3. 将第二步编写的代码重构后归并到之前的代码中

这个循环也被成为”红-绿-重构“,其优势在于:

  1. 这种实践强制开发者先考虑interface,面向接口编程
  2. 代码写出来就是self-testing-code
  3. 就算写出来的是坨shit,它也是通过测试、有用例保护的shit
  4. 重视重构,并将其作为保持代码整洁的关键

1.2.Self-Testing-Code

那么上面提到的self-testing-code又是什么:字面意思就可以验证自己的代码,差不多就算这个意思了。它的要求是编码也要编写自动化测试,作为代码是否可以交付的标准。其优势如下:

1.作为持续集成/持续交付的关键(印象深刻,没有测试用例保护的代码你敢动吗?看着奇奇怪怪又不敢改难不难受?)

2.表层的:极大减少bug数量,养成开发者写测试用例的风气,找到一个bug可以顺藤摸瓜摸出一类bug

3.深层的:从此对现有逻辑的改动有了【安全网】,不会在改动的同时引入新的bug,不会因此惧怕变化进而拒绝改动进而积累技术债进而周而复始地推倒重做

另一方面,它也推动了monitor的发展:在生产环境中加入monitor,出现问题立刻得到报警,迅速修复or回退版本,最小化损失。

它跟TDD的关系,Martin Fowler认为TDD是self-testing-code的一种实践,因为TDD需要test driven, 需要在最开始写测试代码;而self-testing-code只要有测试帮忙保证代码质量就好,不关心啥时候去写它

1.3.Unit-Test

单测。Martin Fowler认为单测的定义一直很模糊,应该被规范。

对于单测的定义,达成共识的有:

1.low-lever,小范围

2.由开发者自己编写

3.快

而存在争议的主要问题则做了具体分析。

1.3.1单测的最小单位

单元测试的最小单位是什么,或者具体到Java等面向对象语言中,最小单位是否就是一个class?

这个问题Martin Fowler认为应该视具体情况而定,最小单位应该是一个概念集合or相对独立个体之类的东西,只不过很多情况下都把这些放在同一个class中罢了。

1.3.2单测中对其他class依赖的处理

主要有两种方式:

1.单测就只测一个class,其他依赖用mock等手段解决

2.单测可以假设其依赖的class没问题,然后不mock直接用真实class去测试

纠结点在于,不用mock显然更方便,但容易受真实环境、速度、其他class的bug影响而导致测试不通过。这个要辩证去看,取长补短,怎么方便怎么来。譬如网络通信或者很久才能传回来结果的这种,该mock就mock省时省力;另一方面随着硬件的发展,例如数据库访问已经没那么慢了,那么也完全可以直接访问数据库啥的,省去一步mock

1.3.3单测的速度标准

怎么才算”快“不同人不同看法了,只要速度能在可接受范围之内,能让我们乐意去频繁地执行它就ok

另:对于那些”慢“的测试,考虑构建一条开发流水线,让流水线帮忙执行

2.DHH的质疑

2.1演讲

RailsConf 2014 演讲中顺便抨击了TDD,他认为测试本身没有错,但将TDD奉为圭臬就算本末倒置。(但整场演讲并非为了怼TDD,而是阐述开发时应有全局视角,写的代码应该易读易维护)

论据如下:

1.TDD将“容易测试”作为代码质量标准,但好测的代码其质量未必就高,因为开发者可能会牺牲代码结构整洁来追求“好测”

2.测试本身在开发过程中就应该起辅助作用,而不是成为主导

3.单元测试。单元测试只关注局部,TDD让单测驱动开发是错上加错。集成测试才是实际上应当关注的东西。单测全过不代表集成测试也没问题

4.过度追求代码覆盖率、用例:代码的比例以及测试运行的速度:单测覆盖了整个系统也不代表能用,高比例意味着更多代码让代码更臃肿,单测关注点小了当然运行更快。cost != value

DHH分析了为什么我们看好TDD的原因:

“Measurement is the first step that leads to control and eventually to improvement. If you can’t measure something, you can’t understand it. If you can’t understand it, you can’t control it. If you can’t control it, you can’t improve it”

不能度量->不能理解->不可控->无法改进。

这话没错,但我们知道命题跟逆否命题等价,以上面为例,应该等价于:能改进->必可控,可控->必能理解,能理解->必能度量。而对于TDD,则被理解成了“有度量方案,才能提高代码质量”,也就是错误地把否命题跟原命题等价了

然后他引用了TDD的主要支持者之一Kent Bech的观点:

"我们靠可运行的代码拿薪水,而不是单测。因此我认为应该用尽可能少的代码证明我的代码是正确的,从而建立我对自己代码的信心。"

但实践中,大部分人跑偏了。清晰整洁的代码才是我们应当追求的首要目标,我们大部分情况下写出来的第一版代码更像是一份【草稿】,只有不断重写改进简化,去掉90%无意义的“废话”,才能将真正的重点突出出来。

2.2 博文

由于广受争议,接着他写了一篇《TDD已死,测试长存》的文章(tdd is dead, long live testing)表达自己的坚定反对TDD的立场。

文章观点跟上面演讲类似,抨击TDD因为耗时而贬低系统测试、催生各种仅为方便测试但损坏系统清晰架构的产物如service-object,command-pattern等,然后强调应该给system-test它应该有的地位但不希望系统测试被误解为“system-test only”;顺便他肯定了test-first的价值和对推动自动化测试发展所做的贡献。

3.反驳

3.1 Uncle Bob入场

题外话:其实我个人挺欣赏Uncle Bob的,了解到这场论战也是从他博客摸过来的。他《整洁代码》我没看过,但《整洁架构》看过,吹爆,力荐。

Uncle Bob针对DHH的博文发表了自己的意见。简述内容如下:

1.Diss一下DHH不恰当的比喻,认为DHH过分夸大了TDD的弊端

2.表示理解DHH的观点:唯TDD论调当然让人不舒服,但如果没有TDD或者类似TDD的手段支持代码,那么只会感觉更加不爽

3.重申TDD的一主三次原因:

次要原因如debug速度快,提供的测试用例可以充当文档,测试可以帮助解耦,这些都或多或少有些争议。

主要原因则母庸质疑:如果你有一套可信的测试套件(suit),那么就会乐意根据测试结果部署系统;如果测试执行又很快,那么清理、重构代码的时候也会很轻松

4.TDD给了开发团队清理代码的信心,避免团队陷入“大泥球”中,这种信心值得我们付出一些额外的努力,但推崇者们一直强调这一点,就使得一些如DHH等人感觉不适。

5.对于DHH希望系统测试替代单测成为主导的想法,Uncle Bob觉得只要你的测试能提供给你上述的那种信心,那么无论何种测试都是ok的,但一则仅仅系统测试很难探测出细节设计中存在的bug,二则很多真实环境的运行速度远远没有快到DHH认为的“可以接受的慢”

6.希望某一天系统测试确实能达到DHH的标准,那时候Bob当然会选择更少工作量的系统测试了

其实我个人感觉到这里就有些抓字眼儿抬杠了,DHH完全摒弃单测,Uncle Bob抓住这点攻击单纯系统测试的弊端。但实际开发那肯定两者结合呀。

3.2 DHH反击

(其实前面还有篇被推荐的Jim OKelly文章,但是链接500错误了,看不到也搜不着转载就很难受)

没几天,DHH写了篇《Test-induced design damage》

不好测的代码不是好代码,这句话催生了很多损害性的设计:通过不必要的中间层或者概念开销使代码更容易测试,但TDD信徒不承认这些额外的代码是损害性的。

并非所有class都适合单测。比如controller,这就是为系统测试而生的嘛;再如数据库相关,现在早就不是曾经那个数据库慢的要死的年代了,不mock问题不大。(不过这俩都是用ruby举例,若涉及ruby特有的区别还请留言指明)

testing-first的思路总会让实践者倾向于单测驱动。

Rails/MVC测试的“底线”:

  1. modle-test测试复杂逻辑足矣,但没必要将它们跟数据库割裂开。通过测试框架和复用公共资源可避免针对每个case昂贵的初始化开销
  2. 对于controller集成测试足矣,鉴权、邮件等操作没必要单独提取成command模式之类,controller中的私有方法并不总是邪恶的。我们要的贫血跟啥都没有的壳壳是有区别的。
  3. 对view的测试也该用系统测试,应当亲自去浏览器中保证这些东西都是没问题的。

重点在于根据实际情况取舍。例如UI复杂model简单那么自然应该向系统测试倾斜;controller仅仅传递个参数,那么甚至没必要去测试它们。

总之,设计应该驱动测试,而非让测试驱动设计。对实际项目的设计规划才应该决定系统中哪一层才该让我们下更多工夫去测它。唯有抛开对TDD的执念,才能看到真正提高代码质量、清晰程度的方法,而非怎么让它容易测试,毕竟系统完整性比好不好测重要。

这篇文章是对DHH上一篇文章的补充,用几个Rails/MVC的例子具体说明测试要看具体情况,不可唯单测论。至此我感觉TDD支持与反对两拨人最终目标并不相悖,只不过DHH感觉TDD中部分人太走极端,而Uncle Bob又质疑DHH的测试方案能否给任意修改系统代码提供安全网和足够的信心。

3.3Kent Beck经典反串

Kent Beck在Facebook发表经典反串文《RIP TDD》(这篇我真的找得很难受,年代久远+网络差劲,所以存了一份在这里)

我不太喜欢一句话绕八个弯儿,所以就直接放Kent Beck想表达的意思了。

抛弃TDD会带来如下麻烦:

1.过度设计。有测试用例把关时可以只关注用例需要的功能,而不是凭自己感觉过度去设计它(You Aren't Gonna Need It)

2.失去评估我API设计是否满足需求的评价标准

3.容易写出常犯的逻辑小错误

4.TDD可以通过测试用例直观表达对需求文档所预期的实现方式和开发期间的想法,也可以通过维护用例来维护这些需求

5.就算对如何实现没有思路,也可以先把测试代码写出来然后慢慢想怎么实现功能

6.TDD强制将interface与具体实现分离的思路可以防止具体逻辑脏了interface代码,同时可以先把暂未实现的interface拿去用,不耽误其他人使用时对其的反馈

7.TDD能直观地跟同事讨论编程中遇到的困难

8.TDD的反馈实在太好用了,没有类似的把关方式难以确信自己代码是否存在问题

注:我十分担心自己误解了大神的意思,而这篇文章很短,强烈推荐大家亲自去看一眼后反过来印证交流。

Kent Beck并未回击DHH观点,而是反串方式指出TDD不可忽略的优势。这里我越想越觉着他高明:很多情况下讨论会发展得像十字军东征一样,一定要铲除异己争出高下,但每人性格经历啥的都不一样,加上软件行业项目的背景之类也不同,你坚信的未必是适用于别人的。Kent Beck很高明,他摆出TDD的益处,尝试让大家一起探讨出一套更合理的开发模式,化争辩为探讨。

3.4.Uncle Bob再发声

《When TDD doesn't work》

这篇文章中Uncle Bob将TDD跟会计的【复式记账法】作比较,会计中账本汇总起来一合计,平了,帐没问题;TDD一次测试编码一次开发编码,开发编码在测试用例上通过了,那么代码也就没问题。复式记账法实际上是最经典的记账手段之一,那么同样思路的TDD也有类似的存在价值。

但是软件行业确实有跟会计行业的不同之处。Uncle Bob认为不同之处在于其【边界】。

第一,边界以外,例如音频输出还是亲自听一下放心,UI界面当然亲眼看下才能通过,这些都没必要专门准备单测。

第二,交界处,例如控制UI显示的CSS代码。TDD的指导原则是test应当尽可能具体,而开发代码应当尽可能通用,我们不可能为每个test-case添加else语句来过测。但是CSS这种不吃这一套,写CSS代码就是要具体,这种情况下TDD就失去了其test-first的指导作用,那么也没必要为其准备单测。

第三,测试本身,为测试代码准备测试只会无限递归。对于诸如测试框架、测试替身等,实际的单测执行结果与生产代码即是对其验证。

另:不可以因为边界处可以绕过测试就把各种逻辑往边界处堆,上述的边界都是指剔除具体逻辑之后的边界。

【边界】处这些难以TDD的东西,也被称为【谦卑对象】(Humility,参见他《整洁架构》中有展开谈),以UI为例,即仅控制UI展示而不包含具体展示逻辑的代码(控制逻辑放到诸如presenter等其他模块中以保持代码整洁)。这种设计会破坏系统架构么?Uncle Bob认为不会,因为UI的修改频率与其他模块是不一样的,分离开反而有利于两者以不同速度演进(同样在《整洁架构》中有详细举例展开)。

3.5 DHH继续阐述观点

《Slow database test fallacy》,作为对上一博文的补充,举例说明自己如何不用TDD、保持修改代码时的自信、又不因test-first思路破坏代码清晰程度的。

传统的TDD一般是不测数据库等外部系统的,因为毕竟慢,一般都会用mock代替。这条建议在97年适用,而现今硬件高速发展,已经完全可以满足我们对测试速度的需求了,这时候为了速度而加入中间层【破坏】*系统架构隔离数据库是否仍然值得,就应当重新评估了。

DHH提出用model级别的测试来替代单测:

两种测试级别model-test和针对整个系统的test-suit,前者很快,后者略慢。

单处改动只用model-test验证(如果单处改动也需要用较慢的test-suit,说明你的系统耦合严重),改好整个功能之后用test-suit确保其他模块不受影响。

两者交替进行,这样可以至少在编码时考虑到整个model的架构清晰程度,同时也能保证有改动代码的信心(系统测试外还有小范围的model测试)。

*:个人观点我仍感觉数据库算是基础设施,将核心业务逻辑与外围基础设施用interface隔离开我认为是值得的。(此处待讨论,因为感觉自己多少受长时间的mvc模式影响)

3.6  Gary Bernhardt 的批评

这篇也是找了好久,作者把他原文给删了不知咋回事儿,所幸有个期刊登过才最后找到了。(注:这里资源下载分会偷涨,若有需要可私信留言要)

Gary文章的主要内容是指出DHH文章的不实之处:

1.TDD跟mock的概念并不是绑定的,mock做法可视情况而决定是否使用。例如他简化controller职能以省略对其单测,对于model层也是直接连数据库测试,但速度都达到自己标准。

2.对于test运行速度提出了更具体的说明。他的标准是300ms内运行完任何测试,这样摁一下运行立刻能拿到结果,不会分心或打断思考。仅当觉得设计上出了问题才会停下来,但那种情况不多。

3.包含全部单测和集成测试代码之后,test:code=1.4:1,也不会到DHH讲的4:1那样夸张。

4.DHH所说的诸如spring预加载等手段才具有极大的复杂性,这些才更容易导致混乱

5.对于TDD过度隔离分层破坏代码的指责也是扯淡(我个人也感觉隔离一下反而让结构更清晰了些...)

6.公众人物过分夸张容易误导粉丝,谨慎言行

7.test速度并不等价于全盘接受TDD,怀疑mock或者测试替身(double test)都只是权宜之计因为这两者都需要不少额外的代码。

8.TDD无疑有用,但无论TDD还是任何其他技术都有自己的局限性,勿当银弹。TDD的问题在于如果想甄别这些特殊场景,就需要首先全盘接受TDD然后按他观点去做,这容易让新手走极端;但花一些时间去思考、学习这些例外场景是值得的。

这篇主要是不满DHH过度夸张的说法。但他提出的观点都有具体的数据支撑。并且他同意不可把TDD当银弹使。

3.7 Uncle Bob的另一篇文章

Uncle Bob似乎真的对这个话题很感兴趣,隔天又发一文《Test Induced Design Damage?》。文章内容:

1.缅怀Jim,赞他观点:“应用逐渐发展,但框架默认提供的东西并非也是越多越好”

2.赞Jim分离关注点、解耦所作出的贡献:把按不同速度演进的代码分割成不同模块是必要的。

3.mock只是手段,分离关注点才重要。关注点分离之后自然运行速度快。例如解耦web

4.DHH把分离关注点误认为是为TDD而做的妥协了。Jim总会这样分割模块,这跟提升测试速度完全是两回事儿,也完全没有损坏代码结构。实际上,DHH并未拿出具体的例子。

5.测试运行速度应当是良好设计的外在表现之一。随着应用规模增长,将模块解耦很有必要。

4.会面

一段时间后,由于反响太大,DHH也跟Kent Beck, Martin Fowler聊过。经过一系列的安排之后,几位大佬开展了一个系列座谈会,共同讨论这些争议问题。Martin Fowler的博客里有对此的总结

4.1TDD and Confidence

DHH上来先提出了自己的疑惑与观点:

TDD定义与单测定义?

TDD想快就得加中间层,这使得对架构造成破坏,mock太重太多额外代码仅仅为了将所有test转变成单测

TDD红-绿-重构三步骤很难用太死板,每人思维方式不同这种步骤太限制人思维方式

Martin Fowler希望Kent Berk谈一谈他如何与TDD结缘的以便了解其定义,讨论如下:

K:先写了测试,编码时知道做过不了我就能避开它了。灵感来源是如果我发现代码有问题自己又不好解决,我会找一种简便方式先实现再说。对于大型问题我往往不知所措,而TDD解决了这些。

M:我最开始的时候团队重视测试,但并非在最开始就测

K:对自己代码信心才是关键。写完别人测,那我就只好在家等着祈祷它没问题,觉都睡不好。TDD是提供自信的方案之一,之一

于是这场讨论开始关注【代码自信】与TDD的区别与联系:

D:我认为代码敲的顺不顺心跟【代码自信】是相近的,TDD跟我讲没测试就算没完成工作让人不爽。TDD的红-绿-重构三步走并非全人类的天性,我觉着这是本辩论的起源:先提问后实现,还是先写出零散想法,将其聚合后再验证。

K:昨天编码,一半适用tdd一半不适:有时候真实数据太多样,甚至难以用数据结构去表示它们,只能先做出异常处理打好日志,到时候再说。

D:同感,我也在需求十分明确时使用tdd,但实际情况不同。你们用TDD可以接受何种范围内的代价,mock多少才不会成为负担?

K:TDD是否值得使用要看它的回报是否与投入相衬。DHH这段很关键,很多时候平衡利弊是理解系统设计的关键。我不强求必须TDD/mock,但我需要反馈途径

M:D对TDD破坏系统设计的指责:我不认为测试必须隔离必须mock,我认为这无关紧要。我将这些分成两个概念:self-testing code和TDD,前者才重要,但TDD是实现前者的手段之一,至少没辙的时候考虑它一下

D:发现我所反对的TDD跟你们理解的TDD不少一个东西。开发时的自信是我们要共同追求的东西,但很多信徒认为无论自信还是self-testing code等我们认为“主要”的东西,都是TDD的副产品。太多人把这些混为一谈了

总结一下,这次讨论在【首要目标】方面达成了一致:如何能有敲代码的自信才是关键,mock,TDD等都是手段之一。达成目的最重要,手段就只是手段。但TDD确实是被广泛使用的手段之一,没辙的时候可以考虑先按TDD红-绿-重构三步走搞着。

4.1.* 余波

讨论之后各路大佬发表自己看法。

Gary Berhardt对mock的看法:mock很”重“往往因为错误地深层嵌套mock了,这种会暴露实现细节,当具体实现代码需要更改时,测试用例也得跟着变。一方面mock能免则免,另一方面考虑重构降低协作深度(解耦)以避免嵌套。TDD优势有:1.人们倾向于偷懒把东西放到一个方法中,需要有强制力迫使大家对方法分解重构;2.TDD使我们在编码之前就能通过编写测试发现系统设计上的问题

Uncle Bob的看法:DHH也认为测试是必不可少的一环,能提供类似文档说明的功能,并且发现设计上的问题,最重要是测试才能带给我们【代码自信】。那么既然反正要做,为什么不把重要的事情放在最开始做呢

4.2 Test-induced design damage

上回讨论时间不够,哥仨约了第二次。

Martin Fowler开场指明要讨论是否TDD真的会对代码结构造成破坏。包括三个问题:这些破坏是TDD的锅么?这些所谓破坏究竟是利是弊?如何定义怎样才算是破坏?

DHH展示了一段具体的代码示例,简单的功能经过六边形加工后,被割裂成三部分:Controller,Runner,Repository,加上命令模式,徒增混乱。

K:DHH展示的并非TDD的问题,而是一个”在何种场景采用何种方式设计系统结构“的衡量付出回报的问题

D:如果你喜欢这种方式,为什么不把它广泛运用呢,这就是为什么TDD信徒们最终把它当银弹的原因,

K:我的最终目标是将TDD的有点融入进我自己的开发习惯中

D:更多的代码做一样的事不好,除非业务需要这样的复杂性。因为这对理解与维护都带来了额外的负担。虽然TDD给了开发者【代码自信】,但也因为这种自信,信徒们开始不分场景地使用它。

M:这不是TDD的锅。这是开发者们本希望让自己的代码易于测试,却最终过度地将自己代码逻辑割裂开,测试驱动变成了隔离驱动。实际上是错误的隔离粒度导致了这种问题。

D:同意过度隔离导致问题发生,但认为是TDD导致了隔离驱动开发。TDD的流行使得开发者真正关注这些隔离的可能性,例如repository的设计本意是让我们可以选择具体的数据获取方式,但实际上变成了因为repository方便测试,开发者开始无差别使用repository。

K:例子很棒。由于不同数据存取方式的异常处理逻辑不同,因此往往没有真正将这些东西解耦出去。

D:我发现内聚和耦合总是很难分清。有时候我们认为在解耦,但其实是在破坏内聚。DHH在权衡利弊的时候不会考虑这种所谓”解耦“,因为虽然无法单测,但集成测试完全可以替代它给DHH带来【代码自信】。

K:难测的代码往往背后藏着拙劣设计。通过测试我可以去发现并尝试改进它们。

D:测试当然可以帮助改进设计,但不能将可测试性与良好设计等价。

K:感觉DHH缺少自信,早晚会发现这种设计所蕴含的思想与之带来的优势的。

D:我不是我没有。我认为这是一种信徒行为,我要看到实实在在的好处才会接纳它。TDD当然有用,但我不会像信徒一样将其视为通用解决方案了。

K:并非特指TDD,而是泛指软件设计。TDD当然不是万能的,自己多体验体验然后自己做决定是否使用。

4.3 Feedback and QA

第三次视频讨论。

Kent Beck开场:反馈的等待时间有长有短,频率和代价有高有低,软件的使用时间难以评估。那么我们应当如何为我们的代码付出【适当】的努力才既能满足开发需要,又避免过度设计?

Martin Fowler补充:我们希望通过反馈得知些什么,认为有:1.用户到底需要什么 2.我有没有编码的同时对其他模块功能造成破坏 3.协作中别人有没有影响我代码的正常运行

DHH的观点:

1.早在TDD之前就有test-suit和QA(质量保证)的概念进行反馈了。TDD的流行是因为很多小地方缺少QA,而TDD恰好给到了开发者QA所提供的保障。但有时候TDD使得开发者过于自信了,不可因为TDD而忽略了传统QA的工作。

2.有时候我们并不需要做到99.99%,测试是为了代码质量,而不是覆盖率之类的指标。

然后开始讨论DHH关于QA的观点

K:其实往昔的东西未必都是好的。曾经遇到过QA,反馈极慢,协作极其困难。我认为现在反而是种进步,现在Facebook的QA就只是一句话:Nothing at Facebook is somebody else's problem.

M:同意K的观点,传统QA确实太慢了。

D:但现在很多人不知道QA的进化历程,它们只是简单将代码质量跟测试等价。而且就算测试全绿,你永远不知道用户会搞出什么幺蛾子来,但这确实也是反馈途径之一,但这跟全绿的测试用例相悖了。

M:这种反馈是对自己未考虑周全的测试用例的一种补充,虽然很烦但能学到很多。如果你感觉自己不再犯错,不能接受自己犯错的事实,这本身就是个错误,停止了作为程序员的成长。被凌晨两点的电话叫起来不能觉着犯,因为可以在反馈的问题中学到宝贵经验(额...)

这次感觉接近在聊人生了,干货不太多,大概就,不要抗拒反馈,接受错误并从错误中学习。

4.4 Costs of Testing

其实主要的思想基本都在之前讲过了,所以这一篇我搬运简单一些。这次也是对DHH几个观点的讨论。

D:开发设计本质是个权衡利弊的过程,权衡利弊先要认识短板。TDD有它的cost,例如过度测试,这使得难以拥抱变化:一处改动涉及到全部相关测试用例。

K:考虑delta覆盖率作指标,及时删除无用test-case,test:code比率要视情况而定,简单系统往往比率很低,这个数据无法说明问题。

M:(换了个角度)如果改动代码时缺乏【代码】自信,那么往往说明你的测试不够,或者说有价值的测试不够。另一个极端是测试太多造成代码难以改动。应当把我们的位置摆在这两个极端中间。

K:测试只是让系统正常运行中的重要一环,TDD不重要。

D:1.以前对系统最直观的认知方式是文档,现在则是测试,因为跑一跑就ok很直观。但测试或许不该因此应当被放在首要地位甚至高于生产代码;2.红-绿-重构里面,重构这一环节被严重忽略。

K,M表示赞成上面两点,并表示喜欢用测试用例表达自己对代码的改动。

D:代码品质往往太主观而难以量化,于是开发者倾向于去量化诸如测试数,覆盖率,速度等数值,这使得大家倾向于过度关注这些东西。另外,TDD已征服世界,因此难以对其改动

M:test-first重要,TDD不重要,测试性确实也重要。TDD并未征服世界,我们在开发时仍需权衡。

4.5 Q&A

好长,然后看了看总结文,发现观点仍然跟上面是重复的,就,最上面参考连接看吧。不过看提问的话,问题都提得emmm,太想要个通用解决方案了,什么时候适用什么时候不适应这类,当然要考虑具体场景的。引用一波参考连接里的总结:

  • KB 說:

    • TDD isn't dead, but is glad David set fire to it so it could come out like a phoenix.
  • DHH 在最後的對談提到:
    • 之所以發起 TDD is Dead 是因為人們沒辦法暢談 TDD 不 work 的地方,而被不斷地灌輸一定要 TDD 的觀念。
  • ML  做最後的結論:
    • 三人對於 self-testing code, TDD 在某些情境下 work 有共識

      • 但對 TDD 是否在其他情境下 work 則沒有共識
    • 在軟體開發過程,每個人應建立對自己及團隊有效的開發流程,不該盲目的套用技術
      • We're not in a codified science, so we have to work with our own experience.

5.开端与尾声

上面都是14年的事情,但是17年Uncle Bob又写了一篇《TDD Harms Architecture》谈起TDD(事实上我也是被这篇文章引过来看上面一长串TDD相关资料的),作为写这篇文章的起因,我把这篇文章主旨也放一放。

1.TDD是否破坏架构设计并非新观点,建议将Martin Fowler对TDD相关概念的定义、DHH的演讲、三位大佬的视频讨论汇总先读一遍然后自己得出结论。

2.测试代码【脆弱】的问题:一处改动导致多处测试代码需要修正,这被认为是TDD的锅,但这背后其实是设计的问题:

Uncle Bob原图

右边当然更容易测,也更少有改动test代码的需要:OCP与DIP的妙用在此体现。

3.大部分新手按TDD规则容易写出的是左边这种,最主要原因是单测粒度比较细,容易测试与class一一对应,造成测试用例与生产代码的紧耦合。其原因就在忽略了TDD中最重要的一步:重构。

5.在系统设计的时候,我们需要一种【宏观】的东西指导我们设计,但TDD的任务并不在此处。TDD是根据宏观设计,按照红-绿-重构的步骤去实现它们,并给开发者以【代码信心】。

6.为了连接各部分代码,API经过重构出现了,不同模块的代码被隔离开,优化架构的同时也使测试代码更易于维护。

7.As the tests get more specific, the production code gets more generic.小步快走的重构使得测试代码与生产代码向相反的方向发展,如果看不到这一层,例如生产代码堆if-else,那么当然会感觉TDD对架构造成了破坏。

8.总结:TDD原则并非解决方案,根据具体情况,参考TDD或者各种其他编程通识进行权衡取舍才可以;不依照设计原则重构不可能保证代码质量;TDD是一种思想不是银弹,误解与错误实践才是破坏设计的真凶;最后决策者是开发人员自己,应当自己做出适当的选择。

注:TDD三原则如下

定律一、在编写不能通过的单元测试前,不可编写生产代码。

定律二、只可编写刚好无法通过的单元测试,不能编译也算不过。

定律三、只可编写刚好足以通过当前失败测试的生产代码。

6.写在最后

其实一边写这篇文章一边感悟挺多的,但是怕个人观点会先入为主影响思考,就不拿出来放正文了,有啥看法咱们评论区交换意见。

测试驱动开发(TDD):质疑与思辩相关推荐

  1. 测试驱动开发(TDD)实战小例子(JAVA版)

    我们知道,测试驱动开发(TDD)的基本思想就是在开发功能代码之前,先编写测试代码.也就是说在明确要开发某个功能后,首先思考如何对这个功能进行测试,并完成测试代码的编写,然后编写相关的代码满足这些测试用 ...

  2. 「敏捷架构」核心实践:测试驱动开发(TDD)简介

    测试驱动开发(TDD) 是一种渐进的开发方法,它结合了测试优先的开发,即在编写足够的产品代码以完成测试和重构之前编写测试.TDD的主要目标是什么?一个观点是TDD的目标是规范而不是验证(Martin, ...

  3. Python测试驱动开发(TDD)

    Python测试驱动开发(TDD) 前言:TDD是一种敏捷开发模式,而不是测试方法. 测试很难 --- 难在坚持,一直做下去. 现在花时间编写的测试不会立即显出功效,要等到很久以后才有作用 --- 或 ...

  4. 测试驱动开发(TDD)的实践

    测试驱动开发(TDD)的实践 本文作者: Mr.J 本文链接: https://jiangtj.com/articles/almond/test-driven%20development/ 测试驱动开 ...

  5. 测试驱动开发-TDD

    测试驱动开发,英文全称Test-Driven Development,简称TDD,是一种不同于传统软件开发流程的新型的开发方法.它要求在编写某个功能的代码之前先编写测试代码,然后只编写使测试通过的功能 ...

  6. java测试驱动开发_java测试驱动开发(TDD)之《遥控军舰》

    永久更新地址:https://my.oschina.net/bysu/blog/1647738 写在前面:若有侵权,请发邮件by.su@qq.com告知. 本文主要是学习<Java测试驱动开发& ...

  7. C++ 测试驱动开发 TDD(一)

    文章目录 TDD 介绍 Soundex 算法示例介绍 增加Soundex 算法测例1 增加Soundex 算法测例2 Soundex 算法测例1 .2重构 后记 最近阅读了<C++程序设计实践与 ...

  8. java测试驱动开发(TDD)之《井字游戏》

    为什么80%的码农都做不了架构师?>>>    永久更新地址:https://my.oschina.net/bysu/blog/1632393 写在前面:若有侵权,请发邮件by.su ...

  9. 测试驱动开发-TDD(1)

    测试:作为动词,它是评估的意思:作为名词,它是导致最终是接受还是不接受的过程. 测试是相互独立的. 测试列表,就跟你生活中记录你的工作计划一样. 测试优先:你应该在什么时候编写测试呢?在你编写要被测试 ...

  10. tdd测试驱动开发课程介绍_测试驱动开发的实用介绍

    tdd测试驱动开发课程介绍 by Luca Piccinelli 通过卢卡·皮奇内利 测试驱动开发很难! 这是不为人知的事实. (Test Driven Development is hard! Th ...

最新文章

  1. php barcode_php生成条形码
  2. 如何将模糊的图片变得清晰
  3. 想开网店?向你推荐最好的开源电子商务平台
  4. python pandas读写excel_python pandas读写excel
  5. Report framework entry point CRM_BSP_OIC_1O_SEARCH_FROM_RF
  6. 会打乒乓球的机器人!
  7. HDU3415 Max Sum of Max-K-sub-sequence
  8. java中引用数组_javaOO——引用数组
  9. javaScript 计算两个日期的天数相差~~~
  10. 20190226work
  11. vue 判断移动端、pc端
  12. 常微分方程:初值问题与边值问题
  13. 石油化工设备维护检修规程_【干货】罗茨鼓风机维护检修规程
  14. Java之T分布计算数据的双侧置信区间
  15. iOS 几种打包方式
  16. spring使用之旅 ---- bean的装配
  17. [C#][转载]Sqlite操作大全
  18. java web简单线上游戏_手把手教你用Java实现一个简易联网坦克对战小游戏 !
  19. 串口收 程序FPGA
  20. Vmware中桥接无法获取IP

热门文章

  1. 【记录】使用Python+emf转换工具生成矢量图
  2. 用In Design编辑页眉页脚和页码
  3. 线性反馈移位寄存器LFSR(斐波那契LFSR(多到一型)和伽罗瓦LFSR(一到多型)|verilog代码|Testbench|仿真结果)
  4. LCP插件创建对等802.1ad接口
  5. 学习笔记——ECMAScript6
  6. 办公电脑远程 关于电脑远程办公的方法分享
  7. Docker应用容器引擎——docker的常用命令详解
  8. 【win7系统 adb配置与adbd驱动安装记录】
  9. python 科学计数法转换成数字_JS如何将科学计数法转换成数字或者字符串?
  10. halcon:标定助手标定测量