198

采用测试驱动设计会失去什么?

只列出否定的;不要以否定的形式列出福利。

4

31 回答 31

199

如果您想做“真正的” TDD(阅读:首先使用红色、绿色、重构步骤进行测试),那么当您想要测试集成点时,您还必须开始使用模拟/存根。

当您开始使用模拟时,过一段时间,您会想要开始使用依赖注入 (DI) 和控制反转 (IoC) 容器。为此,您需要对所有事物使用接口(它们本身就有很多陷阱)。

归根结底,您必须编写更多的代码,而不是仅仅使用“普通的旧方式”。您不仅需要编写一个客户类,还需要编写一个接口、一个模拟类、一些 IoC 配置和一些测试。

请记住,测试代码也应该得到维护和照顾。测试应该和其他所有东西一样可读,编写好的代码需要时间。

许多开发人员不太了解如何以“正确的方式”完成所有这些工作。但是因为每个人都告诉他们 TDD 是开发软件的唯一真正方法,所以他们只是尽力而为。

这比人们想象的要难得多。通常使用 TDD 完成的项目最终会产生很多没人真正理解的代码。单元测试经常测试错误的东西,错误的方式。没有人同意一个好的测试应该是什么样子,即使是所谓的大师也不同意。

所有这些测试都使得“改变”(与重构相反)系统的行为变得更加困难,而简单的改变变得过于困难和耗时。

如果您阅读 TDD 文献,总会有一些非常好的示例,但通常在现实生活中的应用程序中,您必须有一个用户界面和一个数据库。这就是 TDD 变得非常困难的地方,而且大多数来源都没有提供好的答案。如果他们这样做,它总是涉及更多的抽象:模拟对象、接口编程、MVC/MVP 模式等,这又需要大量的知识,而且……你必须编写更多的代码。

所以要小心......如果你没有一个热情的团队和至少一个经验丰富的开发人员,他们知道如何编写好的测试并且还知道一些关于好的架构的事情,那么在走 TDD 道路之前你真的必须三思而后行.

于 2008-09-15T16:58:54.730 回答
134

几个缺点(我并不是说没有好处——尤其是在编写项目的基础时——最终会节省很多时间):

  • 大时间投资。对于简单的情况,你会损失大约 20% 的实际实现,但对于复杂的情况,你会损失更多。
  • 额外的复杂性。对于复杂的情况,您的测试用例更难计算,我建议在这种情况下尝试使用将在调试版本/测试运行中并行运行的自动参考代码,而不是最简单情况的单元测试。
  • 设计影响。有时设计在开始时并不清楚,并且随着您的进行而发展 - 这将迫使您重做测试,这将产生很大的时间损失。在这种情况下,我建议您推迟单元测试,直到您对设计有所了解。
  • 持续调整。对于数据结构和黑盒算法,单元测试将是完美的,但对于倾向于更改、调整或微调的算法,这可能会导致人们可能声称不合理的大量时间投资。因此,当您认为它确实适合系统并且不要强迫设计适合 TDD 时,请使用它。
于 2008-09-15T21:29:47.497 回答
69

当您有大量测试时,更改系统可能需要重新编写部分或全部测试,具体取决于哪些测试因更改而无效。这可能会将相对快速的修改变成非常耗时的修改。

此外,您可能会开始更多地基于 TDD 而不是真正好的设计原则来做出设计决策。虽然您可能有一个非常简单、容易的解决方案,无法按照 TDD 要求的方式进行测试,但您现在有一个更复杂的系统,实际上更容易出错。

于 2008-09-15T16:22:10.887 回答
56

我认为对我来说最大的问题是“进入”所花费的大量时间。我仍然处于 TDD 之旅的开始阶段(如果您有兴趣,请参阅我的博客以更新我的测试冒险),而且我确实花了几个小时才开始。

让你的大脑进入“测试模式”需要很长时间,而编写“可测试代码”本身就是一项技能。

TBH,我恭敬地不同意Jason Cohen关于公开私有方法的评论,这不是它的意义所在。在我的新工作方式中,我没有比以前公开更多的方法。但是,它确实涉及架构更改并允许您“热插拔”代码模块以使其他一切更容易测试。你应该让你的代码内部更容易做到这一点。否则我们又回到原点,一切都是公开的,其中的封装在哪里?

因此,(IMO)简而言之:

  • 思考所花费的时间(即实际探索测试)。
  • 了解如何编写可测试代码所需的新知识。
  • 了解使代码可测试所需的架构更改。
  • 提高您的“TDD-Coder”技能,同时尝试提高我们光荣的编程工艺所需的所有其他技能:)
  • 组织您的代码库以包含测试代码,而不会搞砸您的生产代码。

PS:如果您想要正面的链接,我已经提出并回答了几个问题,请查看我的个人资料

于 2008-09-15T16:25:34.793 回答
51

在我一直在实践测试驱动开发的几年中,我不得不说最大的缺点是:

卖给管理层

TDD 最好成对完成。一方面,当您知道如何编写if/else语句时,很难抗拒只编写实现的冲动。但是一对会让你保持任务,因为你让他保持任务。可悲的是,许多公司/经理并不认为这是对资源的良好利用。当我有两个功能需要同时完成时,为什么要花钱让两个人编写一个功能?

卖给其他开发商

有些人只是没有耐心编写单元测试。有些人对他们的工作感到非常自豪。或者,有些人就像看到令人费解的方法/功能从屏幕末端流血一样。TDD 并不适合所有人,但我真的希望如此。对于那些继承代码的可怜人来说,这将使维护东西变得更加容易。

与生产代码一起维护测试代码

理想情况下,只有当您做出错误的代码决定时,您的测试才会中断。也就是说,您认为系统以一种方式工作,但事实并非如此。通过打破一个测试或一组(小)测试,这实际上是个好消息。您确切地知道您的新代码将如何影响系统。但是,如果您的测试写得不好、紧密耦合,或者更糟糕的是,生成了(咳嗽VS 测试),那么维护您的测试可以很快成为一个合唱团。并且,在足够多的测试开始产生比他们正在创造的感知价值更多的工作之后,当计划被压缩时(例如,它到了关键时间),测试将是第一个被删除的东西

编写测试以覆盖所有内容(100% 代码覆盖率)

同样,理想情况下,如果您遵循该方法,您的代码将默认 100% 测试。通常,我想,我最终的代码覆盖率会超过 90%。这通常发生在我有一些模板风格的架构时,并且基础已经过测试,我尝试偷工减料而不测试模板自定义。另外,我发现当我遇到以前没有遇到过的新障碍时,我在测试它时会有一个学习曲线。我承认我会用老式的 skool 方式编写一些代码,但我真的很喜欢 100%。(我想我在学校里是个成绩优异的人,呃 skool)。

然而,我想说的是,TDD 的好处远远超过了一个简单想法的负面影响,即如果您可以实现一组涵盖您的应用程序的良好测试,但不是那么脆弱以至于一个更改会破坏它们,那么您将能够像第一天一样在项目的第 300 天继续添加新功能。这不会发生在所有尝试 TDD 的人身上不工作,期间。

就我个人而言,我发现使用 TDD,我可以编写更简单的代码,我花更少的时间来争论特定的代码解决方案是否可行,并且我不害怕更改任何不符合规定标准的代码行团队。

TDD 是一门很难掌握的学科,我已经学习了几年,而且我仍然一直在学习新的测试技术。这是一个巨大的前期投资,但从长远来看,你的可持续性将比没有自动化单元测试的情况大得多。现在,如果只有我的老板能解决这个问题。

于 2008-09-18T04:55:26.067 回答
24

在您的第一个 TDD 项目中,有两大损失,时间和个人自由

你浪费时间是因为:

  • 创建一个全面的、重构的、可维护的单元和验收测试套件会为项目的第一次迭代增加大量时间。从长远来看,这可能会节省时间,但同样也可能是您不必浪费的时间。
  • 您需要选择并成为一组核心工具的专家。单元测试工具需要由某种模拟框架来补充,并且两者都需要成为您的自动化构建系统的一部分。您还想选择并生成适当的指标。

你失去了个人自由,因为:

  • TDD 是一种非常有纪律的代码编写方式,它往往会与技能等级最高和最低的人发生冲突。总是以某种方式编写生产代码并让你的工作接受持续的同行评审可能会吓坏你最差和最好的开发人员,甚至导致人员流失。
  • 大多数嵌入 TDD 的敏捷方法都要求您不断与客户讨论您打算完成的工作(在这个故事/一天/无论如何)以及权衡是什么。再一次,这不是每个人的一杯茶,无论是在开发人员方面还是在客户方面。

希望这可以帮助

于 2008-09-15T18:42:18.787 回答
14

TDD 要求您在编写代码以通过这些测试之前计划好您的类将如何运行。这既是优点也是缺点。

我发现很难在“真空”中编写测试——在编写任何代码之前。根据我的经验,每当我在编写课程时不可避免地想到一些我在编写初始测试时忘记的东西时,我往往会绊倒我的测试。然后是时候不仅重构我的课程,还重构我的测试。重复此操作三到四次,这可能会令人沮丧。

我更喜欢先编写我的课程草稿,然后编写(并维护)一系列单元测试。在我有了草稿之后,TDD 对我来说效果很好。例如,如果报告了一个错误,我将编写一个测试来利用该错误,然后修复代码以使测试通过。

于 2008-09-15T16:31:00.427 回答
13

使用 TDD 进行原型设计可能非常困难 - 当您不确定要采用哪种方式解决问题时,预先编写测试可能很困难(除了非常广泛的测试)。这可能是一种痛苦。

老实说,我不认为绝大多数项目的“核心开发”有任何真正的缺点。它被说得比它应该说的要多得多,通常是那些相信他们的代码足够好以至于他们不需要测试的人(它从来都不是),而那些只是简单地懒得写它们的人。

于 2008-09-15T16:31:56.137 回答
9

好吧,这个拉伸,你需要调试你的测试。此外,编写测试需要一定的时间成本,尽管大多数人都同意这是一项前期投资,可以在应用程序的整个生命周期内节省调试时间和提高稳定性。

不过,我个人遇到的最大问题是提高纪律以实际编写测试。在一个团队中,尤其是一个成熟的团队中,很难让他们相信所花费的时间是值得的。

于 2008-09-15T16:19:42.077 回答
8

TDD 的缺点是它通常与“敏捷”方法紧密相关,这并不重视系统文档,而是为什么测试“应该”返回一个特定值而不是任何其他值的理解只存在于开发人员的头。

一旦开发人员离开或忘记了测试返回一个特定值而不是其他值的原因,你就完蛋了。如果 TDD 有充分的文档记录并被人类可读的(即尖头发的管理器)文档包围,那么当世界发生变化并且您的应用程序也需要时,可以在 5 年内参考这些文档。

当我谈到文档时,这不是代码中的宣传,这是存在于应用程序之外的官方文字,例如经理、律师和必须更新的可怜的 sap 可以参考的用例和背景信息您在 2011 年的代码。

于 2008-09-15T17:02:34.580 回答
8

我遇到过几种 TDD 让我发疯的情况。列举一些:

  • 测试用例可维护性:

    如果你在一家大企业,很有可能你不必自己编写测试用例,或者至少大部分测试用例都是在你进入公司时由其他人编写的。应用程序的功能会不时发生变化,如果您没有适当的系统(例如 HP Quality Center)来跟踪它们,您很快就会发疯。

    这也意味着新团队成员需要花费大量时间来了解测试用例的情况。反过来,这可以转化为所需的更多资金。

  • 测试自动化复杂度:

    如果您将部分或全部测试用例自动化为机器可运行的测试脚本,则必须确保这些测试脚本与其相应的手动测试用例同步,并与应用程序更改保持一致。

    此外,您将花时间调试可帮助您捕获错误的代码。在我看来,这些 bug 大多来自于测试团队未能在自动化测试脚本中反映应用程序的变化。业务逻辑、GUI 和其他内部内容的更改可能会使您的脚本停止运行或运行不可靠。有时这些变化非常微妙且难以察觉。一旦我的所有脚本都报告失败,因为它们的计算基于表 1 中的信息,而表 1 现在是表 2(因为有人在应用程序代码中交换了表对象的名称)。

于 2008-09-15T17:12:08.677 回答
7

如果您的测试不是很彻底,您可能会因为测试通过而误认为“一切正常”。从理论上讲,如果您的测试通过,则代码可以正常工作;但是如果我们第一次就可以完美地编写代码,我们就不需要测试了。这里的道德是确保在调用完成之前自己进行健全性检查,不要仅仅依赖测试。

在那一点上,如果您的健全性检查发现未测试的内容,请确保返回并为其编写测试。

于 2008-09-15T16:33:42.823 回答
6

最大的问题是那些不知道如何编写适当的单元测试的人。他们编写相互依赖的测试(它们在 Ant 上运行得很好,但是当我从 Eclipse 运行它们时突然失败,只是因为它们以不同的顺序运行)。他们编写的测试并不特别测试任何东西——他们只是调试代码,检查结果,然后将其更改为测试,称之为“test1”。它们扩大了类和方法的范围,只是因为为它们编写单元测试会更容易。单元测试的代码很糟糕,存在所有经典的编程问题(重耦合、500 行长的方法、硬编码值、代码重复),而且维护起来很麻烦。出于某种奇怪的原因,人们将单元测试视为不如“真实”代码的东西,而他们却不 根本不在乎他们的质量。:-(

于 2008-09-15T22:49:01.160 回答
4

你会浪费很多时间来编写测试。当然,这可能会在项目结束时通过更快地捕获错误来保存。

于 2008-09-15T16:18:23.673 回答
4

在测试所有代码之前,你失去了说你“完成”的能力。

你失去了在运行之前编写成百上千行代码的能力。

你失去了通过调试学习的机会。

您失去了发布您不确定的代码的灵活性。

您失去了紧密耦合模块的自由。

您失去了跳过编写低级设计文档的选项。

您失去了每个人都害怕更改的代码所带来的稳定性。

于 2009-05-04T19:14:39.233 回答
2

我支持关于初始开发时间的答案。如果没有测试的安全性,您也会失去舒适地工作的能力。我也被描述为 TDD 疯子,所以你可能会失去几个朋友;)

于 2008-09-15T16:20:31.437 回答
2

它被认为更慢。从长远来看,这不是真的,因为它会拯救你,但你最终会写更多的代码,所以可以说你把时间花在“测试而不是编码”上。这是一个有缺陷的论点,但你确实问了!

于 2008-09-15T16:20:36.537 回答
2

重新关注困难的、不可预见的需求是程序员的常客。测试驱动开发迫使您专注于已知的、平凡的需求,并将您的开发限制在已经想象的范围内。

想一想,您最终可能会针对特定的测试用例进行设计,因此您不会有创意并开始思考“如果用户可以做 X、Y 和 Z 那就太酷了”。因此,当该用户开始对潜在的酷需求 X、Y 和 Z 感到兴奋时,您的设计可能过于拘泥于已经指定的测试用例,并且难以调整。

这当然是一把双刃剑。如果您将所有时间都花在设计用户可能想要的每一个可以想象的、可以想象的 X、Y 和 Z 上,那么您将不可避免地永远无法完成任何事情。如果你完成了某件事,任何人(包括你自己)都不可能知道你在代码/设计中做了什么。

于 2008-09-15T16:45:09.333 回答
2

最大的缺点是,如果你真的想正确地进行 TDD,那么在你成功之前你将不得不失败很多。考虑到有多少软件公司在工作(每 KLOC 美元),你最终会被解雇。即使您的代码更快、更清晰、更易于维护并且错误更少。

如果您在一家按 KLOC(或已实施的要求——即使未测试)向您支付报酬的公司工作,请远离 TDD(或代码审查、结对编程或持续集成等)。

于 2008-09-15T18:57:09.843 回答
1

为“随机”数据(如 XML 提要和数据库)编写测试可能既困难又耗时(没那么难)。我最近花了一些时间处理天气数据馈送。为此编写测试非常令人困惑,至少因为我对 TDD 没有太多经验。

于 2008-09-15T16:23:01.477 回答
1

您将失去具有多种职责的大型课程。您还可能会丢失具有多种职责的大型方法。你可能会失去一些重构的能力,但你也会失去一些重构的需要。

Jason Cohen 说过这样的话:TDD 需要对您的代码进行特定的组织。这可能在架构上是错误的;例如,由于不能在类外部调用私有方法,因此您必须使方法成为非私有方法以使其可测试。

我说这表明缺少抽象——如果私有代码确实需要测试,它可能应该在一个单独的类中。

戴夫·曼

于 2008-09-15T16:29:33.787 回答
1

您必须以不同的方式编写应用程序:一种使它们可测试的方式。一开始你会惊讶于这是多么困难。

有些人觉得在写之前想好要写什么的概念太难了。对于某些人来说,诸如嘲笑之类的概念也很困难。如果旧应用程序不是为测试而设计的,那么它们中的 TDD 可能会非常困难。围绕对 TDD 不友好的框架进行 TDD 也可能是一场斗争。

TDD 是一种技能,因此初级开发人员一开始可能会遇到困难(主要是因为他们没有被教导以这种方式工作)。

总体而言,尽管随着人们变得熟练,缺点会得到解决,并且您最终会抽象出“臭”的代码并拥有更稳定的系统。

于 2008-09-15T16:32:22.047 回答
1

进入它需要一些时间,并且需要一些时间在项目中开始这样做,但是......当我发现自动化测试可以很快发现的愚蠢错误时,我总是后悔没有使用测试驱动方法。此外,TDD 提高了代码质量。

于 2008-09-15T19:23:29.360 回答
1
  • 单元测试需要编写更多代码,因此前期开发成本更高
  • 需要维护更多代码
  • 需要额外的学习
于 2008-09-15T19:29:46.100 回答
1

很好的答案。我会添加一些方法来避免 TDD 的阴暗面:

  • 我编写了应用程序来进行自己的随机自测。编写特定测试的问题在于,即使您编写了很多测试,它们也只涵盖了您想到的情况。随机测试生成器会发现您没有想到的问题。

  • 大量单元测试的整个概念意味着您有可能进入无效状态的组件,例如复杂的数据结构。如果您远离复杂的数据结构,那么需要测试的东西就会少很多。

  • 在您的应用程序允许的范围内,避免依赖于通知、事件和副作用的正确顺序的设计。那些很容易被丢弃或打乱,所以他们需要大量的测试。

于 2008-12-12T14:39:19.683 回答
0

TDD 要求您的代码有特定的组织。这可能效率低下或难以阅读。甚至在架构上是错误的;例如,由于private不能在类外部调用方法,因此您必须使方法成为非私有方法以使其可测试,这是错误的。

当代码更改时,您也必须更改测试。通过重构,这可能是很多额外的工作。

于 2008-09-15T16:18:46.417 回答
0

让我补充一点,如果您将 BDD 原则应用于 TDD 项目,您可以缓解这里列出的一些主要缺点(混乱、误解等)。如果你不熟悉 BDD,你应该阅读 Dan North 的介绍。他提出这个概念是为了回答在工作场所应用 TDD 时出现的一些问题。可以在此处找到 Dan 对 BDD 的介绍。

我之所以提出这个建议,是因为 BDD 解决了其中一些负面因素并充当了一个空白点。在收集反馈时,您需要考虑这一点。

于 2008-09-15T17:44:28.677 回答
0

您必须确保您的测试始终是最新的,您开始无视红灯的那一刻就是测试变得毫无意义的那一刻。

您还必须确保测试是全面的,否则一旦出现大错误,您最终说服让您花时间编写更多代码的闷热管理类型会抱怨。

于 2008-09-15T22:17:42.873 回答
0

教我团队敏捷开发的人不相信计划,你只为最微小的需求写了那么多。

他的座右铭是重构、重构、重构。我开始明白重构意味着“不提前计划”。

于 2008-09-16T02:22:44.750 回答
-1

开发时间增加:每种方法都需要测试,如果您有一个具有依赖关系的大型应用程序,则需要准备和清理数据以进行测试。

于 2010-10-13T09:46:00.827 回答
-6

你失去了进行增量更改(代码重构)的能力,并且仍然感到温暖和模糊,因为代码做了它应该做的事情。您几乎失去了用最少的显式依赖来构建代码的自由和轻松的动力。IOW,您将能够在不注意的情况下嵌入许多依赖项。如果您使用 TDD,则在编写测试时依赖项会显示为痛苦/异味。

于 2008-09-15T16:29:12.210 回答