关于单元测试:测试驱动开发的缺点?

关于单元测试:测试驱动开发的缺点?

Disadvantages of Test Driven Development?

采用测试驱动设计会给我带来什么损失?

只列出底片; 不要列出以负面形式写的福利。


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

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

最终,您必须编写更多的代码,而不是仅仅按照"普通的旧方法"编写代码。您不仅需要编写一个客户类,还需要编写一个接口,一个模拟类,一些IoC配置和一些测试。

请记住,测试代码也应该维护和保养。测试应该与其他所有内容一样可读,并且需要花费时间来编写好的代码。

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

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

所有这些测试使"更改"(与重构相反)系统行为变得更加困难,简单的更改变得非常困难且耗时。

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

所以要小心...如果您没有一支热情的团队,但至少没有一位经验丰富的开发人员,他们知道如何编写良好的测试,并且还了解一些有关良好体系结构的知识,那么在走TDD之路之前,您确实必须三思而行。 。


有几个缺点(我并没有声称没有好处-特别是在编写项目基础时-最终会节省很多时间):

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

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

而且,您可能开始更多地基于TDD而不是实际上是好的设计原则来做出设计决策。尽管您可能有一个非常简单,容易的解决方案,无法测试TDD的要求,但您现在拥有了一个更为复杂的系统,实际上更容易出错。


我认为对我来说最大的问题是"投入"大量的时间。我仍然非常热爱TDD(如果您有兴趣,请参阅我的博客以获取更新我的测试历程)的开始,而我实际上已经花了数小时来开始。

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

TBH,我谨不同意杰森·科恩(Jason Cohen)关于公开私有方法的评论,这不是事实。在我的新工作方式中,我没有比以前更多的公开方法了。但是,它确实涉及架构更改,并允许您"热插拔"代码模块,以使其他所有内容更易于测试。您不应该使代码的内部更易于访问。否则,我们将回到一切公开的状态,其中的封装在哪里?

因此,(IMO)简而言之:

  • 思考所需的时间(即实际上是在测试)。
  • 知道如何编写可测试代码所需的新知识。
  • 了解使代码可测试所需的体系结构更改。
  • 在尝试提高我们出色的编程技巧所需的所有其他技能的同时,提高您的" TDD编码器"技能:)
  • 组织您的代码库以包括测试代码,而不会花费您的生产代码。

PS:如果您想链接到肯定的链接,我已经询问并回答了几个问题,请查看我的个人资料。


在我从事"测试驱动开发"的几年中,我不得不说最大的缺点是:

卖给管理层

TDD is best done in pairs. For one, it's tough to resist the urge to just write the implementation when you KNOW how to write an if/else statement. But a pair will keep you on task because you keep him on task. Sadly, many companies/managers don't think that this is a good use of resources. Why pay for two people to write one feature, when I have two features that need to be done at the same time?

卖给其他开发商

Some people just don't have the patience for writing unit tests. Some are very proud of their work. Or, some just like seeing convoluted methods/functions bleed off the end of the screen. TDD isn't for everyone, but I really wish it were. It would make maintaining stuff so much easier for those poor souls who inherit code.

维护测试代码和生产代码

Ideally, your tests will only break when you make a bad code decision. That is, you thought the system worked one way, and it turns out it didn't. By breaking a test, or a (small) set of tests, this is actually good news. You know exactly how your new code will affect the system. However, if your tests are poorly written, tightly coupled or, worse yet, generated (cough VS Test), then maintaining your tests can become a choir quickly. And, after enough tests start to cause more work that the perceived value they are creating, then the tests will be the first thing to be deleted when schedules become compressed (eg. it gets to crunch time)

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

Ideally, again, if you adhere to the methodology, your code will be 100% tested by default. Typically, thought, I end up with code coverage upwards of 90%. This usually happens when I have some template style architecture, and the base is tested, and I try to cut corners and not test the template customizations. Also, I have found that when I encounter a new barrier I hadn't previously encountered, I have a learning curve in testing it. I will admit to writing some lines of code the old skool way, but I really like to have that 100%. (I guess I was an over achiever in school, er skool).

但是,我要说的是,TDD的好处远远超过了一个简单的想法的负面影响:如果您可以实现一组涵盖您的应用程序的良好测试,但又不那么脆弱,以至于一次更改会破坏所有这些测试,您将能够像在第1天一样在项目的第300天继续添加新功能。并非所有尝试TDD的人都认为这是所有错误代码的魔术子弹,因此他们认为可以工作,期间。

我个人发现使用TDD可以编写更简单的代码,花费更少的时间来讨论某个特定的代码解决方案是否可以工作,并且我不担心更改任何不符合以下条件的代码行:团队。

TDD是一门很难掌握的学科,我从事这门课程已经有几年了,我仍然一直在学习新的测试技术。这是一笔巨大的时间投资,但从长远来看,与没有自动化的单元测试相比,您的可持续性将大大提高。现在,如果只有我的老板能弄清楚这一点。


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

您浪费时间是因为:

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

您失去个人自由是因为:

  • TDD是一种非常有纪律的代码编写方式,往往会与技能等级最高和最低的人产生摩擦。始终以某种方式编写生产代码并使您的工作受到不断的同行审查可能会吓到您最差和最好的开发人员,甚至导致人员减少。
  • 嵌入TDD的大多数敏捷方法都要求您不断与客户讨论您打算完成的工作(在本故事中/每天/无论如何)以及权衡取舍。再一次,这不是每个人都可以喝的茶,无论是在围栏的开发者方面还是在客户方面。

希望这可以帮助


TDD要求您在编写代码通过这些测试之前,计划一下类将如何运行。这是一个加号和一个减号。

我发现在编写任何代码之前很难在"真空"中编写测试。根据我的经验,每当我不可避免地在编写类时却想到一些在编写初始测试时忘记了的东西时,我都会跳过测试。然后是时候不仅重构我的类,还重构我的测试了。重复三到四次,可能会令人沮丧。

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


使用TDD进行原型设计可能会非常困难-当您不确定要走解决方案的路时,预先编写测试可能很困难(除了非常广泛的测试之外)。可能会很痛苦。

老实说,我认为对于绝大多数项目而言,"核心开发"并没有任何实质性的弊端。通常,人们认为自己的代码足够好以至于不需要测试(从来没有),而那些纯朴的人也不会为编写它们而烦恼。


好了,这需要您调试测试。同样,编写测试会花费一定的时间,尽管大多数人都认为这是一项前期投资,可以在节省时间的调试和稳定性方面在应用程序的生命周期内获得回报。

不过,我个人遇到的最大问题是,要学会纪律以实际编写测试。在团队中,尤其是已建立的团队中,很难说服他们花费的时间是值得的。


如果您的测试不是很彻底,您可能会因为测试通过而陷入"一切正常"的错误感觉。从理论上讲,如果您的测试通过了,则说明代码有效。但是,如果我们可以在第一时间完美编写代码,就不需要测试。这里的道义是要确保在调用完整的东西之前自己做一个健全性检查,而不仅仅是依靠测试。

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


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

一旦开发人员离开或忘记了测试返回一个特定值而不返回其他特定值的原因,您就被搞砸了。如果TDD充分记录在案,并且周围环绕着人类可读的文件(即,尖尖的经理),则可以在5年内(当世界变化且您的应用也需要使用它时)进行引用。

当我谈到文档时,这不是代码的模糊,而是应用程序外部存在的官方书面材料,例如用例和背景信息,经理,律师和需要更新的可怜树液可以参考这些信息您的代码在2011年。


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

  • 测试用例的可维护性:

    如果您在大型企业中,很多机会是您不必自己编写测试用例,或者至少当您进入公司时,其中大多数是由其他人编写的。应用程序的功能会不时更改,如果您没有合适的系统(例如HP Quality Center)来跟踪它们,您将很快发疯。

    这也意味着新团队成员将花费大量时间来掌握测试用例的进展。反过来,这可以转化为更多的资金需求。

  • 测试自动化的复杂性:

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

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


最大的问题是不知道如何编写适当的单元测试的人。他们编写了彼此依赖的测试(并且与Ant一起运行时效果很好,但是当我从Eclipse运行它们时,突然突然失败了,只是因为它们以不同的顺序运行)。他们编写的测试不会对任何东西进行特别的测试-他们只是调试代码,检查结果,然后将其更改为test,将其称为" test1"。它们扩大了类和方法的范围,只是因为为它们编写单元测试会更容易。单元测试的代码非常糟糕,存在所有经典的编程问题(繁重的耦合,500行长的方法,硬编码的值,代码重复),并且难以维护。由于某些奇怪的原因,人们将单元测试视为劣于"真实"代码,并且根本不在乎其质量。 :-(


在测试所有代码之前,您将失去说自己"完成"的能力。

您将失去在运行之前编写数百或数千行代码的能力。

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

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

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

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

您将失去每个人都不敢更改的代码所带来的稳定性。

您失去了"黑客"的头衔。


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


最大的缺点是,如果您确实想正确执行TDD,则在成功之前必须失败很多。鉴于有多少家软件公司在工作(按KLOC美元计),您最终将被解雇。即使您的代码更快,更干净,更易于维护且漏洞更少。

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


我第二次回答有关初始开发时间的问题。您还会失去在没有测试安全性的情况下舒适地工作的能力。我也被描述为TDD坚果吧,所以您可能会失去几个朋友;)


人们认为它慢一些。从长远来看,这不是很悲痛,因为它可以使您免于沉迷,但是最终您将编写更多的代码,因此可以说您将时间花在"测试非编码"上。这是一个有缺陷的论点,但您确实提出了!


重新关注困难的,不可预见的需求是程序员的祸根。测试驱动的开发迫使您将精力集中在已知的普通需求上,并将您的开发限制在已经想到的范围内。

考虑一下,您很可能最终会针对特定的测试用例进行设计,因此您将不会有创造力,而是开始思考"如果用户可以执行X,Y和Z,那将很酷"。因此,当用户开始对潜在的散热要求X,Y和Z感到兴奋时,您的设计可能过于严格地专注于已经指定的测试用例,并且将很难进行调整。

当然,这是一把双刃剑。如果您花费所有时间为用户可能想要的每种可能的,可想象的X,Y和Z进行设计,那么您将不可避免地永远不会完成任何事情。如果您完成了某些工作,那么任何人(包括您自己)都将不可能知道您在代码/设计中正在做什么。


一切都很好。我将添加一些方法来避免TDD的阴暗面:

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

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

  • 在您的应用程序允许的范围内,请避开依赖于通知,事件和副作用的正确排序的设计。这些很容易掉落或混乱,因此需要进行大量测试。


为诸如XML提要和数据库之类的"随机"数据编写测试可能很困难并且很耗时(不是那么困难)。我最近花了一些时间来处理天气数据供稿。至少因为我没有太多的TDD经验,这使编写测试相当混乱。


您将失去承担多个职责的大班课程。
您也可能会失去承担多个职责的大型方法。
您可能会失去一些重构的能力,但是您也会失去一些重构的需求。

杰森·科恩(Jason Cohen)说了些类似的话:
TDD需要您的代码的特定组织。这可能在结构上是错误的。例如,由于不能在类外部调用私有方法,因此必须使方法成为非私有方法以使其可测试。

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

戴夫·曼


您必须以不同的方式编写应用程序:一种使其可测试的方法。首先,您会感到惊讶,这很困难。

有些人发现了在写得太难之前先考虑要写什么的概念。嘲笑之类的概念对于某些人来说也可能很难。如果不是为测试而设计的,则旧版应用中的TDD可能会非常困难。围绕不兼容TDD的框架进行TDD也可能会很困难。

TDD是一项技能,因此初级开发人员起初可能会遇到困难(主要是因为尚未教会他们这种工作方式)。

总体而言,尽管随着人们的熟练程度而解决了弊端,但您最终还是抽象出了"臭味"代码,并拥有了更稳定的系统。


  • 单元测试需要编写更多的代码,因此开发的前期成本更高
  • 需要维护更多代码
  • 需要额外的学习


在项目中花一些时间才能开始使用它,但是...当我发现自动测试可以很快发现的愚蠢错误时,我总是后悔没有采用"测试驱动"方法。另外,TDD提高了代码质量。


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

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


教我团队敏捷开发的人不相信计划,您只写了最小的要求。

他的座右铭是重构,重构,重构。我了解到重构意味着"不提前计划"。


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

我之所以提出这一建议,是因为BDD解决了其中的一些负面问题,并起到了一定的作用。您需要在收集反馈时考虑这一点。


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

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


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


您将失去进行增量更改(代码重构)的能力,并且仍然对代码执行预期的工作感到热情和模糊。您几乎没有自由和轻松的动力来以最少的显式依赖关系构造代码。 IOW,您将能够嵌入很多依赖关系而无需注意。如果您使用TDD,则在编写测试时依赖项将显示为痛苦/气味。


推荐阅读