采用测试驱动设计会让我失去什么?
只列出消极因素;不要用否定的形式列出好处。
您在编写测试上损失了大量时间。当然,在项目结束时,可以通过更快地捕获错误来节省时间。
TDD需要对代码进行特定的组织。这可能效率不高或难以阅读。甚至在架构上是错误的;例如,由于private方法不能在类外部调用,你必须使方法非私有以使它们可测试,这是错误的。
private
当代码更改时,您必须更改测试。对于重构,这可能是一个
对于这种拉伸,您需要调试您的测试。此外,编写测试还需要一定的时间成本,尽管大多数人都认为这是一项前期投资,在应用程序的生命周期中,它在调试节省的时间和稳定性方面都得到了回报。
不过,我个人遇到的最大问题是,如何建立规程来实际编写测试。在一个团队中,特别是在一个已建立的团队中,很难说服他们所花费的时间是值得的。
我赞同关于初始开发时间的答案。您还失去了在没有测试安全性的情况下轻松工作的能力。我也被描述为一个TDD疯子,所以你可能会失去一些朋友;)
它被认为更慢。从长远来看,这并不是真的,因为它会为你节省很多时间,但你最终会写更多的代码,所以可以说你把时间花在了“测试而不是编码”上。这是一个有缺陷的论点,但你确实问了!
当您有大量的测试时,更改系统可能需要重新编写部分或全部测试,这取决于更改使哪些测试无效。这可能会把一个相对快速的修改变成一个非常耗时的修改。
此外,您可能开始更多地基于TDD而不是基于真正好的设计原则来做出设计决策。虽然您可能有一个非常简单、容易的解决方案,但不可能以TDD要求的方式进行测试,但您现在有一个更复杂的系统,实际上更容易出错。
为xml提要和数据库等“随机”数据编写测试可能很困难,也很耗时(没那么难)。最近我花了一些时间处理天气数据。为此编写测试是相当令人困惑的,至少我在TDD方面没有太多经验。
我认为对我来说最大的问题是“进入”所花费的时间的巨大损失。我仍然处于TDD之旅的开始阶段(如果你感兴趣,请参阅我的博客更新我的测试冒险),我实际上已经花了小时开始。
让你的大脑进入“测试模式”需要很长时间,而编写“可测试代码”本身就是一种技能。
TBH,我尊重地不同意杰森·科恩的评论将私有方法公开,这不是它的意义所在。在我的新工作方式中,我并没有采取比以前更多的公开方法。然而,它确实涉及到体系结构的变化,并允许您“热插拔”代码模块,以使其他一切更容易测试。你应该不使你的代码的内部更容易做到这一点。否则我们将回到原点,一切都是公开的,其中的封装在哪里?
所以,(在我看来)简单地说:
PS:如果你想要积极的链接,我已经问过并回答了几个问题,看看我的配置文件。
您失去了增量更改(代码重构)的能力,但仍然觉得代码做了它应该做的事情。你几乎失去了用最小的显式依赖来构造代码的自由和轻松的动力。低,你将能够嵌入大量的依赖而不注意。如果你使用TDD,那么在编写测试时,依赖关系就会显示为痛苦/气味。
我认为这表明了一个缺失的抽象——如果私有代码确实需要测试,那么它可能应该在一个单独的类中。
戴夫·曼
TDD要求您在编写代码以通过这些测试之前计划类将如何运行。这是一个正负。
我发现很难在“真空”中编写测试——在编写任何代码之前。根据我的经验,每当我在编写类时不可避免地想到一些在编写初始测试时忘记的东西时,我就会被测试绊倒。然后是时候重构我的类了,还有我的测试。这样重复三四次,你就会很沮丧。
我更喜欢先写一个类的草稿,然后编写(并维护)一组单元测试。在我有了草稿之后,TDD就很适合我了。例如,如果报告了一个错误,我将编写一个测试来利用该错误,然后修复代码以使测试通过。
使用TDD创建原型是非常困难的——当你不确定你要采取什么方法来解决问题时,预先编写测试是很困难的(除了非常宽泛的测试)。这可能是一种痛苦。
老实说,我并不认为对于绝大多数项目的“核心开发”有任何真正的不利因素;它被贬低了很多,通常被那些认为他们的代码足够好,以至于不需要测试的人(它从来都不需要),以及那些根本懒得写它们的人。
您必须以不同的方式编写应用程序:使它们具有可测试性。一开始你会惊讶这有多困难。
有些人觉得在动笔之前先想好要写什么这个概念太难了。对一些人来说,嘲笑之类的概念也很难理解。如果不是为测试而设计的,那么遗留应用中的TDD将非常困难。围绕对TDD不友好的框架进行TDD也会很困难。
TDD是一种技能,所以初级开发人员一开始可能会遇到困难(主要是因为他们没有被教会如何以这种方式工作)。
总的来说,当人们变得熟练时,缺点就会得到解决,你最终会抽象出“臭”代码,并拥有一个更稳定的系统。
如果您的测试不是很彻底,您可能会因为测试通过而陷入“一切正常”的错误感觉。理论上,如果您的测试通过了,那么代码就正常工作了;但是如果我们第一次就能写出完美的代码,我们就不需要测试了。这里的寓意是,在断言某件事情完成之前,要确保自己做一个健全的检查,不要仅仅依赖于测试。
在这一点上,如果您的健全检查发现了一些没有测试的东西,请确保返回并为其编写测试。
重新关注困难的、不可预见的需求是程序员的永恒祸害。测试驱动的开发迫使您将注意力集中在已知的、普通的需求上,并将您的开发限制在已经想象好的需求上。
考虑一下,您很可能最终设计特定的测试用例,因此您不会变得有创造性,并开始思考“如果用户可以做X、Y和Z就太酷了”。因此,当用户开始对潜在的酷需求X、Y和Z感到兴奋时,您的设计可能过于严格地集中在已经指定的测试用例上,并且很难调整。
当然,这是一把双刃剑。如果你把所有的时间都花在设计用户可能想要的每一个可以想象到的X、Y和Z上,你将不可避免地永远不会完成任何事情。如果你真的完成了一些事情,任何人(包括你自己)都不可能知道你在代码/设计中做了什么。
如果你想做“真正的”TDD(阅读:首先测试红色、绿色和重构步骤),那么当你想测试集成点时,你也必须开始使用模拟/存根。
当您开始使用模拟时,一段时间后,您将希望开始使用依赖注入(DI)和控制反转(IoC)容器。要做到这一点,你需要为所有事情使用接口(这本身就有很多陷阱)。
在一天结束的时候,你必须写更多的代码,而不是仅仅用“简单的老方法”。除了一个客户类,您还需要编写一个接口、一个模拟类、一些IoC配置和一些测试。
并且记住,测试代码也应该得到维护和照顾。测试应该和其他内容一样具有可读性,编写好的代码需要时间。
许多开发者不太明白如何以“正确的方式”完成所有这些工作。但是因为每个人都告诉他们TDD是开发软件的唯一正确方法,所以他们只能尽自己最大的努力。
这比人们想象的要难得多。通常用TDD完成的项目最终会有很多没人真正理解的代码。单元测试经常以错误的方式测试错误的东西。没有人同意一个好的测试应该是什么样的,即使是所谓的专家。
所有这些测试使得“改变”(与重构相反)系统的行为变得更加困难,简单的改变变得非常困难和耗时。
如果你阅读TDD文献,总有一些非常好的例子,但通常在现实生活的应用程序中,你必须有一个用户界面和一个数据库。这就是TDD非常困难的地方,大多数来源都不能提供好的答案。如果他们这样做,它总是涉及更多的抽象:模拟对象,编程到接口,MVC/MVP模式等,这也需要大量的知识,而且……你必须编写更多的代码。
所以要小心……如果你没有一个热情的团队和至少一个有经验的开发人员,他知道如何编写好的测试,也知道一些关于好的架构的事情,那么在走上TDD道路之前,你真的必须三思而后行。
TDD的缺点是它通常与“敏捷”方法紧密相关,后者将没有的重要性放在系统文档上,而不是理解为什么测试“应该”返回一个特定的值,而不是其他任何值,只存在于开发人员的脑海中。
一旦开发人员离开或忘记测试返回一个特定值而不是其他值的原因,您就完蛋了。如果TDD有充分的文档记录,并且被人类可读的(例如。5年后,当世界发生变化,你的应用也需要改变时,你可以参考这些文档。
当我说到文档时,这不是代码中的宣传,这是存在于应用程序外部的正式写作,比如经理、律师和不得不在2011年更新代码的可怜的sap可以参考的用例和背景信息。
我遇到过一些TDD让我抓狂的情况。举几个例子:
如果您在一家大企业中,很多情况下您不必自己编写测试用例,或者至少在您进入公司时,大部分测试用例是由其他人编写的。应用程序的特性会不时地发生变化,如果你没有一个适当的系统(比如惠普质量中心)来跟踪它们,你很快就会变得疯狂。
这也意味着新的团队成员将花费相当多的时间来掌握测试用例的情况。反过来,这可以转化为需要更多的资金。
如果您将部分或全部测试用例自动化到机器可运行的测试脚本中,您将必须确保这些测试脚本与相应的手动测试用例同步,并与应用程序更改保持一致。
此外,您还将花时间调试帮助您捕获错误的代码。在我看来,这些错误大多来自测试团队未能在自动化测试脚本中反映应用程序更改。业务逻辑、GUI和其他内部内容的更改可能会使脚本停止运行或运行不可靠。有时这些变化非常微妙,很难察觉。一旦我的所有脚本都报告失败,因为它们的计算基于表1中的信息,而表1现在是表2(因为有人交换了应用程序代码中的表对象的名称)。
让我补充一下,如果您将BDD原则应用到TDD项目中,您可以减轻这里列出的一些主要缺陷(混淆、误解等)。如果您不熟悉BDD,您应该阅读Dan North的介绍。他提出这个概念是为了回答在工作场所应用TDD所产生的一些问题。Dan对BDD的介绍可以在在这里中找到。
我之所以提出这个建议,是因为BDD解决了其中一些负面问题,并起到了弥补差距的作用。在收集反馈时,你需要考虑这一点。
在你的第一个TDD项目中,有两大损失,时间和个人自由
你浪费时间是因为:
你失去个人自由是因为:
希望这能有所帮助
最大的缺点是,如果你真的想要正确地进行TDD,你将不得不在成功之前经历很多次失败。考虑到有多少软件公司在工作(每KLOC一美元),你最终会被解雇。即使你的代码更快、更干净、更容易维护、bug更少。
如果你在一家通过kloc(或实现的需求——即使没有测试)支付你薪水的公司工作,那么就远离TDD(或代码审查,或结对编程,或持续集成,等等等等)。
它需要一些时间来进入,也需要一些时间来开始一个项目,但是……当我发现自动化测试可以很快发现的愚蠢错误时,我总是后悔没有采用测试驱动方法。此外,TDD还提高了代码质量。
一些缺点(我并不是说没有好处——尤其是在编写项目的基础时——它会在最后节省很多时间):
你必须确保你的测试总是最新的,当你开始忽视红灯的时候,测试就变得毫无意义了。
您还必须确保测试是全面的,否则一旦出现大错误,您最终说服的沉闷的管理类型会抱怨,让您花时间写更多的代码。
最大的问题是那些不知道如何编写适当的单元测试的人。它们编写相互依赖的测试(它们在Ant中运行时工作得很好,但是当我从Eclipse中运行它们时突然失败了,只是因为它们以不同的顺序运行)。他们编写的测试不测试任何特定的东西——他们只是调试代码,检查结果,并将其更改为测试,称为“test1”。它们扩大了类和方法的范围,只是因为为它们编写单元测试会更容易。单元测试的代码非常糟糕,包含所有经典的编程问题(重耦合、500行长的方法、硬编码值、代码重复),维护起来非常困难。出于某种奇怪的原因,人们认为单元测试不如“真正的”代码,他们根本不关心单元测试的质量。: - (
教我的团队敏捷开发的人不相信计划,你只为最小的需求写那么多。
他的座右铭是重构,重构,再重构。我开始理解重构意味着“不提前计划”。
在我从事测试驱动开发的这几年里,我不得不说最大的缺点是:
TDD最好两人一组。首先,当你知道如何编写如果/其他语句时,很难抗拒只写实现的冲动。但一对情侣会让你专心工作,因为你让他专心工作。可悲的是,许多公司/经理并不认为这是对资源的有效利用。为什么要花钱请两个人写一篇文章,而我却需要同时完成两篇文章呢?
有些人就是没有耐心编写单元测试。有些人对自己的工作非常自豪。或者,有些人只是喜欢看到令人费解的方法/函数从屏幕的尽头流出。TDD并不适合所有人,但我真的希望它适合。对于那些继承代码的可怜人来说,这将使维护工作变得容易得多。
理想情况下,只有当您做出错误的代码决策时,您的测试才会中断。也就是说,你认为系统是单向工作的,但事实并非如此。通过分解一个测试或一个(小)测试集,这实际上是一个好消息。你知道完全你的新代码将如何影响系统。然而,如果你的测试写得很糟糕,紧密耦合,或者更糟糕的是,生成(咳嗽 VS Test),那么维护你的测试可以很快成为一个合唱团。并且,在足够多的测试开始导致比它们所创造的感知价值更多的工作之后,当计划被压缩时,测试将是第一个被删除的东西。到了关键时刻)
理想情况下,如果您坚持这种方法,您的代码将在默认情况下100%测试。通常,我的代码覆盖率会达到90%以上。这种情况通常发生在我有一些模板风格的体系结构时,并且对基础进行了测试,我试图抄近路,不测试模板自定义。此外,我发现当我遇到以前没有遇到过的新障碍时,我在测试它时有一个学习曲线。我承认我用老式的方式写了一些代码,但我真的很喜欢那种100%的方式。(我想我在学校是一个优等生,呃,学校)。
然而,我想说的是,TDD的好处远远大于坏处,因为如果你可以实现一组覆盖应用程序的良好测试,但不会脆弱到一个更改就会破坏所有测试,那么你将能够在项目的第300天继续添加新功能,就像你在第1天所做的那样。这种情况不会发生在所有尝试TDD的人身上,他们认为TDD是解决他们所有漏洞百出的代码的灵丹妙药,所以他们认为TDD行不通。
就我个人而言,我发现使用TDD,我编写了更简单的代码,我花了更少的时间来争论某个特定的代码解决方案是否可行,并且我不害怕更改任何不符合团队设定的标准的代码行。
TDD是一门很难掌握的学科,我已经做了几年了,而且我一直在学习新的测试技术。这是一项巨大的时间投资,但是,从长期来看,您的可持续性将比没有自动化单元测试大得多。现在,要是我的老板们能搞清楚就好了。
回答都很好。我想补充一些避免TDD阴暗面的方法:
我写了一些应用程序来做随机的自我测试。编写特定测试的问题是,即使您编写了很多测试,它们也只覆盖您想到的情况。随机测试生成器会发现您没有想到的问题。
大量单元测试的整个概念意味着您有可能进入无效状态的组件,例如复杂的数据结构。如果您远离复杂的数据结构,那么需要测试的内容就会少得多。
在应用程序允许的范围内,避免设计依赖于通知、事件和副作用的正确顺序。这些很容易被丢弃或打乱,所以它们需要大量的测试。
在测试所有代码之前,您将失去说“完成”的能力。
您将失去在运行之前编写数百或数千行代码的能力。
你失去了通过调试来学习的机会。
您失去了发布您不确定的代码的灵活性。
您失去了紧密耦合模块的自由。
你失去了跳过编写低级设计文档的选项。
你失去了每个人都害怕更改的代码所带来的稳定性。
开发时间增加:每个方法都需要测试,如果您有一个具有依赖关系的大型应用程序,则需要为测试准备和清理数据。