使用单元测试进行开发与不进行测试之间的时间差


132

我是一个单独的开发人员,拥有一个受时间限制的漂亮工作环境,每个项目的开发时间通常为1-4周,具体取决于需求,紧迫性或两者兼而有之。在任何给定的时间,我都会处理大约3-4个项目,其中一些时间表彼此重叠。

预期,代码质量会受到影响。我也没有正式测试;它通常会涉及到遍历整个系统,直到出现故障为止。结果,大量的错误逃逸到了生产环境中,我必须修复这些错误,然后又使我的其他项目受挫。

这是进行单元测试的地方。正确完成后,应该将错误(更不用说那些逃到生产环境的错误)保持在最低限度。另一方面,编写测试可能要花费大量时间,对于像我这样的时间受限制的项目来说,这听起来并不好。

问题是,与未经测试的代码相比,编写经过单元测试的代码要花多少时间差?随着项目范围的扩大,时间差如何扩大?


评论不作进一步讨论;此对话已转移至聊天
maple_shaft

8
您正在解决错误的问题。您太忙了,似乎没有项目管理支持。您是否正在估算项目工作量?您是否将20%的时间用于错误修复,会议和其他非编码任务?你加班多少?
托尼·恩尼斯

20
您是否意识到您实际上是在说:“我有时间做两次,但没有时间做正确的一次。”?
RubberDuck

5
@RubberDuck实际上在项目复杂度曲线中有一个点,以“写入时间与测试时间”来衡量,其中“拧两次”所需的时间少于“写入并进行测试”的时间。我认为它可能在bash oneliner的某个区域。
Lyndon White

有一次开发人员收到了礼物,并感谢项目被取消。我指出,如果我们知道该产品不发货,我们本来可以提高生产力。因此,在这种情况下,无需测试即可进行开发。
JDługosz

Answers:


149

测试越晚,编写测试的成本就越高。

错误的生存时间越长,修复的成本就越高。

收益递减法则确保您可以尝试消除遗漏,以确保没有错误。

佛陀教导了中间道路的智慧。测试很好。好东西太多了。关键是能够分辨出您何时失衡。

与在编写代码之前编写测试的情况相比,在没有测试的情况下编写的每一行代码都会花费更大的成本来添加测试。

没有测试的每一行代码将更加难以调试或重写。

您编写的每个测试都需要时间。

每个错误都需要时间来修复。

忠实的人会告诉您在没有首先编写失败的测试之前不要编写一行代码。该测试可确保您得到预期的行为。由于测试证明行为相同,因此它使您可以快速更改代码,而不必担心会影响系统的其余部分。

您必须权衡所有这些与测试未添加功能的事实。生产代码添加了功能。功能是付账的。

务实地说,我添加了所有可以摆脱的测试。我无视观看测试的评论。我什至不相信代码会按照我的想法去做。我相信测试。但据我所知,偶尔扔冰雹玛丽会很幸运。

但是,许多成功的编码人员都不会进行TDD。那并不意味着他们不测试。他们只是不执着地坚持每行代码都有针对它的自动化测试。甚至Bob叔叔也承认他不测试自己的UI。他还坚持要求您将所有逻辑移出UI。

作为橄榄球的隐喻(即美式橄榄球),TDD是一项很好的地面比赛。仅在您编写一堆代码并希望它能正常工作的地方进行手动测试是一个成功的游戏。您都可以擅长。除非你们俩都能做到,否则您的职业生涯不会进入季后赛。除非您学习何时挑选每个,否则它不会成为超级碗。但是,如果您需要朝某个特定方向轻推,那么当我过世时,官员的电话经常会反驳我。

如果您想尝试一下TDD,我强烈建议您练习之前尝试练习。TDD半途而废,全心全意,半途而废是一个不尊重它的重要原因。这就像将一杯水倒入另一杯中。如果您不承诺而快速彻底地做到这一点,那末您到处都是运球的水。


68
一件好事太多了,无论您还是佛陀都没有测试过我祖母的饼干:-)
皮埃尔·阿洛德

3
@NickAlexeev我喜欢那里的图表。它没有指出的一件事是,单元测试(通常是自动化的)真的很擅长在修改代码时发现错误。我很乐意将其分为“发布前发现的错误”和“发布后发现的错误”。单元测试是防止回归的最佳方法。
corsiKa

3
我认为这是一个非常平衡的答案:测试所有内容,甚至是琐碎的事情,都可能浪费时间。对容易断裂的复杂零件进行良好的测试确实会有所帮助。我刚刚完成了将一个小型但不平凡的项目从Java移植到C ++的工作。我首先移植了测试,这指导我设置了整个C ++实现。一旦所有测试都变成绿色,只需移植一些较简单的类即可,并且运行非常顺利。另一方面,我没有对所有代码进行测试:这将使实施至少延长3-4天,而收效甚微。
Giorgio 2016年

5
对此稍有不同意见:“您必须权衡所有因素与测试未添加功能的事实。代码增加了功能。功能是付账的。” 我强烈建议,不是功能付钱-它是有效的功能。(或者人们是否因无法正常工作的交付物而获得报酬?)。我完全同意其余的答案。
Tony Suffolk

6
@ TonySuffolk66你是正确的,这是工作功能付钱的(除非有flimflam销售技术),但是人们早在TDD成为现实之前就已经创建了工作功能。他们会在很久以后消失的。记住,TDD是一种严格的测试方法。这不是唯一的纪律性测试方法。
candied_orange

112

我同意其余的答案,但直接回答时差问题是什么

Roy Osherove在他的书《单元测试的艺术》,第二版,第200页中进行了一个案例研究,该案例针对两个不同的客户,使用相似的团队(类似技能)实施了类似规模的项目,其中一个团队进行了测试,而另一团队则没有进行测试。

他的结果是这样的:

有和没有测试的情况下衡量团队的进度和输出

因此,在项目结束时,您将获得更少的时间和更少的错误。当然,这取决于项目的规模。


32
样本量太小,以至于不能认为这是科学的,但我认为这代表了很多人的经验。我发现执行TDD时,大多数额外的时间花在了修复导致单元测试失败的错误上,而不是自己编写测试。这并没有真正增加额外的时间,只是在您发现并解决这些问题时进行了调整。任何真正的额外时间都在解决您不会发现的问题,至少在第一轮工作中没有。
JimmyJames

7
@JimmyJames这是一个案例研究,在尚无法进行大规模的可重复实验的情况下,已广泛用于商业和科学领域。有很多心理学期刊。“不科学”不是正确的词。
djechlin '16

25
我为什么认为如果该案例研究的结果显示出相反的结果,就不会被纳入本书;-)?
布朗

11
@DocBrown我想知道有多少案例研究在找到正确答案之前被丢弃了:-)
gbjbaanb

6
@JimmyJames几乎可以肯定是科学。此外,另一位科学家可能会将此研究读为“ n = 1”案例研究,认为值得进一步研究,然后进行大规模的统计研究,甚至进行纵向对照实验,然后确认或拒绝。这正是科学的运作方式。那应该是这样的。你可以阅读更多关于如何科学在这里工作en.wikipedia.org/wiki/Scientific_method
djechlin

30

我只知道一项研究是在“实际环境”中进行的:通过测试驱动的开发实现质量改进:四个工业团队的成果和经验。明智地执行此操作的成本很高,因为这基本上意味着您需要与相似的团队一起开发同一软件两次(理想情况下甚至更频繁),然后将所有软件扔掉。

该研究的结果是,开发时间增加了15%–35%(这远远不及TDD评论家经常引用的2倍),而释放前的缺陷密度则从40%–90%(! )。请注意,所有团队都没有使用TDD的经验,因此可以假设时间的增加至少部分归因于学习,因此随着时间的流逝甚至会进一步减少,但是这项研究并未对此进行评估。

请注意,这项研究是关于TDD的,而您的问题是关于单元测试的,这是非常不同的事情,但这是我能找到的最接近的东西。


1
施加额外的约束会更有趣:没有可变状态,遵循SOLID,静态类型化,没有依赖null,对命令式有功能,代码协定,静态分析,自动重构,没有IoC容器(但DI)等。单元测试的数量会减少(但不会消失)。
2016年

24

做得好,即使不考虑捕获额外错误的好处,使用单元测试进行开发也可以更快。

事实是,我不够出色,无法在编译后立即使我的代码正常工作。当我编写/修改代码时,我必须运行代码以确保其能够达到我的预期。在一个项目中,这往往看起来像:

  1. 修改代码
  2. 编译应用
  3. 运行应用程序
  4. 登录应用程序
  5. 打开一个窗口
  6. 从该窗口中选择一个项目以打开另一个窗口
  7. 在该窗口中设置一些控件,然后单击一个按钮

当然,毕竟,通常需要花费一些往返时间才能真正使它正确。

现在,如果我正在使用单元测试该怎么办?然后该过程看起来更像:

  1. 编写测试
  2. 运行测试,确保它以预期的方式失败
  3. 写代码
  4. 再次运行测试,查看是否通过

与手动测试应用程序相比,这更容易,更快捷。我仍然必须手动运行该应用程序(因此,当我上交实际上根本不起作用的工作时,我看起来并不傻),但是在大多数情况下,我已经解决了一些问题,而我只是在这一点上进行验证。实际上,我通常使用保存时自动重新运行测试的程序来使此循环更加紧密。

但是,这取决于在易于测试的代码库中工作。许多项目,甚至那些具有许多测试的项目,都使编写测试变得困难。但是,如果您使用它,则可以拥有一个比自动测试更容易通过自动测试进行测试的代码库。另外,您可以保留自动化测试,并继续运行它们以防止回归。


1
使用nCrunch之类的工具可以减少第2步和第4步,从而使反馈循环更加紧密。
欣快的2016年

恕我直言,“我仍然必须手动运行该应用程序”是一项重要的观察。没有银弹。
书斋

20

尽管已经有很多答案,但它们有些重复,我想换个角度。单元测试是有价值的,当且仅当它们可以增加业务价值时。出于测试目的而进行的测试(琐碎或重言式的测试),或达到某种任意度量标准(例如代码覆盖率)的测试,都是精通货物的编程。

测试不仅需要花费大量的时间,而且维护成本也很高。他们必须与他们测试的代码保持同步,否则就一文不值。更不用说在每次更改时运行它们的时间成本。那不是破坏交易(或没有做真正必要的借口),但需要在成本效益分析中加以考虑。

因此,在决定是否(或以哪种类型)测试功能/方法时要问的问题是,问自己“我通过该测试所创造/保护的最终用户价值是多少?”。如果您无法回答这个问题,那么可能就不值得花费编写/维护该测试的费用。(或者你不明白的问题域,这是waaaay比缺乏测试更大的问题)。

http://rbcs-us.com/documents/Why-Most-Unit-Testing-is-Waste.pdf


1
我对BDD并不是很熟悉,但会猜测它的粒度比方法/函数级别的粒度略大,并且可能与用户值的联系不那么紧密。
贾里德·史密斯


9
“为了测试而进行的测试(琐碎或重言式的测试),或者达到某种任意度量标准(例如代码覆盖率),是一种易学的编程。” 如此真实,如此善道。以这样的方式进行测试,使您感觉像是一个很酷的坏蛋-将自己视为……间谍,精英运动员……不要像“政府部门”那样进行测试。你懂?
Fattie

2
@SteveJessop不同意,代码覆盖率(就度量而言)本质上是任意的:在机器指令级别通过非平凡程序的路径数(即计数的路径)将大于在其上的原子数。地球甚至可能是可见宇宙。这是不可测试的。因此,任何对“代码覆盖率”的主张都是某种启发式选择的任意阈值。程序员擅长以此类指标为代价,而以实际问题为代价。
贾里德·史密斯

2
我还要说的是,每当测试失败时,测试就大约(尽管不是精确地)提供业务价值,而解决方案是改进被测代码。因此,如果您使用的是TDD,则每次测试至少会自动发生一次。根据定义,重言式测试不会失败,因此是没有用的。对于“琐碎”的测试-在我职业生涯的早期就使用Java TCK,对于从零开始重新实现API可能失败的琐碎测试,我不再感到惊讶;-)但是商业价值在于几乎总是凭经验来预测,“任意”也是如此。
史蒂夫·杰索普

9

这取决于人员,以及要使用的代码的复杂性和形状。

对我来说,在大多数项目中,编写单元测试意味着我可以更快地完成工作25%。是的,甚至包括编写测试的时间。

因为事实是编写代码时软件没有完成。当您将其运送给客户并且客户对此感到满意时,它就完成了。到目前为止,单元测试是捕获大多数错误,隔离大多数错误以进行调试以及使人们确信代码良好的最有效方式。无论如何,您都必须做这些事情,所以请做好它们。


7
我认为值得一提,这是一项后天的技能。我看到很多人听到这样的说法,即TDD实际上甚至不是节省时间的预付款,从长远来看,它只是回报更快的时期。然后他们尝试了一天,这很痛苦,因为他们有0年的经验,读过0本书,没有练习,只是希望它能神奇地工作。TDD并没有让您成为更好的开发人员的秘密,您仍然需要练习,仍然需要思考,仍然需要做出良好的决策。
萨拉

1
@kai-+1。在尝试之前,我花了数周时间阅读有关TDD的信息。我读了所有我能找到的东西。我读书。我通读了所有著名的敏捷博客作为示例。我从头到尾阅读了xUnit测试模式。在最初的几周里,我花了两倍的时间。
Jules

2
我同意。TDD很难。心态很难。任何说“只是先编写测试”并声称免费的人都不知道该怎么做。这需要练习。
duffymo '16

@kai:出于类似原因,很多人都无法触摸输入。他们尝试了一次,一小时后仍然没有比以前更快的打字速度;-)
史蒂夫·杰索普

@SteveJessop我想这是一个非常整洁的比较。还是非常不健康,出去慢跑10分钟,精疲力尽,想知道为什么你不能在一小时内跑10英里。它确实说明了获得收益之前您需要如何工作。
萨拉

4

问题是,与未经测试的代码相比,编写经过单元测试的代码要花多少时间差?随着项目范围的扩大,时间差如何扩大?

随着项目寿命的增加,问题变得更加严重:因为每当添加新功能和/或重构现有实现时,都应重新测试以前测试过的内容,以确保其仍然有效。因此,对于一个长期(多年)的项目,您可能不仅需要测试功能,还需要对其进行100次以上的测试。因此,您可能会从自动测试中受益。但是,如果IMO是自动化的系统测试,而不是自动化的单元测试,那就足够了(甚至更好)。

第二个问题是,如果不及早发现错误,可能很难发现和修复错误。例如,如果系统中存在一个错误,并且在您进行最新更改之前我知道它运行良好,那么我将集中精力于您的最新更改,以了解它可能是如何引入该错误的。但是,如果在进行最新更改之前我不知道该系统正在运行(因为在进行最新更改之前未对系统进行正确的测试),则该错误可能在任何地方。

以上内容特别适用于深层代码,而不适用于浅层代码,例如,在新页面不太可能影响现有页面的情况下添加新网页。

结果,大量的错误逃逸到了生产环境中,我必须修复这些错误,然后又使我的其他项目受挫。

根据我的经验,这是不可接受的,因此您提出的问题是错误的。与其问测试是否可以使开发更快,还不如问什么会使开发更安全。

一个更好的问题可能是:

  • 单元测试是否是正确的测试,您需要避免产生的“大量错误”?
  • 是否还有其他建议或替代的质量控制/改进机制(除单元测试之外)?

学习是一个分为两个阶段的过程:学会做得足够好,然后学会更快地做。


3

需要考虑的某些方面,其他答案中未提及。

  • 额外收益/额外成本取决于编写单元测试的经验
    • 在我的第一个单元测试项目中,额外的费用翻了三倍,因为我必须学习很多东西,并且犯了很多错误。
    • 经过10年的tdd经验,我需要多25%的编码时间来提前编写测试。
  • 有了更多的tdd-moduls,仍然需要手动进行gui-gui测试和集成测试
  • tdd仅在从一开始就起作用。
    • 将tdd应用于现有的成长项目很昂贵/困难。但是您可以改为执行回归测试。
  • 自动化测试(单元测试和其他类型的测试)需要维护const才能使其正常运行。
    • 通过复制和粘贴创建测试可能会使testcode-maintanace变得昂贵。
    • 随着经验的增长,测试代码变得更加模块化,更易于维护。
  • 随着经验的增长,何时应该创建自动测试以及何时不应该创建自动测试。
    • 示例对简单的getter / setter / wrapper进行单元测试没有太大的好处
    • 我不通过gui编写自动化测试
    • 我注意可以对业务层进行测试

摘要

从tdd开始,只要您处于“时间受限的工作环境”中,就很难达到“收益大于成本”状态,尤其是当有“聪明的经理”告诉您“摆脱昂贵,无用的环境”时测试的东西”

注意:“单元测试”是指“隔离测试模块”。

注意:“回归测试”是指

  • 编写一些产生一些输出文本的代码。
  • 编写一些“回归测试”代码,以验证生成结果仍然相同。
  • 回归测试让您知道结果何时更改(可能还可以,也可以指示新错误)
  • “回归测试”的概念类似于批准测试
    • ...拍摄结果快照,并确认结果没有改变。

需要校对(测试的文学对等吗?)
JDługosz16年

3

像处理大多数任务的人一样,程序员低估了完成任务的实际时间。考虑到这一点,可以花10分钟编写一个测试,因为实际上可以花大量时间编写大量代码,而实际上,您会花时间在测试中输入相同的函数名称和参数。这是TDD方案。

不写测试,就像有一张信用卡。我们倾向于花费更多或编写更多代码。更多代码有更多错误。

我建议不要专注于应用程序的关键和复杂部分,而不必决定是否覆盖全部代码,而是在那里进行测试。在银行应用中,这可能是利息计算。发动机诊断工具可能具有复杂的校准协议。如果您一直在从事一个项目,那么您可能知道它是什么以及错误在哪里。

慢慢开始。在判断之前要流利一些。您可以随时停止。


3

程序员委员会提倡TDD和其他测试方法已有很长的历史,我不会回想他们的观点并同意他们的观点,但是这里有一些其他的考虑因素,应该引起一些注意:

  • 根据上下文的不同,测试同样不方便,高效。我正在开发Web软件,告诉我您是否具有测试整个UI的程序...现在我正在编程excel宏,是否应该在VBA中真正开发一个测试模块?
  • 编写和维护测试软件是一项真正的工作,从短期来看很重要(从长远来看会有所作为)。编写相关测试也是获得技能
  • 团队合作和单独工作并没有相同的测试要求,因为在团队中,您需要验证,理解和交流您未编写的代码。

我会说测试很好,但是请确保您尽早测试并测试增益在哪里。


1
“我真的应该为VBA开发一个测试模块吗?” 你该死的对。 rubberduckvba.com/Features#unitTesting
RubberDuck

有几个原因,因为它doen't适合我的需要不会用这个(我在最多几天的任务,锁定的环境,继任者不会打扰第三方)。好的评论虽然,语言本身并不是借口:)
亚瑟·哈维利切克

所有公平点@ArthurHavlicek。
RubberDuck

2
在VBA中编写测试仍然微不足道。是否具有某些单元测试框架具有的所有奇特功能?这很难,但是运行一个mainTest()调用所有测试模块的程序并不难。
enderland

1

TDD经常被忽视的好处是,测试可以作为一种安全措施,以确保您在进行更改时不会引入新的错误。

TDD方法最初无疑会耗费更多时间,但要点是,您将编写更少的代码,这意味着更少的出错。当然,您经常包括的所有这些花哨的内容都不会纳入代码库。

电影《剑鱼》中有一个场景,如果要发挥记忆力,黑客就不得不用枪顶着脑袋,并且要犯错误……否则会分散注意力。关键是,当您的代码中留有顶空并且您有时间在身边时,工作起来要容易得多,而不是几个月后客户就对您大吼大叫,而其他优先事项却受到挤压。

开发人员知道,以后修复错误的代价更高,但要立即解决。如果您每天可以得到500美元来编码现在的编码方式,或者如果您以TDD方式编写,则可以得到1000美元,那么您将使您成为第二笔报价的人。您越早停止将测试视为繁琐的工作并将其视为省钱的功能,您的生活就会越好。


您第一句话中的那件事称为回归测试

0

我可以与您的经验联系在一起-我们的代码库几乎没有测试,而且几乎无法测试。从字面上看,开发某些东西花了很多时间,而修复产品错误则花费了新功能的宝贵时间。

对于部分重写,我誓言要为所有核心功能编写测试。起初,它花费了相当长的时间,并且我的工作效率明显下降,但是后来我的工作效率却比以往任何时候都要好。

改进的部分原因是我减少了生产错误,从而减少了中断->在任何给定时间,我都有更好的专注力。

此外,独立测试和调试代码的能力确实值得-一套测试大大优于只能手动设置才能调试的系统,例如,启动应用程序并导航至屏幕并执行某些操作...也许几十次

但是请注意,一开始生产率会下降,因此开始学习一些时间压力还没有达到预期的项目的测试。另外,尝试在未开发的项目上启动它,对遗留代码进行单元测试非常困难,当您知道好的测试套件的外观时,它会有所帮助。


0

只是为了补充先前的答案:请记住测试本身并不是目的。进行测试的目的是使您的应用程序在不预期的上下文等环境中通过演化达到预期的性能。

因此,编写测试并不意味着证明实体所有端点的所有行为。这是一个常见错误。许多开发人员认为他们需要测试所有功能/对象/方法/属性/等。这导致高工作量以及大量不相关的代码和测试。这种方法在大型项目中很常见,在大型项目中,大多数开发人员并不了解整体行为,而只能看到他们的交互领域。

在处理稀疏资源和测试时,正确的方法非常明显并且具有常识,但是却没有正式形式化:首先 将测试开发资源投资于高级功能,然后逐步将其具体化。这意味着在某个时候,作为一个孤独的开发人员,您不仅会专注于单元测试,而且会专注于功能/集成/等。测试并根据您的时间资源,逐渐按照计划和考虑逐步进入主要的单一功能。高级测试将提供必要的信息,以解决低级/单一测试并根据您拥有的资源来计划测试开发策略。

例如,您想首先将处理链作为黑盒进行测试。如果您发现链中的某个成员由于未考虑某些极端条件的行为而失败,那么您可以编写测试,以保证该功能不仅在该成员上而且在其他成员上都保证。然后,您交付。对于下一个周期,您检测到有时网络会失败。因此,您可以在可能易受攻击的模块上编写解决此类问题的测试。等等。

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.