关于测试驱动开发 (Test Driven Development) 的争议之一是,耗费相当人力编写单元测试代码能否真的带来开发效率的净收益。
TDD 的拥趸认为答案是肯定的:完备的单元测试可以极大缩短从代码修改到发现错误的 turn-around 时间,在理想情况下,每次修改后仅需几分钟的单元测试可以立即发现并定位错误,修复错误并保证代码质量的开销得以降低。他们抱怨在缺乏单元测试的情况下,即使测试团队积极参与,从代码修改提交到发现问题通常也会经过几天时间。如果测试团队仅运行系统功能测试而缺乏模块级别测试,发现问题甚至可能推迟到几周之后。TDD 拥趸认为如此长的 turn-around 时间必然极大地增加定位错误的困难。当然,TDD 的拥趸也承认接近理想的单元测试并不轻松,除去编写和维护一套并行于产品代码并且需要和产品代码同时演进的测试代码的工作量,还必须用 extract interface/implementation 、parameterize constructor 、mock object 等对代码清晰度构成干扰的 dependency break 手段改造产品代码。
几乎所有我熟悉的同事,包括我自己都对单元测试有诸多怀疑。切身感受告诉我们,TDD 的鼓吹者似乎遗漏了某些环节。比如说有这么一项技术,它本身远非完美,需要程序员花费一定精力来掌握和操作,但是消耗的精力低于 TDD,对产品代码清晰度的干扰也低于 TDD,又能保证和 TDD 相当的代码质量。从推行 TDD 耗费的精力来看,存在一种投入产出更有效率的技术的可能性并不是悲观的。
最近我似乎找到了这项技术。TDD 的拥趸很少提及版本管理系统 (Source Control Management System, SCM 系统) 的作用。我不是说那些鼓吹 TDD 的人都不会用 SCM,但是他们似乎没有意识到 SCM 的关键作用。而像我们这样的人又往往认为 SCM 在软件开发中的核心地位透明得像空气一样,以至于在讨论中疏于提及 SCM,想当然地认为他人不至于忽视这个技术。但是 TDD 拥趸的各种论述似乎说明他们对 SCM 的认识远远低于我们的想象。
SCM 鼓励程序员采用一种大胆尝试的态度进行开发。现代的分布式 SCM 系统 (DSCM) 提供低开销的 branching 功能,鼓励为目的单一的短小修改创建 submission,而非为整个 feature 构建巨大的单个版本。Braching 和维护多个短小修改并非没有开销,但是相对来远低于 TDD。实践这些版本管理策略让 code base 成为一组可以单独 revoke 的 submission 序列 (或者说一组序列,因为 branching 还为 revokability 的粒度提供进一步的层次管理) 。Revoke 较早提交的 submission 通常并不会干扰较晚提交的 submission。这正是 TDD 的拥护者在鼓吹他们的技术时所忽视的另一种保证质量的方式,他们把 code base 看成是一个冰激凌蛋筒,先放进去的冰球被后放进去的死死压住。所以在放每一个冰球的时候都要异常小心地舔两口来保证质量。更糟糕的是,全面单元测试容易鼓励一种习惯:既然每次修改由单元测试保证正确,小的修改就不必进入 SCM 系统成为单独 submission,只有完整的 feature 才被提交到 SCM 系统。甚至可以怀疑 TDD 正是 Subversion 统治 SCM 的黑暗中世纪诞生的极端思想。
而我们这类人希望把 TDD 所消耗的精力节省下来用于让 SCM 发挥最大的作用。保证每一次 submission 尽量短小并互不干扰。保证每次 submission 之后的 code base 都处于一个基本 deliverable 的状态,让 experimental branch 和 main branch 的距离恰当的体现代码的成熟度。固然这也需要相当的技巧和努力,它对产品代码清晰度产生的是正面促进而非干扰,无需平行地维护一套产品之外的测试代码。SCM 令 design for testability 让位于 design for clarity。
在 SCM 应用良好的项目中,从代码修改到发现 bug 的 turn-around 时间可能会有所增加,但不会增加定位问题的难度。在中等团队中,turn-around 时间的增加只是在吞吐量 (through-put) 和反应度 (responsiveness) 之间的自然取舍,并无实质资源的浪费。基于 SCM 的产品更像操作系统的 page fault 机制,可以而且仅在真正出现问题的地方投入资源。退一步说,在真正出现问题的地方,SCM 可以赋予开发者在局部采用 TDD 模式的自由。如果在正常情况下一种模式比另一种模式开销低,而且前者可以在异常情况下无缝切换到后者,那么后者根本不值得作为常态的工作方式。
人类预测未来的能力非常糟糕,工具的发展趋势是帮助人们把分配资源的决定尽量推迟到必须做决定不可的时候。那种寄希望于预先精细计划并分配资源来保证正确性的 wishful thinking,就像「瀑布模型」和「premature optimization」,必须被更灵活的决策机制取代。
2020/05/13 4:16 下午 |
不知道怎么说,TDD/SCM 是两个概念,在软件生产中相互配合,一刀切的说法,错在一定要选择一个立场。
2020/05/24 11:32 下午 |
TDD 不是局部概念,它是整个项目的总体概念。而 SCM 让这个「总体概念」过时。是的,它们是两个概念,但是相互排斥,无法配合。
2021/12/07 3:40 下午 |
之前在ThoughtWorks我们既用tdd也用git小步提交。让两者结合的关键在于CICD,tdd下的每次提交必须确保ci能够通过,效果(至少我个人)认为还不错