编写测试会让我变慢

“编写测试会拖慢开发速度” 是对 TDD 的常见抱怨。这种批评有一定的道理。就我个人而言,我只觉得 TDD 让我更快,但学术研究并不认同。计算机协会(ACM)对18项主要研究的元分析 表明,TDD 确实提高了学术环境中的生产力,但在工业环境中增加了额外的时间。然而,这并不是全部。

理解放慢速度的好处

上述研究表明,使用 TDD 花费额外时间的回报是减少了软件中发布的缺陷数量。通过 TDD,这些缺陷比其他方法更早地被识别和消除。通过在手动质量保证(QA)、部署和发布之前解决问题,并在可能面对最终用户的错误报告之前,TDD 使我们能够减少大量的浪费工作。

我们可以从下图中看到工作量的差异:

image 2025 01 11 20 15 54 253
Figure 1. Figure 3.1 – Not using TDD slows us down due to rework

顶行表示使用 TDD 开发一个功能,我们有足够的测试来防止任何缺陷进入生产环境。底行表示在没有 TDD 的情况下以 “编写-修复” 风格开发相同的功能,并发现缺陷已经进入生产环境。没有 TDD,我们很晚才发现错误,惹恼了用户,并因返工付出了沉重的时间代价。请注意,“编写-修复” 解决方案看起来让我们更快进入 QA 阶段,直到我们考虑到未发现缺陷引起的返工。返工是这个神话中没有考虑到的部分。

使用 TDD,我们只是将所有的设计和测试思维明确化并提前进行。我们使用可执行的测试来捕获和记录它。无论我们是否编写测试,我们仍然花费相同的时间来考虑代码需要覆盖的具体内容。事实证明,编写测试代码的机械性工作花费的时间非常少。你可以在第 5 章 “编写我们的第一个测试” 中自己测量这一点。编写一段代码的总时间是设计时间加上编写代码的时间,再加上测试时间。即使不编写自动化测试,设计和编码时间仍然是恒定且占主导地位的因素。

另一个被方便地忽略的领域是手动测试所花费的时间。毫无疑问,我们的代码将被测试。唯一的问题是什么时候以及由谁测试。如果我们先编写测试,那就是由我们开发者来测试。它发生在任何有问题的代码被检查到我们的系统之前。如果我们把测试留给手动测试的同事,那么我们会拖慢整个开发过程。我们需要花时间帮助同事理解我们代码的成功标准是什么。然后他们必须制定手动测试计划,通常需要编写、审查并接受为文档。

执行手动测试非常耗时。通常,整个系统必须构建并部署到测试环境中。数据库必须手动设置为包含已知数据。必须通过点击用户界面(UI)进入合适的屏幕,以执行我们的新代码。输出必须手动检查并决定其正确性。每次我们进行更改时,都必须手动执行这些步骤。

更糟糕的是,我们越晚进行测试,就越有可能在现有的错误代码之上构建。我们无法知道我们正在这样做,因为我们还没有测试我们的代码。这通常变得难以解决。在一些项目中,我们与主代码分支的差距如此之大,以至于开发者开始互相发送补丁文件。这意味着我们开始在这些错误代码之上构建,使其更难移除。这些都是不良实践,但它们确实发生在实际项目中。

与先编写 TDD 测试相比,差异再大不过了。使用 TDD,设置是自动化的,步骤被捕获并自动化,结果检查也是自动化的。我们将手动测试的时间从几分钟减少到使用 TDD 单元测试的毫秒级。每次我们需要运行该测试时,都会节省这些时间。

虽然手动测试不如 TDD 高效,但还有一种更糟糕的选择:根本不进行测试。将缺陷发布到生产环境意味着我们让用户来测试代码。在这里,可能会有财务考虑和声誉受损的风险。至少,这是一种非常缓慢的发现错误的方式。从生产日志和数据库中隔离有问题的代码行非常耗时。根据我的经验,这通常也非常令人沮丧。

有趣的是,一个永远找不到时间编写单元测试的项目,却总能找到时间搜索生产日志、回滚发布的代码、发布营销沟通,并停止所有其他工作来进行优先级1(P1)修复。有时,感觉在某些管理方法中,找到几天时间比找到几分钟更容易。

TDD 确实在编写测试时增加了前期的时间成本,但作为回报,我们在生产中需要修复的缺陷更少——与在实时代码中发生缺陷的多次返工周期相比,节省了大量的成本、时间和声誉。

克服测试让我们变慢的异议

构建一个案例来追踪手动质量保证(QA)中未发现缺陷和部署失败所耗费的时间。查找最近一次现场问题修复所花费的大致时间。确定缺失的单元测试本可以预防该问题。然后估算编写该单元测试所需的时间。将这些数据呈现给利益相关者。计算所有这些工程时间成本以及任何收入损失,可能会更加有效。

了解到测试确实在减少缺陷方面具有整体效益,让我们来审视另一个常见的反对意见,即测试没有价值,因为它们不能预防每一个错误。