如今,测试驱动开发(TDD)规模很大。我经常在Programmers SE和其他场所中将其推荐为解决各种问题的解决方案。我不知道为什么会这样。

从工程的角度来看,它使我感到困惑,原因有两个:


“写测试+重构直到通过”方法看起来令人难以置信的反工程。例如,如果土木工程师使用该方法进行桥梁建造,或者使用汽车设计师作为其汽车的制造商,他们将以很高的成本重塑桥梁或汽车,结果将是没有经过深思熟虑的体系结构造成的混乱。 “重构直到通过”准则通常被视为忘记架构设计并做任何必要的工作以符合测试的要求。换句话说,测试而不是用户来设置需求。在这种情况下,我们如何保证结果中的良好“缺陷”,即最终结果不仅正确,而且可以扩展,健壮,易于使用,可靠,安全,可靠等?这通常是架构所要做的。
测试不能保证系统能够正常工作。它只能表明它没有。换句话说,测试可能会向您显示如果系统未通过测试,则该系统包含缺陷,但是通过所有测试的系统并不比未通过测试的系统安全。测试覆盖率,测试质量和其他因素在这里至关重要。在民用和航空航天业中,已经报告了“绿色”结果给许多人带来的错误安全感觉,这是极其危险的,因为它可能被解释为“系统很好”,而实际上意味着“系统很好”。作为我们的测试策略”。通常,不检查测试策略。或者,谁来测试测试?

总而言之,我更关心TDD中的“驱动”位,而不是“测试”位。测试完全可以;我没有得到的是通过设计来驱动设计。

我想看到一些答案,这些答案包含了为什么软件工程中的TDD是一种好的做法以及为什么我上面已经解释的问题与软件无关(或不足够相关)的原因。谢谢。

评论

桥梁,汽车和其他物理设计远不及软件那样具有延展性。这是一个重要的区别,这意味着软件与实际工程之间的比较并不总是很重要。适用于网桥的软件可能不适用于软件,反之亦然。

我有点同意你的怀疑。例如,我承认我的印象是,在编写代码时,拥有测试套件可能会以某种方式“软化”注意力。当然,测试是一件好事(如果您希望有重构的可能性,则必须进行测试),但是只有当它们补充了对细节,边界情况,效率或可扩展性的关注,而不是取代它们时,这才是好事。

@ 6502:一定! TDD并非灵丹妙药,也无法解决软件开发过程中出现的所有问题。但是,这是组织工作流程的有用方法。例如,您可以强制要求所有边界案例都包含在测试中。您仍然需要知道这些边界条件是什么,但是现在您还拥有一个工具来检查您的代码是否正确处理了它们。

@CesarGon,您在我前段时间在SO上问过的这个问题中也可能很有趣……不是TDD,而是相关的……那里有一些非常启发性的答案。

没有一个土木工程/软件开发的模拟如此令人惊讶。同样,我经常注意到,我不能像修剪草坪一样烹饪煎饼。

#1 楼

我认为这里有一个误解。在软件设计中,设计与产品非常接近。在土木工程,建筑设计中,设计与实际产品脱钩:存在保留设计的蓝图,然后将其蓝图化为最终产品,并且将这些蓝图与大量的时间和精力分开。

TDD正在测试设计。但是,每个汽车设计和建筑设计也都经过测试。首先计算建筑技术,然后以较小规模进行测试,然后以较大规模进行测试,然后再将其发布到实际建筑物中。例如,当他们发明H型钢和负载时,请放心,在实际使用它建造第一个桥之前,已经对其进行了尝试和尝试。

还可以通过设计原型来测试汽车的设计,是的,当然可以通过调整不完全正确的方法,直到达到预期为止。
这个过程的一部分是速度较慢,因为正如您所说,您对产品的了解不多。但是,每一辆汽车的重新设计都借鉴了以前的经验,每座建筑物在空间,光线,绝缘,强度等方面的基础上都有数千年的基础。建筑物中的细节都在不断变化和改进。并在重新设计中进行更新。

还对零件进行了测试。也许与软件的样式并不完全相同,但是通常会测量机械零件(车轮,点火器,电缆)并施加压力,以了解尺寸是否正确,是否有异常现象等等。它们可能会被X射线或激光照射,测量时,他们会敲击砖块来发现破损的砖块,可能会在某种配置下对它们进行实际测试,或者对大型组进行有限的表示以将其真正用于测试。

这些都是您可以使用TDD放置的所有内容。

确实,测试不能保证。程序崩溃,汽车抛锚,当风吹来时,建筑物开始做有趣的事情。但是...“安全性”不是布尔值问题。即使您无法涵盖所有​​内容,也可以覆盖-例如-99%的可能性比仅覆盖50%的可能性要好。如果不进行测试,然后发现钢的沉降不好,就会变脆,刚放上主结构时,锤子第一次敲击就会断裂,这简直是浪费金钱。还有其他可能仍会伤害建筑物的问题,并不能使它变得如此愚蠢,以至于无法轻易避免缺陷会破坏您的设计。

对于TDD的实践,这是一个平衡的问题。 。以一种方式进行操作的成本(例如,不进行测试,然后再整理零件),而以另一种方式进行操作的成本。它始终是一种平衡。但是不要认为其他设计过程没有适当的测试和TDD。

评论


+1讨论制造中在哪里进行测试。好点。

–亚当·李尔♦
2011年1月30日15:20

您说“零件已测试”。可以,但不是测试驱动的设计。飞机零件的设计不是以测试驱动的方式进行,而是以结构化,前期设计的方式进行。与TDD的相似性在这里不存在。

– CesarGon
2011年1月30日在16:43

另外,我认为TDD主要涉及确保您可以检查零件的方法,而不是最后检查大的“全有或全无”的方法。但是,TDD的“首先建立测试”的提法并不意味着“在考虑要达成的目标之前先进行测试”。因为测试是设计的一部分。指定您想要该确切部分执行的操作。在开始键入之前,您已经进行了一些设计。 (通过这种方式,我认为“测试驱动设计”一词会误导性表示单向路径,实际上这是一个反馈回路)。

–印加人
2011年1月30日17:55

+1:软件纯粹是设计。问题中的桥梁类比是完全错误的。 TDD完全适用于外部单元测试。测试驱动设计适用于设计的所有层。

– S.Lott
2011年1月30日23:07

@CesarGon:不,TDD正在通过测试推动开发。这与驱动设计不同。设计决定了您将如何使用系统,从而决定了需要执行哪些测试来复制该行为。不过,实施这些测试通常可以帮助您优化设计。

–deworde
2011年10月21日,9:39

#2 楼

IMO,TDD的大多数成功案例都是伪造的,只是出于营销目的。它可能很少成功,但仅适用于小型应用程序。我正在使用TDD原理的大型Silverlight应用程序上工作。该应用程序已进行了数百次测试,但仍不稳定。由于复杂的用户交互,应用程序的某些部分无法测试。产生的测试包含大量的模拟和难以理解的代码。
最初,当我们尝试TDD时,一切似乎都不错。我能够编写许多测试,并模拟出难以进行单元测试的部分。一旦有了足够多的代码并且需要更改接口,您就会被搞砸。许多测试需要修复,您将重写比代码中实际更改还要多的测试。
Peter Norvig在《 Coders At Work》一书中解释了他对TDD的看法。

Seibel:用
测试来驱动设计的想法怎么样?
Norvig:我看到
测试更多地是纠正错误的方法,而不是设计的方法。 >这种极端的说法是,
“好吧,您要做的第一件事就是编写
测试,说我最后得到了正确的答案
,”然后您运行
并看到它失败,然后您
说,“下一步我需要什么?” —
似乎不是为我设计某些东西的正确方法。看起来
,只有如此简单,以至于预先确定了解决方案,才有意义。我认为您必须先考虑
。您必须说:“碎片是什么?在知道
是什么之前,我该如何为它们编写测试
?”然后,一旦完成
,对每个部分进行测试
并很好地理解它们如何相互影响并<
边界案例等等。那些
都应该进行测试。但是我认为您
说“此测试失败了”,从而推动了整个设计。


评论


现在,如果您将这些事实告诉TDD人士和顾问,那么您得到的答案将是,您做得正确的TDD不对!

– Navaneeth K N
2011年1月31日下午6:29

而且他们会是对的。我们正在非常高容量的系统上执行BDD / TDD,并且运行良好。测试在那里告诉您您违反了预期的行为。如果您打算在以后“破坏”测试并进行更改,则实际上您做错了。应该先更改测试以确保系统的新行为,然后再进行更改。是的,如果您做对了,则可以从“此功能需要做什么”开始编写测试,编写测试的过程可以帮助您认为“ IT部门需要做什么”。哦,从来没有使用过顾问...

–安迪
2011年10月21日,下午1:55

进行大量测试并不能免除您创建适当设计的麻烦。高度耦合的设计,不管围绕它进行多少测试,都将是脆弱的。在这种设计中嵌入测试可能会使整个情况变得更糟。

– Newtopian
2011年10月21日,下午2:32

这不是做错或成为高度耦合的设计的问题。事实是接口发生了变化。这意味着所有使用该接口的测试都必须更改。在大型系统上,使测试与所需的更改保持同步可能会开始使实施不堪重负。如果您要进行敏捷开发,这将成为一个更大的问题,因为接口更改的可能性更大。有趣的是,当方法论不起作用时,方法论的支持者坚持认为您做错了。该方法更有可能不适用于所有问题领域。

–扣篮
2011-10-21 14:42



以我的经验,TDD适用于小型应用程序或模块。当我必须处理复杂的事情时,TDD会使我慢下来,因为它迫使我写出详细的(可运行的)规范,然后才清楚地了解整体情况:因此,我太早地迷失了细节,而且常常不得不如果发现不需要某些类(我仍在使用设计),则放弃大量测试。在这种情况下,我宁愿先获得合理的总体设计,然后充实实现细节(可能使用TDD)。

–乔治
2014年5月6日10:00



#3 楼

测试驱动设计对我起作用的原因如下:

这是规范的可运行形式。

这意味着您可以从测试用例中看到:



被称为代码的代码完全填满了规范,因为期望的结果就在测试用例中。外观检查(期望测试用例通过)可以立即说:“哦,此测试检查在这种情况下调用invoiceCompany的结果应该是该结果。”

应如何调用代码。直接进行测试所需的实际步骤,无需任何外部支架(可以模拟数据库等)。

您首先从外部编写视图。

经常编写代码以首先解决问题的方式编写代码,然后考虑如何调用刚刚编写的代码。这通常会给用户带来尴尬的界面,因为通常更容易“仅添加标志”等。考虑到“我们需要这样做,使测试用例看起来像那样”,您就可以解决这个问题。这将提供更好的模块化,因为代码将根据调用接口编写,而不是相反。

这通常也将导致代码更简洁,而所需的解释性文档也更少。

您可以更快地完成操作

由于您具有可运行形式的规范,因此在完整的测试套件通过后就可以完成。在更详细地阐明事物时,您可以添加更多测试,但是作为基本原理,您将获得非常清晰可见的进度指示器以及完成的时间。

这意味着您可以知道当工作是否必要(它有助于通过测试)时,您最终需要做的更少。

对于那些对它可能有用的人,我鼓励您在下一个库例程中使用TDD。慢慢建立可运行的规范,并使代码通过测试。完成后,所有需要查看如何调用该库的人都可以使用可运行的规范。

最近的研究

”“案例研究的结果表明,与未使用TDD做法的类似项目相比,这四个产品的发布缺陷密度降低了40%至90%。从主观上讲,在采用TDD之后,团队的初始开发时间增加了15%至35%。 〜4个工业团队的成果和经验

评论


我要补充一点,您实际上对完成工作有一些合理而清晰的指导。如果没有一些清晰的过程来客观地验证您已完成手头的任务,那就很难知道。我自己的经验包括浪费大量时间和精力“协商”任务是否已完成以及生产线持续不断地移动。这会影响项目管理的所有级别,包括计划,因为您如何计划这样的任务?更清晰的目标和更快的周转时间可提高吞吐量和沟通。

–爱德华·奇异
2011年1月30日20:56

这应该是公认的答案。

–宁
19年4月25日在8:35

#4 楼

创建软件的过程不是编写代码的过程。没有“广泛的范围”计划,任何软件项目都不应首先开始。就像跨河两岸的项目一样,首先需要这样的计划。

TDD方法(主要)与单元测试有关-至少这是人们倾向于考虑的方式-这正在创造软件代码的最底层位。当已经定义了所有功能和行为并且我们实际上知道我们要实现的目标时。

在结构工程中,它看起来像这样:


“我们将这两块金属
连接在一起,并且该连接
需要承受x的
数量级的剪切力。让我们测试哪种
连接方法是最合适的方法



为了测试软件是否可以整体运行,我们设计了诸如可用性之类的其他测试测试,集成测试和验收测试。这些也应该在编写代码的实际工作开始之前定义,并且在单元测试为绿色之后执行。

请参见V模型:http://zh.wikipedia.org/wiki/ V-Model_%28software_development%29

让我们看看它如何在桥梁上工作:


地方政府对一家桥梁建筑公司说:“我们需要连接这两个点的网桥。网桥每小时必须能够允许n的流量,并且要在2012年12月21日之前准备就绪-这是验收测试的定义。公司将无法获得全部(或任何)钱(如果他们无法通过该测试。
公司的管理决定项目进度。他们成立了工作团队,并为每个团队设定了目标。如果团队无法实现这些目标-桥梁将无法按时建成。但是,这里有一定程度的灵活性。如果其中一个团队有问题,公司可以通过更改需求,更换分包商,雇用更多人员等来弥补这一点,从而使整个项目仍达到第1点中设定的目标。
负责设计特定的桥梁组件,就像上面的示例一样。有时解决方案很明显,因为我们拥有大量有关搭建桥梁的知识(就像在软件开发中使用经过良好测试的库一样-您仅假设它按宣传的方式工作)。有时您需要创建多个设计并对其进行测试以选择最佳设计。尽管如此,组件的测试标准仍然是已知的。


评论


如果我正确理解您的意思,则是说TDD可以,只要(a)仅将其用于单元测试,并且(b)还应将其与其他测试方法一起使用。在这种情况下,它可以寻址OP中的第2点。您将如何处理第1点?

– CesarGon
2011年1月30日13:29

@CesarGon:TDD在集成测试中也非常有用。

–sevenseacat
2011年1月30日13:37



要点1归结为该法案,即在汽车或桥梁的最终项目被接受之前,它要经过多次重复,在此期间,所有细节都要根据“广泛计划”的要求进行审查和测试。它主要是在纸上(或在计算机内存中)完成的,因为在这种情况下这比较便宜,但请注意,通常有物理原型正在构建整个结构(也许不是在桥的情况下)及其组件。

– Mchl
2011年1月30日13:39



@Karpie:还要进行验收测试!您应该事先知道要让客户接受您的工作需要什么。

– Mchl
2011年1月30日13:41

好吧。首先要开始这项工作的第一支团队是一个建筑师团队,他们被要求设计一个能够满足客户标准的桥梁,同时还要便宜,而且看起来可能不错,并且不会在第一阵强风下倒下。团队可能会提出一些或多或少满足这些标准的粗略设计,然后选择一个并进行更详细的处理,重复,重复,重复直到设计就绪(即满足给定的标准并且足够详细,以便项目的其他阶段可以开始)

– Mchl
2011年1月30日在16:15

#5 楼

在我看来,TDD之所以起作用,是因为


它会迫使您定义单位要执行的工作,然后再以任何规格或要求文档通常未涵盖的精度水平决定实施方式/>它使您的代码具有固有的可重用性,因为您必须在测试和生产场景中都使用它。
它鼓励您以较小的难度编写代码来测试块,这往往会导致更好的设计。

特别是在您提出的要点上。


代码比砖或钢更具延展性,因此修改便宜。如果进行测试以确保行为不变,则价格会更低廉。
TDD并非不做设计的借口-仍建议使用高级体系结构,只是不要过多地详述。不鼓励使用Big Up Front Design,但鼓励进行足够的设计
TDD不能保证系统正常运行,但可以避免许多小错误,否则可能会被遗漏。另外,由于它通常会鼓励使用更好的分解代码,因此通常更易于理解,因此不太容易出错[br />

评论


您还应该补充一点,当发现缺陷时,可以确保不会重复出现缺陷,因为您将添加另一个测试。

–安迪
2011年10月21日,下午1:56

#6 楼

TL; DR

编程仍然是设计活动,而不是构造活动。在事实成立之后编写单元测试仅能确认代码已完成其工作,而不是确定它会做有用的事情。测试失败是真正的价值,因为它们可以让您及早发现错误。

代码就是设计

在PPP的第7章中,“鲍勃叔叔”直接谈到了这个问题。在本章的开始,他引用了Jack Reeves的一篇出色的文章,他在文章中建议代码是设计的(链接指向收集该主题的所有三篇文章的页面)。

这个观点的有趣之处在于,他指出,与其他工程学科的建设活动非常昂贵相比,其他工程学科与之不同,软件的构建相对来说是免费的(在您的IDE中进行编译,您便拥有了已构建的软件)。如果将编写代码视为设计活动而不是构造活动,那么红绿重构周期基本上就是设计中的练习。设计会随着编写测试,满足测试要求的代码以及重构以将新代码集成到现有系统中而发展。

TDD作为规范

您为TDD编写的单元测试是您理解规范后的直接翻译。通过编写最低限度地满足您的规范的代码(使测试变为绿色),您编写的所有代码都将用于特定目的。通过重复测试可以验证是否达到了该目的。

将测试编写为功能

在单元测试之后编写测试时,会发生单元测试中的常见错误。代码,最后测试该代码是否可以完成其工作。换句话说,您将看到这样的测试

public class PersonTest:Test
{
   [Test]
   TestNameProperty()
   {
      var person=new Person();
      person.Name="John Doe";
      Assert.AreEqual("John Doe", person.Name);
   }
}


虽然我认为这段代码可能很有用(请确保某人没有通过简单的属性进行淫秽的操作)。它不能用来验证规范。正如您所说,编写此类测试只会带您走这么远。

绿色是好的,价值在于红色
当我在TDD中遇到第一个真正的“ aha”时刻时,发生意外的测试失败。我有一套针对正在构建的框架的测试。添加一个新功能后,我为此进行了测试。然后编写代码以使测试通过。编译,测试...在新测试上获得绿色。但是在另一个我没想到会变红的测试中也出现了红色。

看着失败,我松了一口气,因为我怀疑我会在相当长的一段时间内发现该错误。如果我没有适当的测试。这是一个非常讨厌的错误。幸运的是,我进行了测试,它准确地告诉了我修复该错误所需的操作。如果没有测试,我将继续构建系统(该错误会感染依赖于该代码的其他模块),并且在发现该错误时,正确修复它将是一项主要任务。

TDD的真正好处在于,它使我们能够毫不顾忌地放弃进行更改。就像编程的安全网一样。想想如果空中飞人犯错并跌倒会发生什么。使用网络,这是一个令人尴尬的错误。没有,那是一场悲剧。同样,TDD可以使您避免将愚蠢的错误变成项目致命的灾难。

评论


捕获错误的红色测试的值通常是单元测试的属性,而不是TDD的特定属性。

–罗伯特·哈维(Robert Harvey)
2011年1月31日23:27

您说得对。但是,在事后单元测试中涵盖该特定错误的可能性更低。

–迈克尔·布朗(Michael Brown)
2011年2月1日于1:06

您可以提供一些证据,数据或可靠的分析来支持该主张吗?

– CesarGon
2011年2月20日在18:18

@CesarGon的这项研究是针对从事小型项目的程序员的,这表明使用TDD的开发人员所生成的代码具有比事后进行测试的人员更好的测试覆盖率(92%-98%对80%-90%),因此可以捕获更多代码开发过程中的缺陷(使用TDD生成的代码中发现的缺陷减少了18%)。

–法律
2014年11月10日23:09

#7 楼

您不会找到任何人主张测试驱动开发,甚至提倡测试驱动设计(它们是不同的),它们说测试证明了应用程序。因此,让我们称其为“稻草人”,然后做吧。

您不会发现任何不喜欢TDD或对TDD印象深刻的人,他们说测试是浪费时间和精力。尽管测试不能证明应用程序,但是它们在发现错误方面非常有帮助。

说了这两点,对于在软件上实际执行测试,双方都没有做任何不同的事情。两者都在做测试。两者都依靠测试来发现尽可能多的错误,并且都使用测试来验证软件程序是否在正常运行以及是否可以在当时被发现。没有任何线索的人会在没有测试的情况下出售软件,没有任何线索的人会期望测试将使他们出售的代码完全没有错误。

因此,TDD和not-TDD之间的区别是'正在进行测试。区别在于编写测试的时间。在TDD中,测试是在软件之前编写的。在非TDD测试中,测试是在软件之后或与软件配合进行的。

我所看到的关于后者的问题是,测试随后往往将目标对准编写的软件,而不是期望的结果或规格。即使测试团队与开发团队是分开的,测试团队也倾向于查看软件,进行试用并编写针对该软件的测试。

时不时地注意到一件事研究项目成功的人再次指出,客户多久会布置自己想要的东西,开发人员就会跑出来写点东西,而当他们回到客户那里说“完成”时,结果就完全是完全没有了。客户的要求。 “但是它通过了所有测试……”

TDD的目标是打破这种“循环论证”,并为测试不是软件本身的软件的测试提供基础。编写测试以针对“客户”想要的行为。然后编写该软件以通过那些测试。

TDD是旨在解决此问题的解决方案的一部分。这不是您迈出的唯一一步。您需要做的其他事情是确保有更多的客户反馈和更多的时间。

但是,根据我的经验,TDD很难成功实施。在产品推出之前很难编写测试,因为许多自动化测试都需要进行一些操作才能使自动化软件正常工作。要使不习惯单元测试的开发人员也很难做到这一点。我一次又一次地告诉团队中的人首先编写测试。我从来没有真正做到这一点。最后,时间限制和政治破坏了所有努力,因此我们甚至不再进行单元测试。当然,这不可避免地导致了设计的偶然性和严重的耦合,因此,即使我们愿意,现在实施起来的成本也非常高。避免TDD是TDD最终为开发人员提供的。

评论


+1感谢您的全面答复,诺亚。我同意TDD与非TDD之间的主要区别在于编写测试的时间。但是,我也认为TDD中的第一个“ D”代表“驱动”,这意味着,在TDD中,整个开发过程都是由测试驱动的。这是我最困惑的地方。在实际构建要测试的内容之前,我在编写测试方面没有任何问题。但是让测试驱动吗?只要表面的(即结果)还可以,做什么与绿灯有什么不同?

– CesarGon
2011年1月30日22:38

好吧,塞萨尔,您将提出什么更好的客观标准来决定何时完成开发任务?如果像TDD中一样,测试是开发人员针对的规范,那么开发人员在测试通过时就完成了工作,不是吗?是的,测试中可能存在缺陷,就像任何规范中都可能存在缺陷一样。这不是开发人员要解决的任务。如果测试存在缺陷,则将其修复,然后将开发目标定为新目标,并在所有目标变为绿色时完成。之所以有效,是因为总有一项测试可以通过...没有多余的,没有证件的绒毛。

–爱德华·奇异
2011年1月31日,下午1:28

也许我没有表达清楚自己。测试可能是确定何时完成的好方法。但是我认为它们不是决定必须构建的内容的好方法。而且,在TDD中,我发现人们正在使用测试来决定他们应该构建的东西。那也是你的经历吗?

– CesarGon
2011年1月31日14:09

否。我们的构建是自动化的。它们是由变化触发的。就像我说的那样,TDD只是解决方案的一部分。

–爱德华·奇异
2011年1月31日18:00



#8 楼

首先设计

TDD并不是跳过设计的借口。我已经看到许多“敏捷”潮流中的飞跃,因为它们虽然可以立即开始编码。真正的敏捷将使您获得统计信息编码的速度比启发瀑布过程的(其他领域)工程良好实践要快得多。

但是早点测试

当有人说测试时推动设计的发展,仅意味着人们可以在设计阶段的很早就使用测试,而这要早于完成。进行此测试将对产品的灰色区域提出挑战,并将其与现实世界相提并论,从而大大影响您的设计,而这要早于产品完成。迫使您经常回去设计并对其进行调整以考虑到这一点。

测试和设计...一个又一个

我认为TDD只是带来了测试成为设计不可或缺的一部分,而不是最后进行验证。随着您开始越来越多地使用TDD,您会在设计时就着手解决如何破坏/破坏系统的想法。我个人并不总是先进行测试。当然,我在接口上进行了显而易见的(单元)测试,但是真正的收获来自于我想到这种设计可以打破的新颖且创造性的方式时所创建的集成和规范测试。我一想到一种方法,就为它编写一个测试代码,然后看看会发生什么。有时我可以接受结果,在这种情况下,我将测试移到一个不属于主要版本的单独项目中(因为它将继续失败)。

然后由谁来推动演出?

在TDD中,这里的驱动只是意味着您的测试对设计的影响很大,以至于人们可以感觉到他们实际上在驱动它。然而,到此为止,我明白了您的担忧,这有点吓人...谁主持了演出?

您在驾驶,而不是测试。测试在那里进行,因此随着您的前进,您对所创建的内容会充满信心,从而使您可以进一步了解它基于扎实的基础。

只要测试是可靠的

就是这样,因此在TDD中受驱动。测试并不是驱动整个事情的主要因素,但是它们将对您的工作方式,设计和思维方式产生深远的影响,因此您会将大部分思维过程委托给测试,并以此作为回报。

是的,但是如果我用我的桥梁做到这一点...。

就停在那里...软件工程非常有别于其他工程实践。实际上,软件工程实际上与文献有很多共同点。一个人可以读一本完整的书,从中撕掉4章,然后再写两个新章来代替它们,将它们重新粘贴在书中,您仍然会拥有一本好书。有了良好的测试和软件,您可以撕裂系统的任何部分并用另一个部分替换,这样做的成本不会比最初创建它时高很多。实际上,如果您进行了测试并允许他们对设计产生足够的影响,那么它可能比最初创建它要便宜得多,因为您将有一定的信心,认为这种替换不会破坏测试的内容。

如果太好了,为什么它并不总是起作用?

因为测试与构建所需要的思维方式非常不同。并非每个人都能来回切换,实际上,有些人将无法仅仅因为无法立志破坏自己的创作而建立适当的测试。这将导致项目进行的测试太少或测试仅够达到目标指标(想到代码覆盖率)。他们会乐于进行路径测试和异常测试,但会忘记极端情况和边界条件。

其他人将仅依靠部分或全部放弃设计的测试。每个成员都这样做,然后彼此整合。设计首先是沟通工具,我们扎根于地面要说这就是我要去的地方,草图表示这就是门窗要去的地方。没有此功能,无论您进行了多少测试,您的软件都将注定失败。集成和合并将一直很痛苦,并且它们将缺乏最高抽象级别的测试。

或者这些团队TDD可能不是要走的路。

#9 楼

使用TDD时,您往往不会编写难以或快速测试的代码。这看起来似乎是一件小事,但它可能会对项目产生深远的影响,因为它会影响重构,测试,使用测试重现错误以及验证修补程序的难易程度。

项目中的新开发人员可以在测试得到更好的分解代码支持时加快工作速度。

评论


我喜欢这一点,它强调的一点是,它产生的好处不是很多TDD(尽管进行单元测试显然具有巨大的价值),而是它所产生的那种可测试的(独立的)代码。随之而来的是各种各样的好事(关注点分离,IoC和依赖注入等)

–墨菲
2011年1月30日14:14

@Murph是的TDD可帮助您保持诚实:)

– Alb
2011年1月30日14:20

老实说,我实际上并不相信“更容易上手”的说法-测试可能会有所帮助,但是代码(整体上,不一定是孤立的)可能会更难解码,因为有些东西会好像是魔术般出现您不知道您正在使用IInjectedThing的哪种实现。

–墨菲
2011年1月30日14:47



@Murph从理论上讲,实现IInjectedThing的方式也设计得很好,并包含良好的测试,因此您真的不需要知道它是什么就可以理解被注入的类。

–亚当·李尔♦
2011年1月30日15:19

@Anna-是的,在某种程度上……如果您要尝试找出出现问题的地方(我总是觉得寻找bug是找到立足项目的好方法)或需要更改的地方/添加您需要知道哪里。即使封装得很好,您仍然需要找到它...并且如果它意味着替换某些东西(IWhatsit的新实现),则您需要知道如何使用替代实现。再一次,我并没有反驳说这种结构是不好的-相反的证据太多了-而是暗示某些事情可能不太明显。

–墨菲
2011年1月30日17:31

#10 楼

尽管我自己没有那么多地练习TDD,但我一直在考虑很多。代码质量和后续TDD之间似乎存在(强?)正相关。

1)我的第一个想法是,这(主要)不是因为TDD在代码中(因此)添加了“更好的质量”,更像是TDD帮助清除了最糟糕的部分和习惯,因此间接地提高了质量。

我什至主张这不是测试本身,而是编写这些测试的过程。很难为错误的代码编写测试,反之亦然。并在编程时将其放在首位,从而消除了很多不良代码。

2)另一种观点(这在哲学上是遵循的)是遵循主人的心理习惯。您不会通过遵循他的“外部习惯”来学习成为大师(例如,留胡子的习惯是好的),您必须学习他的内部思维方式,这很难。并且以某种方式使(新手)程序员遵循TDD,使他们的思维方式更接近于大师。

评论


+1我认为您已将其钉牢,Maglob。我特别喜欢您的解释,即“ TDD有助于清除最差的部分和习惯,从而间接提高质量”。长长的胡须比喻也很好。

– CesarGon
2011年1月30日15:59

您不会为错误的代码编写测试,而是先编写测试,然后编写代码以使测试通过。

–user1249
2011年1月30日21:48

Maglob,对事物更实际的一面的热爱,使您获得了最好的覆盖。 @Thorbjørn,我认为Maglob沿线走得更远,如果您的预期设计糟透了,您的测试肯定会直接吸收到您想要实现的糟透程度,并且臭味应该在您的测试之前出现您甚至可以编写任何实际代码。

–FilipDupanović
2011年1月31日14:29



#11 楼


“编写测试+直到通过重构”
的方法看起来令人难以置信
反工程。


您似乎对重构和重构都存在误解。 TDD。


代码重构是
更改计算机程序源代码的过程,而无需修改其外部功能行为以
改进软件的某些非功能性
属性。


因此,在代码通过之前,您无法重构代码。

和TDD,特别是单元测试(由于其他测试对我来说似乎是合理的,所以我认为这是核心的改进),它并不是在重新设计组件之前才起作用。这是关于设计组件并在实现上进行工作,直到组件按设计工作为止。

此外,真正掌握这一点很重要,单元测试与测试单元有关。由于总是从头开始编写很多东西的趋势,因此测试此类单元很重要。一位土木工程师已经知道他使用的单元的规格(不同的材料),并可以期望它们能工作。这是两件事,通常不适用于软件工程师,在使用它们之前对它们进行非常专业的工程设计,因为这意味着要使用经过测试的高质量组件。要使用一些新的纤维纸巾来制作覆盖
体育场的屋顶,您可能希望他将其作为一个单元进行测试,即定义所需的规格(例如重量,渗透性,稳定性等),然后进行测试和优化它,直到遇到它们为止。

这就是TDD起作用的原因。因为如果您构建经过测试的单元的软件,则可以更好地工作,将它们连接在一起,如果没有,则可以认为问题出在胶合代码中,前提是您的测试覆盖范围广。

编辑:
重构意味着:功能不变。编写单元测试的一点是要确保重构不会破坏代码。因此,TDD的目的是确保重构不会产生副作用。
粒度不是透视的主题,因为正如我所说,单元测试是测试单元而不是系统,因此粒度是精确定义的。 br />
TDD鼓励良好的体系结构。它要求您为所有单元定义和实现规范,迫使您在实现之前设计它们,这与您似乎想的完全相反。 TDD规定了单位的创建,这些单位可以单独进行测试,因此可以完全脱钩。
TDD并不意味着我用意大利面条式代码进行了软件测试,并搅拌通心粉直到通过。

与土木工程不同,在软件工程中,项目通常会不断发展。在土木工程中,您需要在位置A处建造一座桥梁,该桥可以承载x吨,并且足够宽,每小时可容纳n辆车。
在软件工程中,客户基本上可以在任何时候做出决定(可能在之后完工),他想要一座双层桥,并且希望它与最近的高速公路连接,并且他希望这座桥成为吊桥,因为他的公司最近开始使用帆船。
软件工程师的任务是更改设计。不是因为他们的设计有缺陷,而是因为那是作案手法。如果软件设计良好,则可以在不重新编写所有低级组件的情况下进行高级别的重新设计。

TDD旨在构建具有经过单独测试的,高度分离的组件的软件。如果执行得当,它将比不使用它更快,更安全地帮助您响应需求的变化。

TDD在开发过程中增加了要求,但并不禁止任何其他质量保证方法。诚然,TDD不能提供与形式验证相同的安全性,但是同样,形式验证非常昂贵,并且无法在系统级别使用。而且,如果您愿意,也可以将两者组合。

TDD还包含在系统级别执行的单元测试以外的测试。我发现这些内容容易解释,但难以执行且难以衡量。而且,它们很合理。尽管我绝对看到它们的必要性,但我并没有真正将它们视为思想。

最后,没有工具能够真正解决问题。工具只会使解决问题更加容易。您可以问:凿子将如何帮助我打造出色的建筑?好吧,如果您打算做直墙,那么直砖会有所帮助。是的,当然,如果您将该工具提供给白痴,他可能最终会用脚猛击它,但这不是凿子的错,这并不是TDD的缺陷,它为新手提供了虚假的安全保障,谁没有编写好的测试。
因此,总而言之,TDD的工作要比没有TDD的要好得多。

评论


我不认为我有误解。我同意您发布的代码重构的定义,但是我也认为您需要查看代码更改的粒度。当您说“更改计算机程序源代码的过程”时,您需要认识到,从某个整体的角度来看,其行为没有改变,但是各部分的行为确实发生了改变。这就是改变的方式。除此之外,我还听您介绍了TDD为何起作用(并与我分享),但是按照我的原始帖子,如何解决体系结构?

– CesarGon
2011年1月31日14:04

@CesarGon:帖子已更新。

–back2dos
2011年1月31日18:12

#12 楼

我不喜欢您说“测试而不是用户来确定要求”。我认为您只考虑在TDD中进行单元测试,而它还涉及集成测试。

除了测试构成软件基础的库之外,还要编写覆盖用户交互的测试与软件/网站/任何。这些直接来自用户,像Cucumber(http://cukes.info)之类的库甚至可以让您的用户以自然语言自己编写测试。

TDD还鼓励代码的灵活性-如果您花了很多时间来设计某种事物的体系结构,如果需要的话,以后进行更改将非常困难。首先编写一些测试,然后编写一些通过这些测试的代码。添加更多测试,添加更多代码。如果您需要从根本上更改代码,则您的测试仍然有效。

与桥梁和汽车不同,单个软件在其生命周期内可能会发生巨大变化,并且无需编写测试就可以进行复杂的重构。首先只是自找麻烦。

评论


我听说您要求TDD带来好处。但是据我了解,您没有解决我在问题中明确要求的体系结构和测试质量问题。

– CesarGon
2011年1月30日13:40



@CesarGon:我认为您的特定问题适用于任何类型的测试,而不仅仅是TDD。因此,我只关注“工作”的TDD的特定功能。

–sevenseacat
2011-1-30在13:43

集成测试绝对比独立单元测试更有意义。我偶然发现的大多数错误案例都不会通过单元测试找到,只有通过将所有螺栓和哨子都安装到位的整个真实系统进行测试。

–user8685
2011年1月30日14:40



#13 楼

我认为您正在从错误的角度接近第一个问题。

从理论上讲,我们通过检查故障点来证明某些方法可行。那就是使用的方法。您可能有许多其他方法可以证明某项功能正常,但是TDD的建立是由于其按位方式的简单性:如果不破坏它,它就会起作用。

在实践中,刚好直译为:现在,我们可以继续进行下一步(成功应用TDD满足所有谓词之后)。如果您从这种角度看待TDD,那么,与“编写测试+重构直到通过”无关,而更多的是要完成此操作,我现在将重点放在下一个功能上,这是目前最重要的事情。

请考虑一下这如何适用于土木工程。我们正在建造一个体育场,可容纳15万人。在证明体育场的结构完整性良好之后,我们首先满足了安全性要求。现在,我们可以集中精力处理其他紧迫的问题,例如洗手间,食品摊位,座位等……使听众的体验更加愉悦。这是一个过分的简化,因为TDD还有很多其他功能,但症结在于,如果您同时专注于令人兴奋的新功能并保持完整性,那么您将无法获得最佳的用户体验。在这两种情况下,您都会获得成功。我的意思是,您怎么能确切知道有多少个洗手间,应该为15万人放置在哪里?我很少见到体育场在我的一生中倒塌,但是我不得不在很多时候在半场比赛中排队。那就是说洗手间问题可以说更加复杂,如果工程师们在安全方面花费更少的时间,他们也许最终能够解决洗手间问题。

您的第二点是无关紧要的,因为我们已经同意绝对是愚蠢的努力,并且因为汉克·穆迪(Hank Moody)说绝对不存在(但我似乎找不到参考)。

评论


+1可以很好地解释我的第一点,也可以参考Hank Moody。辉煌。

– CesarGon
2011年1月31日14:10

谢谢,我很感激。我将TDD视为一种心理现象,而不是一种技术方法/过程。但这只是我对此事的世界观。

–FilipDupanović
2011年1月31日下午14:32

您能确切知道多少个盥洗室以及应将它们放置在何处吗?答案是肯定的-请教任何架构师,他们会告诉您该信息是预先准备好的,有时还会提供清晰的统计数据来备份。

– gbjbaanb
2013年9月14日22:28在

#14 楼

在软件工程中,TDD是一种良好的做法,与在应用程序中进行错误处理,进行日志记录和诊断一样(尽管它是错误处理的一部分),也是一种良好的做法。作为一种将软件开发减少为试验和错误编码的工具。但是,大多数程序员仍然在开发阶段盯着运行时日志,观察调试器中的异常或使用其他失败/成功的迹象,其中包括一整天的编码/编译/运行应用程序。

TDD只是一种形式化和自动化这些步骤的方法,可以使您作为开发人员更加高效。

1)您无法将软件工程与桥梁建设相提并论,桥梁建设的灵活性远不及设计软件程序。搭建网桥就像将相同的程序一遍又一遍地写入有损机器中。网桥不能像软件那样重复和重用。每个桥都是唯一的,必须制造。汽车和其他设计也是如此。

软件工程中最难的事情是再现故障,当桥梁故障时通常很容易确定出了什么问题,并且从理论上讲也很容易再现失败。当计算机程序失败时,它可能是一系列复杂的事件,使系统进入故障状态,并且很难确定错误的位置。 TDD和单元测试可以更轻松地测试软件组件,库和算法的健壮性。

2)使用弱单元测试和浅测试用例,它们不会给系统带来错误的信心,只是不好的做法。当然,忽略系统的体系结构质量并仅仅执行测试也是很糟糕的。但是在建筑工地作弊以节省摩天大楼或桥梁以节省材料却不遵循设计图是很糟糕的,并且一直在发生...

评论


我不同意您的暗示,即在物理(即非软件)系统中很容易重现故障。例如,研究确定航空交通事故中机械故障的根本原因所必需的极其复杂,艰苦的工作。

– CesarGon
2011年1月30日在16:02

嗯,现在您正在将坠毁的客机与发生故障的机桥进行比较,通常情况下,机桥无法飞行,机壳关闭。但是有时飞机和软件之间的比较是有效的。这两个领域都很复杂,需要结构化的测试方法。因此,当网桥发生故障时,您就知道它已经过载。当飞机坠毁时,您会知道在地面上飞行的异常状态失败了,但是原因通常需要与软件故障一样进行彻底调查。

–埃内利
11年2月12日在19:30

桥梁可以复制-至少,您从建筑师那里购买的桥梁蓝图可以粗略地修改以适合您的实际情况。关键是,如果您需要一座桥梁,您会去找建筑师,他会给您列出您可以拥有的几种类型的清单-悬架,箱形,拱形等,以及用于建造桥梁的有限材料清单。

– gbjbaanb
2013年9月14日22:30在

#15 楼

如果您接受发现的bug越早,修复它们的成本越低,那么光是TDD就值得了。

评论


您是否有任何证据表明在TDD设置中会更快发现错误?另外,TDD的副作用如何,例如对体系结构的影响?

– CesarGon
2011年1月30日在22:39



#16 楼

TDD与测试无关。当然,它不能替代良好的测试。它为您提供的是一种经过深思熟虑的设计,便于消费者使用,并且易于维护和以后重构。这些事情反过来导致更少的错误和更好,更易适应的软件设计。 TDD还可以帮助您仔细考虑并记录您的假设,通常会发现其中一些不正确。您会在流程的早期发现这些。

作为一个不错的附带好处,您可以运行大量测试,以确保重构不会改变软件的行为(输入和输出)。

评论


-1。很多人一直在说这个,但是我还没有看到实现它的魔力。

–巴特·范·恩根·舍瑙(Bart van Ingen Schenau)
2011年1月31日13:07

@Bart van Ingen Schenau,您完成了TDD吗?我已经做了大约4年了,我肯定已经看到了“魔术”的发生。

– Marcie
2011年1月31日下午16:46

#17 楼

我给你一个简短的答案。通常,TDD就像单元测试一样,以错误的方式看待。直到最近,我在观看了精彩的技术讲座视频后才了解单元测试。本质上,TDD只是在说明您希望以下工作。它们必须实现。然后,您可以按照通常的方式设计其余的软件。

有点像在设计库之前为库编写用例。除了可以在库中更改用例外,您可能不使用TDD(我将TDD用于API设计)。还鼓励您添加更多测试,并考虑该测试可能会获得的疯狂投入/使用。在编写库或API时,我发现它很有用,如果您更改某些内容,则必须知道自己已破坏某些内容。在大多数日常软件中,我都不会打扰,因为为什么我需要一个供用户按下按钮的测试用例,或者为什么我想接受CSV列表或每行只有一个条目的列表,所以我没有关系。更改它,因此我不应该/不能使用TDD。

#18 楼

当结构工程是具体的时,软件是有机的。

建造桥梁时,它仍然是一座桥梁,不太可能在短时间内演变成其他东西。改进将持续数月和数年,但不会像软件那样花费数小时和数天。

单独进行测试时,通常可以使用两种类型的框架。受约束的框架和不受约束的。不受限制的框架(在.NET中)使您可以测试和替换所有内容,而无需考虑访问修饰符。即您可以存根和模拟私有和受保护的组件。

我见过的大多数项目都使用受约束的框架(RhinoMocks,NSubstitute,Moq)。使用这些框架进行测试时,必须以一种可以在运行时注入和替换依赖项的方式设计应用程序。这意味着您必须具有松散耦合的设计。松耦合的设计(正确完成时)意味着可以更好地分离关注点,这是一件好事。

总而言之,我相信这背后的想法是,如果您的设计是可测试的,那么它就很松散耦合,并具有良好的关注点分离。

附带说明,我看到了真正可测试的应用程序,但是从面向对象设计的角度看,这些应用程序编写得很差。

#19 楼


为什么TDD起作用?


不起作用。

澄清:自动化测试总比没有测试要好。但是我个人认为大多数单元测试都是浪费的,因为它们通常是重言式的(即说从实际测试代码中可以明显看出来),并且不能轻易证明它们是一致的,不是多余的并且涵盖所有边界情况(通常会发生错误) )。

最重要的是:优秀的软件设计并没有像许多敏捷/ TDD推广者所宣传的那样神奇地从测试中消失。否则,每个人都请提供指向经过同行评审的科学研究的链接,以证明这一点,或者至少参考一些开源项目,在该项目中,可以通过其代码更改历史来潜在地研究TDD的益处。