TDD 已死,测试万岁!
发表于2015-10-22
1.补上 DHH 原始 keynote 的影片︰RailsConf2014 Opening Keynote by DHH
2.Uncle Bob 针对 DHH 的战文,也写了一篇文章来反击︰Monogamous TDD
3.XDite 在 4/28 也再撰文整理他的看法︰返璞归真 -- 以最适当的方式设计软件
前言
昨天在 XDite 大大的 Blog 中 (via: RailsConf 2014 - 十周年纪念版 ( 中 )),看到 RoR framework、Basecamp 公司的创办人、Rework 一书 (中译书︰工作大解放) 的作者,David Heinemeier Hansson (DHH),在 RailsConf 2014 中的 Keynote 后,发表了一篇针对 TDD 的战文︰TDD is dead. Long live testing.
其实我觉得 XDite 整理 DHH 的 talk 重点就非常完整了,虽然我自己也是 TDD 的爱好者,但像这种由业界大神所写出来的战文,我觉得都有很高的阅读价值。就算对方立场和你是相反的,从对方的论点中也可以找出满多的检查点,来反思自己在实践上,是不是也犯了他们所讨厌的错误,或是从错误的出发点来想事情。
所以我翻译了这篇文章,除了让自己再精读一下 DHH 的想法外,也让有兴趣的朋友们可以多了解不同的意见,做为反思。
有任何翻译上的谬误,都欢迎提出,谢谢!
翻译正文
TDD 已死,测试万岁 (TDD is dead. Long live testing.)
先写测试的教义就象是禁欲式的性教育︰一个只会带来自我厌恶和羞愧地,不现实和无效率的道德宣传。
一开始测试先行的想法并不像现在这样。当我一开始接触 TDD 时,它就象是个想带领我们进入更好的软件开发世界的友善邀请,一种对心灵的激荡 (hack),来让你进入测试的实务。它扩展了我的眼界,让我了解经过良好测试的程序库的美好,并且对于软件的修改更有自信。
测试先行就象是一套很棒的辅助轮 (译注︰training wheels,就是小朋友学单车时在后轮加装的轮子),教我思考更深层次的测试议题,但当中也有一些想法被我很快地抛在后面。
这些年来,关于测试先行的言论变得更加大声、更加激进,然而,也更加失去风度。有时候我会被吸进了基本教义派的旋涡 - 对于没有依循宗教的福音行事感觉到很糟糕。然后,我会在接下来的几周,尝试使用测试先行来进行开发,直到它开始伤害我的设计后,又再度舍弃它。
这个过程很像溜溜球在自豪的两侧摆荡︰当我可以坚持基本教义的一字一句时,我感到自豪;而当我做不到这点时,我会绝望地崩溃。这就象是原本已经戒酒的人又开始喝酒,你当然要为(喝酒)这事保持沉默,至少不能公开地向大家承认这事。在公开场合,我充其量只是承认自己不是每次都使用测试先行的方式进行开发,但仍然支持测试先行这种实践 (practice) 是「正确的做法」。我现在要收回这段话。
也许使用测试优先这种违反直觉的开发方式,对于打破业界缺乏自动回归测试的生态是有帮助的。但也许测试先行只是一种比喻,而不应该成为软件开发工作的每日工作教条。但不管它是怎么开始的,它会因为损坏而只是昙花一现。支持者常使用测试先行当做一个锤子,来打击怀疑论者,宣告他们不够项目,不够格来撰写软件,就象是试纸检验一样。
够了!这种事不应该再继续下去!我叫 David,我不以测试先行的方式开发软件。我拒绝为此表达任何歉意,也不想再为此隐瞒。我很感激 TDD 为我打开了关于自动化回归测试的眼界,但我早已摆脱设计教条而继续前行。
我建议大家在不使用 TDD,不表示这是不合格的开发方式的可能性的前提下,认真检视这方法(TDD) 会对您的系统设计完整性带来了什么影响。这就象是服用红色药丸一样,您可能不会喜欢之后您所看到的 (译注︰这边的出处是电影「黑客任务」,当初莫斐斯就是拿了红色药丸和蓝色药丸给尼欧选择要不要跳船)。
所以接下来我们该何去何从呢? (So where do we go from here?)
第一步是承认问题。我认为现在我们已经做到了这一步。第二步是重新平衡从单元测试到系统测试间的频谱 (spectrum)。目前狂热的 TDD 的经验导致了在测试上都聚焦于单元测试,因为这是可以驱动代码设计的测试(这也是最初社群鼓吹测试先行的最初理由)。
我不认为这是健康的。先写单元测试导致我们产生了一堆复杂的中介物件作和间接层,以避免让单元测试太「慢」。像数据库存取、档案 I/O、或是通过浏览器来测试系统,都会被隔离出来(译注︰一般建议每个单元测试的执行时间不能超过 500ms,另外不建议单元测试和外部资源 - 象是 DB、档案系统 - 相依。为了达成这目标,我们常会将待测物件 A 的相依物件 B、C、D 以界面隔离,在单元测试时,使用像 Mock/Stub 的方式来注入假造的物件 B、C、D 给物件 A。一方面让物件 A 不会存取外部资源,另一方面也让整个单元测试够快)。这种方式带来了一个真正可怕的怪物架构︰一堆复杂的服务物件、命令模式或其他更糟的东西。
我很少撰写在传统意义上的单元测试︰假造所有的相依物件,来让上千个测试可以在几秒内测试完毕。因为它对于测试 Rails 应用程序并不是一个有用的方法,我会直接测试 active record model,让它们透过 fixture 来存取数据库。然后以这为基础,在上层运行一组对于 controller 的单元测试,但对我来说,我更愿意使用像 Capybara 或类似的系统测试工具,来取代这些单元测试。
我认为这是我们应该前进的方向︰减少对于单元测试的重视。因为我们不再做测试先行的设计实践,而是更加重视 - 喔,是的 - 执行速度更慢的系统测试。(顺便补充一下,由于平行化和云端基础建设的成熟,系统测试其实也不会比单元测试慢得这么多)
Rails 可以帮助大家做这种转移。今天我们不鼓励完整的系统测试,在开发的堆栈中也没有预设的答案,这是一个我们将要修复的错误,但您不必等到这件事发生,让 Capybar 从今天就介入您的开发周期,如此一来您将会有我们正朝着美好明天的正面思维。
但首先,让我们先深呼吸一下。我们正在驱赶一些神圣的奶牛进屠宰场,这是痛苦和血腥的过程。TDD 是如此成功,以至于它结合了许多程序设计师。TDD 所代表的是不只是它在开发方法上的技法,还有背后以此技法开发的从业人员。(TDD is not just what they do, it's who they are.) 我们必须消除在我们面前巨大社群的思考惯性,而这将需要一些时间。
我们能做的最糟糕的事情是再跳到另一个关于测试的宗教,我可以想象那个新宗教的标语是「只做系统测试! 」。请不要再度跳到那个坑去。
是的,测试优先对我来说已经死了。但比起在它的坟墓上跳舞,我更想做的是向它的贡献表达我的敬意。它在我们的(开发方法论)历史上占有一个重要的阶段,但现在是时候让我们继续前行了。
测试万岁!(Long live testing.)