Answers:
有不少,但优势远远大于弊。
学习曲线很陡。
许多开发人员似乎期望从第一天开始就可以通过测试优先编程来提高效率。不幸的是,以相同的速度获得经验和编程需要花费大量时间。你无法解决它。
更具体地说,很容易出错。您可以非常轻松地(出于良好的意图)最终编写一堆很难维护或测试错误内容的测试。这里很难举个例子-这些问题只是需要经验来解决。您需要具有分离关注点和设计可测试性的良好感觉。我最好的建议是与非常了解TDD的人进行配对编程。
您需要进行更多的编码。
测试优先意味着您不能跳过测试(这很好),并且意味着您最终将不得不编写更多代码。这意味着更多的时间。同样,您无法绕开它。您将获得易于维护,扩展和通常更少的错误的代码,但是这需要时间。
对经理人来说可能是一个艰难的选择。
软件经理通常只关心时间表。如果您切换到测试优先编程,而突然花了2个星期而不是一个功能来完成一项功能,那么他们就不会喜欢它。这绝对是一场值得战斗的战斗,许多经理都被开悟了,足以胜任,但这可能是一个艰难的抉择。
对于其他开发人员而言,这可能是一个艰难的选择。
由于学习曲线陡峭,并非所有开发人员都喜欢测试优先编程。实际上,我想大多数开发人员一开始都不喜欢它。您可以执行诸如配对编程之类的事情来帮助他们加快速度,但这可能是一件很难的事。
最后,优点大于缺点,但是如果您只忽略缺点,则无济于事。从一开始就知道您要处理的内容可以帮助您协商一些(即使不是全部)不利条件。
测试优先假设您正在编写的代码是:
如果您的项目不满足这些要求,您将遇到困难。TDD的发起人对此没有很好的回答,建议您重新设计产品,使其更好地属于这些产品系列。在某些情况下这是不可能或不希望的。
在实践中,人们还认为我认为测试优先测试实际上可以证明有关程序正确功能的一切,这对我来说是一个巨大的问题。在许多情况下,这是不正确的,但是即使在正确的情况下,它也远非正确性的完整描述。人们看到数百次通过测试,并认为减少测试是安全的,因为在TDD之前,他们只做了几百个测试用例。根据我的经验,TDD意味着您需要进行更多的集成测试,因为开发人员还将具有虚假的安全性,而将所有测试更改为大型编辑器的痛苦可能会导致开发人员进行有趣的工作。
例子:
我个人最好的例子是在为asp.net编写安全代码时。如果要在计算机配置中的恶劣环境中运行它们,则会对其进行加密,签名和密封,并且由于它们是针对IIS神对象运行的,因此很难非常正确地进行模拟。为性能和内存使用增加一些限制,您很快就会失去在其余区域使用占位符对象的灵活性。
任何类型的微控制器或其他低资源环境代码都可能无法进行真正的OO风格设计,因为抽象无法优化出来,并且您的资源有限。在许多情况下,高性能例程也是如此。
我看到的最大缺点不是TDD本身,而是从业人员。他们采取教条式和狂热的态度,必须对所有内容进行测试。有时(很多时候),这不是必需的。另外,这可能不切实际(例如,将组织引入TDD。)
优秀的工程师可以找到折衷方案,并在何时/何地/如何先进行测试方面进行适当的权衡。另外,如果您发现自己经常花费更多的时间来开发测试而不是实际代码(大约2-3倍或更多),那么您就会遇到麻烦。
换句话说,对TDD(或与此相关的软件开发中的任何事物)要务实和合理。
我从2009年8月上旬开始进行TDD,并说服我的整个公司在2009年9月/ 10月改用它。目前,整个开发团队都已完全转换,将未经测试的代码提交到仓库中被认为是一件坏事,然后抛出。它对我们一直很好,我无法想象切换回牛仔编码。
但是,有两个非常明显的问题。
测试套件必须维护
当您认真对待TDD时,最终会写很多测试。而且,需要花费一些时间和经验来实现什么是正确的测试粒度(过度进行与不充分进行一样糟糕)。这些测试也是代码,并且容易受到bitrot的影响。这意味着您必须将它们维护为其他所有东西:在升级它们依赖的库时进行更新,不时进行重构...当您对代码进行重大更改时,很多测试会突然过时或甚至是错误的。如果幸运的话,您可以简单地删除它们,但是很多时候您最终会提取出有用的位并将其适应新的体系结构。
测试抽象不时泄漏
我们使用的是Django,它具有非常出色的测试框架。但是,有时它所做的假设与实际情况有些矛盾。例如,某些中间件可能会破坏测试。或者,一些测试对缓存后端进行了假设。另外,如果您使用的是“真实”数据库(而不是SQLite3),那么为测试准备数据库将需要很多时间。当然,您可以(并且应该)使用SQLite3和内存数据库进行本地测试,但是某些代码的行为会因所使用的数据库而异。必须设置在实际设置中运行的连续集成服务器。
(有些人会告诉您,您应该模拟数据库之类的所有内容,否则您的测试并非“纯粹”,但这只是意识形态方面的。如果您在模拟代码中出错(相信我,您会的),您的考生将一文不值。)
综上所述,只有当您对TDD相当了解时,我描述的问题才开始出现。当您只是从TDD开始(或从事较小的项目)时,测试重构就不会成为问题。
对我而言,每当我尝试广泛使用它们时,测试都会遇到一些心理方面的深层次问题,例如在TDD中:如果存在,我会草率地编写代码,因为我相信测试会发现任何问题。但是,如果没有提供安全网的测试,我会仔细编码,其结果总是比测试好。
也许只是我。但是我还在某个地方读到,带有各种安全铃铛和口哨声的汽车容易撞车(因为驾驶员知道那里有安全装置),所以也许这是可以肯定的。TDD可能与某些人不兼容。
测试优先确实妨碍了我的一种情况是,我想快速尝试一些想法并在编写适当的实现之前查看它是否可以工作。
我的方法通常是:
有时我无法执行步骤2。
在这种情况下,使用TDD对我来说弊大于弊:
因此,当我不得不探索一些新的想法时,我不会使用TDD,而只会在感觉到新代码即将到来时才介绍单元测试。
注意:有多种不同类型的TDD。无论是单位,BDD,ATDD还是其他变体,仍然存在许多困难
副作用
无论是模拟,固定测试还是功能测试,对外部状态或系统的依赖通常是导致测试最复杂,测试方式混乱以及出错的最大风险的根源。我看到的一些问题:
您将不得不更改编码方式,对于某些情况而言,这将是一个巨大的改变。
不同的人以截然不同的方式编码。在TDD中,您需要从声明特定行为的测试开始,然后实施以使测试通过。我见过并且曾经是一名程序员,其编程不利于TDD。我最初开始习惯于改变自己的开发方法大约花了2个月的时间。
需要花费一些时间来了解您关心的测试和不关心的测试。
每个团队都应该对要在测试中划清界限的位置做出明确的决定。他们珍视哪些东西需要测试,哪些则不需要。学习如何编写好的测试以及您实际关心的测试通常是一个痛苦的过程。同时,代码将继续处于不断变化的状态,直到样式和方法都保持一致。
特定于单元测试:大型重构
具有成千上万个单元测试的重要代码库的大型或基础重构将产生巨大的成本,以更新所有测试。即使这样做是正确的,但通常只是因为与执行相关的成本而在不进行重构的情况下进行回推。
TDD的好处在于,它迫使您保护您的代码,使其免受不了解它的人的攻击。是的,这通常包括您自己。但是,当代码不值得警惕时会发生什么呢?首先,有很多代码根本不存在!因此,TDD的问题在于编写不良代码的开发人员。TDD可能不会帮助他们编写好的代码,更有可能他们也会编写可怕的测试。因此,在他们的情况下,TDD只会增加混乱。写得不好和/或冗余的测试没有其他形式的错误代码那么有趣。