测试优先编程的缺点是什么?


47

如今风靡一时。“每个人”都推荐它。这本身使我感到怀疑。

在进行测试优先(测试驱动)开发时发现了哪些缺点?我正在寻找经验丰富的从业人员的个人经验-我可以在互联网上的其他地方阅读一百个想想的人的假想。

我之所以问并不是因为我不想讨厌TDD,而是因为改善软件开发过程是我的工作,而且我们越了解人们遇到的问题,就越有机会改进该过程。

Answers:


41

有不少,但优势远远大于弊。

学习曲线很陡。

许多开发人员似乎期望从第一天开始就可以通过测试优先编程来提高效率。不幸的是,以相同的速度获得经验和编程需要花费大量时间。你无法解决它。

更具体地说,很容易出错。您可以非常轻松地(出于良好的意图)最终编写一堆很难维护或测试错误内容的测试。这里很难举个例子-这些问题只是需要经验来解决。您需要具有分离关注点和设计可测试性的良好感觉。我最好的建议是与非常了解TDD的人进行配对编程。

您需要进行更多的编码。

测试优先意味着您不能跳过测试(这很好),并且意味着您最终将不得不编写更多代码。这意味着更多的时间。同样,您无法绕开它。您将获得易于维护,扩展和通常更少的错误的代码,但是这需要时间。

对经理人来说可能是一个艰难的选择。

软件经理通常只关心时间表。如果您切换到测试优先编程,而突然花了2个星期而不是一个功能来完成一项功能,那么他们就不会喜欢它。这绝对是一场值得战斗的战斗,许多经理都被开悟了,足以胜任,但这可能是一个艰难的抉择。

对于其他开发人员而言,这可能是一个艰难的选择。

由于学习曲线陡峭,并非所有开发人员都喜欢测试优先编程。实际上,我想大多数开发人员一开始都不喜欢它。您可以执行诸如配对编程之类的事情来帮助他们加快速度,但这可能是一件很难的事。

最后,优点大于缺点,但是如果您只忽略缺点,则无济于事。从一开始就知道您要处理的内容可以帮助您协商一些(即使不是全部)不利条件。


这些是很好的答案,但是可能更具体地讲#1?我特别想听听您如何/是否能够恢复编程速度-您从什么中学到了什么,当您开始进行TDD时了解到什么?
Alex Feinman 2010年

更新以进行澄清
Jaco Pretorius

7
如果您现在要进行测试,则花费在开发上的时间不会有太大变化。看起来事情花的时间更长,因为您要花时间编写和维护单元测试。
克里斯·

1
@JeffO您是否熟悉“我要给自己写一辆小型货车!” 编码学校?
Alex Feinman 2010年

1
@tvanfosson-因为他们试图一次更改两件事-既开始测试又是TDD-可能会出现问题。它还非常准确地增加了时间估计,因此经理和客户只会看到前期的增加,而不是总的时间实际上是已知的(一次),甚至可能更少。如果他们正在做一些测试,那么增加的幅度将很小。
克里斯·

35

测试优先假设您正在编写的代码是:

  • 可通过单元测试的方式进行测试
  • 您正在开发的产品具有明显的方法,不需要大量的原型设计或试验
  • 您将不需要进行过多的重构,或者您有时间重复重写数百或数千个测试用例
  • 没有什么被密封
  • 一切都是模块化的
  • 一切都是可注射的或可嘲弄的
  • 您的组织将足够高的价值放在低缺陷上以证明资源下沉是合理的
  • 在单元测试级别进行测试有好处

如果您的项目不满足这些要求,您将遇到困难。TDD的发起人对此没有很好的回答,建议您重新设计产品,使其更好地属于这些产品系列。在某些情况下这是不可能或不希望的。

在实践中,人们还认为我认为测试优先测试实际上可以证明有关程序正确功能的一切,这对我来说是一个巨大的问题。在许多情况下,这是不正确的,但是即使在正确的情况下,它也远非正确性的完整描述。人们看到数百次通过测试,并认为减少测试是安全的,因为在TDD之前,他们只做了几百个测试用例。根据我的经验,TDD意味着您需要进行更多的集成测试,因为开发人员还将具有虚假的安全性,而将所有测试更改为大型编辑器的痛苦可能会导致开发人员进行有趣的工作。

例子:

我个人最好的例子是在为asp.net编写安全代码时。如果要在计算机配置中的恶劣环境中运行它们,则会对其进行加密,签名和密封,并且由于它们是针对IIS神对象运行的,因此很难非常正确地进行模拟。为性能和内存使用增加一些限制,您很快就会失去在其余区域使用占位符对象的灵活性。

任何类型的微控制器或其他低资源环境代码都可能无法进行真正的OO风格设计,因为抽象无法优化出来,并且您的资源有限。在许多情况下,高性能例程也是如此。


你能举一些反例吗?我什么时候不写可以通过单元测试的方式进行测试的东西?我为什么不编写可模拟或可注入的代码(传统代码除外,这本身就是一个话题)?
Alex Feinman 2010年

编辑添加示例部分
条例草案

4
同意 TDD的工作似乎依赖于您正在使用的机器的一系列假设;对于我约50%的项目来说,这似乎并不成立。
保罗·内森

我完全同意...很好的答案
Khelben 2010年

2
就像这个游戏中的任何东西-适用于许多情况,不适用于其他情况。当心在软件开发的任何领域提倡“一条真实道路”的人。
艾伦B

25

我看到的最大缺点不是TDD本身,而是从业人员。他们采取教条式和狂热的态度,必须对所有内容进行测试。有时(很多时候),这不是必需的。另外,这可能不切实际(例如,将组织引入TDD。)

优秀的工程师可以找到折衷方案,并在何时/何地/如何先进行测试方面进行适当的权衡。另外,如果您发现自己经常花费更多的时间来开发测试而不是实际代码(大约2-3倍或更多),那么您就会遇到麻烦。

换句话说,对TDD(或与此相关的软件开发中的任何事物)要务实和合理。


也许那是迈克尔·费瑟斯(Michael Feathers)对“遗留代码”的“新”定义(即“未经测试的代码”)来自何处?
Phill W. 2014年

这个定义对我不起作用:)对我来说,任何在生产环境中运行且受更改的代码都是遗留代码,与代码或测试质量无关。我们通常将“遗留代码”与“不良代码”或“过时代码”相关联,而实际上在开发中的代码中已经存在不良代码和过时代码,而这些代码尚未见到生产用途。我们的目标应该是使我们的代码从一开始就成为传统,并且具有如此高的质量和实用性,以使其在数十年乃至数十年的时间内都可以使用。
luis.espinal 2014年

6

我从2009年8月上旬开始进行TDD,并说服我的整个公司在2009年9月/ 10月改用它。目前,整个开发团队都已完全转换,将未经测试的代码提交到仓库中被认为是一件坏事,然后抛出。它对我们一直很好,我无法想象切换回牛仔编码。

但是,有两个非常明显的问题。

测试套件必须维护

当您认真对待TDD时,最终会写很多测试。而且,需要花费一些时间和经验来实现什么是正确的测试粒度(过度进行与不充分进行一样糟糕)。这些测试也是代码,并且容易受到bitrot的影响。这意味着您必须将它们维护为其他所有东西:在升级它们依赖的库时进行更新,不时进行重构...当您对代码进行重大更改时,很多测试会突然过时或甚至是错误的。如果幸运的话,您可以简单地删除它们,但是很多时候您最终会提取出有用的位并将其适应新的体系结构。

测试抽象不时泄漏

我们使用的是Django,它具有非常出色的测试框架。但是,有时它所做的假设与实际情况有些矛盾。例如,某些中间件可能会破坏测试。或者,一些测试对缓存后端进行了假设。另外,如果您使用的是“真实”数据库(而不是SQLite3),那么为测试准备数据库将需要很多时间。当然,您可以(并且应该)使用SQLite3和内存数据库进行本地测试,但是某些代码的行为会因所使用的数据库而异。必须设置在实际设置中运行的连续集成服务器。

(有些人会告诉您,您应该模拟数据库之类的所有内容,否则您的测试并非“纯粹”,但这只是意识形态方面的。如果您在模拟代码中出错(相信我,您会的),您的考生将一文不值。)

综上所述,只有当您对TDD相当了解时,我描述的问题才开始出现。当您只是从TDD开始(或从事较小的项目)时,测试重构就不会成为问题。


3
+1。“必须维护”:在测试可重用代码时,这已不再是问题,因为其接口和行为通常需要稳定。因此,我通常只对可重用的库执行TDD。
Dimitri C.

4

对我而言,每当我尝试广泛使用它们时,测试都会遇到一些心理方面的深层次问题,例如在TDD中:如果存在,我会草率地编写代码,因为我相信测试会发现任何问题。但是,如果没有提供安全网的测试,我会仔细编码,其结果总是比测试好。

也许只是我。但是我还在某个地方读到,带有各种安全铃铛和口哨声的汽车容易撞车(因为驾驶员知道那里有安全装置),所以也许这是可以肯定的。TDD可能与某些人不兼容。


这对我来说似乎很奇怪,因为编写可测试的代码通常会使我放慢速度,并更多地考虑我正在编写的代码。这些天,我实际上没有测试就有些紧张。
Matt H

1
它只是表明不同的人的反应确实不同。我不是在抨击TDD-显然有很多人觉得它有用-但事实是它并不适合所有人。
Joonas Pulakka 2010年

2
我同意100%。我无需自动测试就能更好更快地编写代码。当然,不进行测试将是荒谬的,我只是认为自动化是一个坏选择(至少对我而言)。我发现手动测试比维护测试套件要快而且更安全-但我还是一位经验丰富的开发人员,因此我非常善于知道要测试的内容以及在何处以及为什么进行测试,因此我添加了代码并进行了重构无回归。
本·李

1
尽管我应该指出与我一起工作的团队和两个项目都足够小,以至于我对整个体系结构都有很好的了解-在大型团队或大型项目中,我认为自动化测试会更有用,因为那么没有一个开发人员一定能够闻到他们应该在哪里进行测试以避免回归。
本·利

您是否省略了重构步骤?
rjnilsson

2

测试优先确实妨碍了我的一种情况是,我想快速尝试一些想法并在编写适当的实现之前查看它是否可以工作。

我的方法通常是:

  1. 实现一些可以运行的东西(概念证明)。
  2. 如果可行,请通过添加测试,改进设计和重构来合并。

有时我无法执行步骤2。

在这种情况下,使用TDD对我来说弊大于弊:

  • 在概念验证的实施过程中编写测试只会使我放慢速度,并打断我的思路:我想了解一个想法,而且我不想浪费时间在我的第一个粗略实现上测试细节。
  • 可能需要更长的时间才能发现我的想法是否值得。
  • 如果事实证明这个想法没有用,那么我就必须扔掉我的代码和写得很好的单元测试。

因此,当我不得不探索一些新的想法时,我不会使用TDD,而只会在感觉到新代码即将到来时才介绍单元测试。


1
听起来您好像在混淆原型代码和可用代码。原型代码就是测试代码。它不需要进行测试,并且您不应该创建针对它运行的测试。您缺少的步骤介于1.和2.之间:您说“通过编写测试进行合并”。问题是您没有什么要巩固的东西,但是要写些东西。计划重写原型代码,不计划重用它。重用它有很多地方可以妥协。重写使探索阶段和“质量代码”阶段之间的划分正式化。
utnapistim 2014年

3
@utnapistim:我不会将原型代码与可用代码混淆,而是TDD狂热者将它们混淆,并建议您也将TDD用于原型代码。或者更确切地说,他们假设根本没有原型代码。另外,我也同意您的观点,当您从原型过渡到实际实现时,通常必须重写。有时您可以重用部分原型代码,但是您必须准备好进行重写。您确实必须视情况决定。
乔治

3
@utnapistim:另请参见luis.espinal的回答:“我看到的最大缺点不是TDD本身,而是从业人员。他们采取教条式和狂热的态度,必须对所有内容进行测试。”
乔治

1

TDD的缺点或成本

注意:有多种不同类型的TDD。无论是单位,BDD,ATDD还是其他变体,仍然存在许多困难

副作用

无论是模拟,固定测试还是功能测试,对外部状态或系统的依赖通常是导致测试最复杂,测试方式混乱以及出错的最大风险的根源。我看到的一些问题:

  • 模拟:忘记声明呼叫顺序
  • 模拟:模拟与实际通话或响应不匹配
  • 装置:测试依赖于不切实际的数据,掩盖了其他问题
  • 夹具:测试生产中的不可能状态
  • 功能性:由于依赖系统暂时不可用,错误的构建中断
  • 功能:测试速度非常慢

您将不得不更改编码方式,对于某些情况而言,这将是一个巨大的改变。

不同的人以截然不同的方式编码。在TDD中,您需要从声明特定行为的测试开始,然后实施以使测试通过。我见过并且曾经是一名程序员,其编程不利于TDD。我最初开始习惯于改变自己的开发方法大约花了2个月的时间。

需要花费一些时间来了解您关心的测试和不关心的测试。

每个团队都应该对要在测试中划清界限的位置做出明确的决定。他们珍视哪些东西需要测试,哪些则不需要。学习如何编写好的测试以及您实际关心的测试通常是一个痛苦的过程。同时,代码将继续处于不断变化的状态,直到样式和方法都保持一致。

特定于单元测试:大型重构

具有成千上万个单元测试的重要代码库的大型或基础重构将产生巨大的成本,以更新所有测试。即使这样做是正确的,但通常只是因为与执行相关的成本而在不进行重构的情况下进行回推。


0

我的比喻是Scalextric轨道上的障碍。如果戴上它们,您的谨慎程度将大大降低。

人们还对测试进行了一些太空学习-因为他们运行良好,所以他们认为代码已经过全面测试,而这仅仅是测试过程的开始。

在我看来,TDD是BDD的垫脚石。在不知道测试功能的情况下运行的大量测试并不能真正帮助支持开发人员。使用BDD时,测试输出为英文,可以记录测试内容,从而建立对系统的理解。


-1

TDD的好处在于,它迫使您保护您的代码,使其免受不了解它的人的攻击。是的,这通常包括您自己。但是,当代码不值得警惕时会发生什么呢?首先,有很多代码根本不存在!因此,TDD的问题在于编写不良代码的开发人员。TDD可能不会帮助他们编写好的代码,更有可能他们也会编写可怕的测试。因此,在他们的情况下,TDD只会增加混乱。写得不好和/或冗余的测试没有其他形式的错误代码那么有趣。


1
如果您自己不理解自己的代码,那么数十亿个可能的测试用例中的少数几个可能如何防止代码错误?
迈克尔·肖

2
是因为您在编写时理解了它,但是一路上却忘记了它吗?
2014年

+1 TDD不能防止开发人员误解了业务需求。这就是BDD出现的地方...
Robbie Dee 2014年
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.