我为什么不做 TDD

2025-06-04

我为什么不做 TDD

我最近在伦敦 Java 社区做了一个关于调试的演讲。在演讲的问答环节,有人问我测试驱动开发的方法。过去,我对这种做法持更积极的看法。写很多测试,这怎么会是坏事呢?

但随着时间的推移,我对它有了不同的看法。我认为它是一个非常有限的工具,用例非常具体。它不适合我构建的项目类型,而且经常阻碍它本应促进的流畅流程。不过,让我们回顾一下。我非常喜欢这篇区分TDD类型和问题的文章。但让我们稍微简化一下,澄清一下,每个PR都应该有良好的覆盖范围。这不是TDD。这只是好的编程。

TDD 远不止于此。我们需要先定义约束,然后解决问题。这种方法是否比先解决问题,然后再验证约束是否正确更优?这才是 TDD 的核心前提,而不是仅仅编写良好的测试覆盖率。

优点

TDD 是一种很有意思的方法。它在使用弱类型语言时尤其有用。在这种情况下,TDD 非常出色,因为它可以充当严格的编译器和代码检查器的角色。

在其他情况下,这样做也是合理的。例如,当我们构建一个输入和输出定义明确的系统时。我在编写课程和资料时遇到过很多这样的情况。在处理实际数据时,如果我们有中间件来处理数据并以预定义的格式输出,这种情况有时会发生。

这个想法是构建一个包含中间隐藏变量的方程。然后编码就变成了填写方程。在这种情况下非常方便。编码就变成了填空。

“测试驱动开发就是复式簿记。同样的原则。同样的推理。同样的结果。”——鲍勃·马丁叔叔

我认为测试有点像复式簿记。没错,我们应该进行测试。问题是,我们应该基于测试来构建代码,还是反过来?答案就没那么简单了。

如果我们有一个预先存在的系统,并且已经进行了测试,那么 TDD 就完全有意义了。但是,测试一个尚未构建的系统,在某些情况下是有意义的,但并不像人们想象的那么常见。

TDD 最大的卖点在于“设计”。测试实际上就是系统设计,然后我们再去实现这个设计。问题在于我们也无法调试设计。我以前为一家日本大公司做过一个项目。这家公司拥有一套数量最多、最详细的附件设计手册。基于这些设计规范,公司构建了数千个测试。我们的系统需要通过大量的测试。需要注意的是,其中大多数测试甚至都不是自动化的。

测试中存在 bug。有很多互相竞争的实现,但都没有在测试中发现 bug。为什么?因为它们都使用了相同的参考实现源代码。我们是第一个跳过这一步,采用净室实现的团队。这导致这些 bug 一直存在于代码中,其中一些是严重的性能 bug,影响了所有之前的版本。

但真正的问题是进展缓慢。公司无法快速推进。TDD 的支持者会很快评论说,TDD 项目更容易重构,因为测试可以保证我们不会出现回归问题。但这仅适用于事后进行测试的项目。

更糟糕的是

TDD 非常注重快速单元测试。在 TDD 系统上运行缓慢的集成测试或可能一夜之间完成的长期测试是不切实际的。如何验证其规模以及与大型系统的集成度?

在理想世界中,一切都会像乐高积木一样自然地搭好。但我并不生活在这样的世界里,集成测试失败得非常惨。这些失败是最糟糕的,而且错误也最难追踪。我宁愿单元测试失败,所以我才进行单元测试。单元测试很容易修复。但即使覆盖率完美,它们也无法正确测试互连。我们需要集成测试,而它们能发现最严重的错误。

因此,TDD 过分强调“锦上添花”的单元测试,而忽略了必不可少的集成测试。没错,两者都应该有。但我必须有集成测试。集成测试与 TDD 流程不太契合。

正确驱动测试

我根据具体情况选择合适的测试方式。如果遇到需要提前测试的情况,我会采用这种方式。但大多数情况下,先写代码对我来说更自然。编写测试时,检查覆盖率数字非常有帮助,我通常会在测试完成后再做这件事。

正如我之前提到的,我只检查集成测试的覆盖率。我喜欢单元测试,并监控单元测试的覆盖率,因为我也希望单元测试有良好的覆盖率。但就质量而言,只有集成测试才重要。PR 需要单元测试,我不在乎我们是否在实现之前就编写了单元测试。我们应该评判结果。

糟糕的自动化

特斯拉在建设Model 3工厂时,陷入了生产地狱。问题的根源在于他们试图将一切都自动化。帕累托原则完美地适用于自动化。有些环节对自动化的阻力很大,反而会让整个流程更加糟糕。

这种方法在 UI 测试中确实存在缺陷。Selenium 等解决方案在 Web 前端测试方面取得了巨大进步。然而,其复杂性依然巨大,测试也非常脆弱。最终,测试难以维护。更糟糕的是,由于我们不想重写测试,UI 更难重构。

我们或许可以完成 80% 的测试功能,但自动化测试的收益会逐渐递减。在这种环境下,TDD 会遇到问题。功能虽然简单,但测试构建却变得难以进行。

最后

我并不反对 TDD,但我不推荐它,实际上我也不使用它。如果一开始就进行测试是合理的,我可能会这么做,但这并不是真正的 TDD。我根据结果来评判代码。TDD 可以提供很好的结果,但它往往过分强调单元测试。从长远来看,集成测试对质量更为重要。

自动化很棒,但除非它停止。总有一天,自动化测试会变得毫无意义。接受这一点,并将精力集中在更高效的方向上,可以节省我们大量的时间和精力。

作为一名 Java 开发者,我偏爱类型安全、严格的语言,因此我对此持有偏见。JavaScript 和 Python 等语言由于其灵活性,可以从大量的测试中受益。因此,TDD 在这些环境中更有意义。

总而言之,测试是好的。然而,TDD并不能使测试变得更好。如果它对你有用,那它确实是一种有趣的方法。在某些情况下,它的作用巨大。但是,认为TDD必不可少,甚至认为它会显著改善最终代码,这种想法是没有意义的。

文章来源:https://dev.to/codenameone/why-i-dont-do-tdd-1j71
PREV
宣布 CodeLand 2022 的第一位主题演讲者:Angie Jones!
NEXT
安静辞职关乎忠诚