为什么单元测试失败被视为不好?


93

在某些组织中,显然,软件发布过程的一部分是使用单元测试,但是在任何时间点,所有单元测试都必须通过。例如,可能会有一些屏幕以绿色显示所有通过的单元测试-这应该很好。

就我个人而言,出于以下原因,我认为不应该这样:

  1. 它提出了这样的想法,即代码应该是完美的,并且不应该存在错误-在现实世界中,对于任何大小的程序,这肯定是不可能的。

  2. 考虑将失败的单元测试是不利的。或者肯定会提出很难修复的单元测试。

  3. 如果在任何时间点所有单元测试都通过了,那么在任何时间点都没有软件状态的大图。没有路线图/目标。

  4. 它阻止了在实现之前预先编写单元测试。

我什至建议即使发布单元测试失败的软件也不一定是坏事。至少您会知道该软件的某些方面存在局限性。

我在这里想念什么吗?为什么组织希望所有单元测试都能通过?这不是生活在梦想世界中吗?难道这实际上并不能阻止对代码的真正理解吗?


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

Answers:


270

这个问题包含恕我直言的几个误解,但我要重点关注的一个主要问题是,它不能区分本地开发分支,主干,暂存或发布分支。

在本地dev分支中,几乎随时都有可能在单元测试中失败。在后备箱中,这仅在某种程度上是可以接受的,但已经是一个强大的指标,可以尽快修复问题。请注意,后备箱中单元测试失败会干扰团队的其他成员,因为它们要求所有人检查是否不是他/她的最新更改引起了失败。

在登台或发布分支中,失败的测试为“红色警报”,表明将某些变更集从主干合并到发布分支后,某些变更集完全出了问题。

我什至建议即使发布单元测试失败的软件也不一定是坏事。

发行具有某些严重度低于某些严重性的错误的软件不一定是不好的。但是,这些已知的故障不应导致单元测试失败。否则,在每次单元测试运行之后,您将不得不查看20个失败的单元测试,并逐一检查该失败是否可以接受。这变得麻烦,容易出错,并且丢弃了单元测试自动化方面的很大一部分。

如果您确实对可接受的已知错误进行了测试,请使用单元测试工具的禁用/忽略功能(因此默认情况下,它们不会按需运行)。此外,向您的问题跟踪器添加一个低优先级票证,这样就不会忘记该问题。


18
我认为这是真正的答案。OP提到了“发布过程”和“某些屏幕[显示测试结果]”,这听起来像是构建服务器。发布与开发不同(请勿在生产中开发!);在开发人员中通过测试失败是很好的,就像TODO。当推送到构建服务器时,它们应该全部为绿色(“完成”)。
华宝

7
比最高投票者的答案好得多。它显示了对操作来源的理解,而没有向他们讲解一些理想的世界情况,承认了已知错误的可能性(并非为了解决某些罕见的极端情况而放弃了整个路线图),并解释了单元测试仅应明确在发布分支/进程中为绿色。
塞巴斯蒂安·范·登·布鲁克

5
@SebastiaanvandenBroek:感谢您的肯定答复。明确地说:恕我直言,失败的单元测试即使在后备箱中也很少见,因为这样的失败率太高会打扰整个团队,而不仅仅是造成失败的更改者。
布朗

4
我认为这里的问题是所有自动化测试都是单元测试。许多测试框架都具有标记预期失败的测试的能力(通常称为XFAIL)。(这不同于需要错误结果的测试。理想情况下,XFAIL测试会成功,但不会成功。)测试套件仍会通过这些失败。最常见的用例是仅在某些平台上失败的事情(在那些平台上只有XFAIL),但是使用该功能跟踪某些现在需要修复的工作也很合理。但是这些测试通常不是单元测试。
凯文·卡斯卡特

1
+1,尽管我建议对这句话稍加(粗体):“这变得麻烦,容易出错,使人们忽视测试套件中的故障(如噪音),并丢弃了单元测试自动化方面的很大一部分。
mtraceur

228

...所有单元测试都以绿色通过-这应该很好。

很好的。没有“理应如此”。

它提出了这样的想法,即代码应该是完美的,并且不应该存在错误-在现实世界中,对于任何大小的程序,这肯定是不可能的。

不会。它证明您已经测试了代码,并且可以进行到此为止。您的测试很可能不会涵盖所有情况。如果是这样,最终将在错误报告中出现任何错误,您将编写[失败]测试以重现问题,然后修复应用程序以使测试通过。

考虑将失败的单元测试是不利的。

失败或否定的测试对您的应用程序将接受和不接受的应用程序设置了严格的限制。我知道的大多数程序都会反对2月30日的“日期”。此外,开发人员(我们是创造性的类型)也不想破坏“他们的孩子”。对“幸福路径”案例的关注最终导致了脆弱的应用程序经常崩溃。

比较开发人员和测试人员的心态:

  • 一旦代码执行了他们想要的操作,开发人员就会停止。
  • 当测试人员无法再使代码中断时,它就会停止。

这些是截然不同的观点,许多开发人员难以调和。

或者肯定会提出很难修复的单元测试。

您不会编写测试来自己完成工作。您编写测试以确保您的代码正在执行应做的事情,更重要的是,在更改其内部实现,代码将继续执行应做的事情。

  • 调试“证明”,代码将你希望它是什么今天
  • 测试“证明”代码在一段时间后仍能完成您想要的工作

如果在任何时间点所有单元测试都通过了,那么在任何时间点都没有软件状态的大图。没有路线图/目标。

唯一的“图片”测试为您提供了快照,该快照可在代码被测试的时间点“起作用”。在那之后它如何演变则是另外一个故事。

它阻止了在实现之前预先编写单元测试。

那正是你应该做的。编写一个失败的测试(因为尚未测试的方法尚未实现),然后编写方法代码以使该方法正常工作,并因此通过测试。这几乎是测试驱动开发的关键。

我什至建议即使发布单元测试失败的软件也不一定是坏事。至少您会知道该软件的某些方面存在局限性。

通过破坏的测试发布代码意味着其功能的某些部分不再像以前那样工作。这可能是有意为之的,因为您已修复了错误或增强了功能(但随后应先更改测试以使其失败,然后对修复/增强进行编码,以使测试在此过程中进行)。更重要的是:我们都是人类,我们会犯错误。如果您破坏了代码,则应该破坏测试,而那些破坏的测试应设置警报铃声。

这不是生活在梦想世界中吗?

如果有的话,它是生活在现实世界中,承认开发商既不是无所不知,也不infallable,我们犯错误,我们需要一个安全网,以赶上我们,如果当我们乱了!
输入测试。

难道这实际上并不能阻止对代码的真正理解吗?

也许。您不一定需要了解某些东西的实现来为其编写测试(这是它们的重点)。测试定义了应用程序的行为和限制,并确保除非您有意更改它们,否则它们将保持不变。


7
@Tibos:禁用测试就像注释一个功能。您具有版本控制。用它。
凯文

6
@Kevin我不知道“使用它”的意思。我将测试标记为“已跳过”或“待定”或测试运行程序使用的任何约定,并将该skip标签提交给版本控制。
dcorking

4
@dcorking:我的意思是不要注释掉代码,将其删除。如果您以后决定需要它,请从版本控制中还原它。提交禁用的测试也没有什么不同。
凯文

4
“您的测试很可能不会涵盖所有情况。” 到目前为止,我要说的是,对于经过测试的每一个平凡的代码,您肯定都不会涵盖所有情况。
corsiKa

6
@Tibos单元测试的支持者说,从编写失败的测试到为其编写代码的周期时间应该很小(例如20分钟。有些人声称30秒)。如果您没有时间立即编写代码,则可能太复杂了。如果不是很复杂,请删除测试,因为如果再次添加删除的功能,可以重写测试。为什么不注释掉呢?你不知道该功能永远被再次添加,所以注释掉测试(或代码)就是噪音。
CJ丹尼斯

32

为什么单元测试失败被视为不好?

它们不是-测试驱动的开发基于失败测试的概念。未能通过单元测试来推动开发,而未能通过验收测试来推动故事发展。

你缺少的是背景 ; 单元测试在哪里允许失败?

通常的答案是,单元测试只能在私有沙箱中失败。

基本概念是这样的:在共享失败测试的环境中,需要花费更多的精力来了解对生产代码的更改是否引入了新的错误。零和非零之间的差异比N和非N之间的差异更容易检测和管理。

此外,保持共享代码的整洁意味着开发人员可以继续工作。当我合并您的代码,我不需要从我被支付来解决,以校准我的多少测试的认识问题转移情境应该被失败。如果共享代码通过了所有测试,那么当我合并更改时出现的任何故障都必须是代码与现有的干净基准之间相互作用的一部分。

同样,在上机过程中,新开发人员可以更快地投入生产,因为他们不需要花时间去发现哪些失败的测试是“可接受的”。

更准确地说:纪律是构建期间运行的测试必须通过。

尽我所知,禁用失败的测试没有什么不妥。

例如,在“持续集成”环境中,您将以高节奏共享代码。经常进行集成并不一定意味着您的更改必须准备好发布。各种各样的黑暗部署技术可以防止流量在准备就绪之前被释放到代码段中。

这些相同的技术也可以用于禁用失败的测试。

我在定点发布中进行的练习之一是处理具有许多失败测试的产品的开发。我们想到的答案仅仅是浏览套件,禁用失败的测试并记录每个测试。这使我们能够迅速达到所有启用的测试都通过的程度,并且管理层/目标捐赠者/金拥有者都可以看到我们为达到该点而进行了哪些交易,并且可以就清理与新工作做出明智的决定。

简而言之:除了将大量失败的测试留在运行套件中之外,还有其他技术可用于跟踪未完成的工作


我会说“没有...... 没有错有失败的测试,禁用 ”。
CJ丹尼斯

这一变化肯定澄清了含义。谢谢。
VoiceOfUnreason

26

有很多不错的答案,但我想补充一个我认为尚未很好涵盖的角度:进行测试的真正意义是什么。

没有单元测试来检查您的代码是否没有错误。

我认为这是主要的误解。如果这是他们的职责,那么您确实希望到处都会有失败的测试。但反而,

单元测试检查您的代码是否按照您的预期进行。

在极端情况下,可能包括检查已知的bug 固定的。关键是要控制您的代码库,并避免意外更改。进行更改后,可以正常进行测试,并且可以预期会破坏某些测试-您正在更改代码的行为。现在,新近通过的测试可以很好地说明您所做的更改。检查所有破损是否都符合您的更改要求。如果是这样,只需更新测试并继续。如果不是,那么,您的新代码肯定有问题,请在提交前回去修复它!

现在,上述所有条件只有在所有测试均为绿色时才能起作用,并给出强烈的肯定结果:这正是代码的工作方式。红色测试没有该属性。“这是此代码不执行的操作”很少是有用的信息。

验收测试可能是您想要的。

有验收测试之类的东西。您可能需要编写一组测试才能调用下一个里程碑。这些可以是红色的,因为这是设计的目的。但是它们与单元测试完全不同,既不能也不应该替换它们。


2
我曾经不得不用另一个图书馆代替。单元测试帮助我确保新代码对所有极端情况仍然一视同仁。
托尔比约恩Ravn的安徒生

24

我将其视为等同于破窗综合症的软件。

正常运行的测试告诉我,该代码具有特定的质量,并且代码所有者在乎它。

至于何时应该关注质量,这取决于您正在使用的源代码分支/存储库。开发代码很可能已经通过了坏的测试,表明正在进行的工作(希望如此!)。

对于实时系统,在分支机构/存储库上进行的损坏测试应立即设置警报铃声。如果损坏的测试被允许继续失败或被永久标记为“忽略”-则其数量会随着时间的推移而增加。如果不定期检查这些内容,则将有先例可以保留残破的测试。

在许多商店中,破坏性测试被视为贬义词,以至于是否可以提交破坏性代码受到限制


9
如果测试记录了系统的运行方式,则它们肯定应该一直通过-如果没有通过,则意味着不变性已损坏。但是,如果它们记录了系统应该采用的方式,则失败的测试也可以使用它们-只要您的单元测试框架支持将它们标记为“已知问题”的好方法,并且将它们与某个项目链接即可在您的问题跟踪器中。我认为这两种方法都有其优点。
a安

1
@Luaan是的,这确实是假设所有单元测试都是一样创建的。对于构建经理来说,根据测试的运行时间,脆弱程度以及其他各种条件,通过某些属性将测试切成薄片并切成小块,这并不少见。
罗比·迪

根据我自己的经验,这个答案很棒。一旦有人习惯于忽略一堆失败的测试,或在某些方面破坏最佳实践,请等待几个月,您将看到被忽略的测试百分比急剧增加,代码质量下降到“ hack-script”水平。而且很难使每个人都回想起这个过程。
USR-本地ΕΨΗΕΛΩΝ

11

这是潜在的逻辑谬误:

如果所有测试通过都很好,那么如果任何测试失败也必须是不好的。

单元测试,它IS当所有的测试都通过好。测试失败时,它也很好两者不必反对。

测试失败是您的工具在到达用户之前就发现的问题。这是在错误发布之前纠正错误的机会。那是一件好事。


有趣的思路。我看到这个问题的谬误更像是这样:“因为单元测试失败会很好,所以所有测试通过都会不好”。
布朗

尽管您的最后一段很不错,但问题似乎是对“在任何时候所有单元测试都必须通过”(如公认的答案所表明)和单元测试要点的误解。
迪克林

9

Phill W的答案很好。我不能代替它。

但是,我确实希望专注于可能已经造成混乱的另一部分。

在某些组织中,显然,软件发布过程的一部分是使用单元测试,但在任何时间点,所有单元测试都必须通过

“在任何时间”都夸大了您的案件。重要的是,实现某些更改之后开始实施另一个更改之前,单元测试必须通过。
这是您跟踪哪些更改导致错误发生的方法。如果单元测试实施更改25之后但实施更改26 之前开始失败,则您知道更改25导致了该错误。

当然,实施变更期间,单元测试可能会失败;tat很大程度上取决于变化的幅度。如果我要重新开发一项核心功能,而不仅仅是一项较小的调整,那么我可能会中断测试一段时间,直到完成新版本的逻辑。


这可能会导致团队规则冲突。我实际上在几周前遇到了这个问题:

  • 每次提交/推送都会导致构建。构建绝不能失败(如果失败或任何测试失败,则归咎于提交开发人员)。
  • 每个开发人员都希望在一天结束时推送他们的更改(即使不完整),因此团队负责人可以在早上进行代码审查。

无论哪种规则都可以。但是,这两个规则不能一起工作。如果我被分配了一项需要几天才能完成的重大更改,那么我将无法同时遵守这两个规则。除非我每天都对我的更改发表评论,并且仅在完成所有操作后才对它们进行注释,否则便会取消注释;这只是荒谬的工作。

在这种情况下,这里的问题不是单元测试没有目的。这是公司抱有不切实际的期望。他们的任意规则集不能涵盖所有情况,并且不遵守规则被盲目地认为是开发人员失败,而不是规则失败(在我的情况下是失败)。


3
单向这可以工作,是使用分支,使得开发者承诺,推动以特色不需要而不完全干净地建立分支机构,但承诺的核心分支不触发构建,应建立干净。
格温·埃文斯

1
强制推行不完整的更改是荒谬的,我看不出这样做的任何理由。为什么更改完成后不进行代码审查?
卡勒姆·布拉德伯里

好吧,对于一个人来说,这是一种快速的方法,可以确保代码不仅在开发人员的笔记本电脑/工作站上停止硬盘工作或丢失,还可以确保即使在工作过程中也可以提交代码,工作量有限。
Gwyn Evans '18

1
功能标志修复了明显的悖论。
RubberDuck

1
@Flater是的,也可以用于修改现有逻辑。
RubberDuck

6

如果您不修复所有单元测试,则可以迅速进入没人修复任何损坏的测试的状态。

  1. 不正确,因为通过单元测试未显示代码完美

  2. 提出同样难以测试的代码也是一种阻碍,从设计的角度来看这是很好的

  3. 代码覆盖可以为您提供帮助(尽管不是万能药)。同样,单元测试只是测试的一个方面-您也需要集成/验收测试。


6

给已经很好的答案加点...

但在任何时候,所有单元测试都必须通过

这表明对发布过程缺乏了解。测试失败可能表明TDD下的计划功能尚未实现;或者它可能指示已知的问题,该问题已计划在将来的发行版中进行修复;或仅仅是管理层认为这并不重要,因为客户不太可能注意到这一点。所有这些共享的关键是,管理层已对故障做出判断。

它提出了这样的想法,即代码应该是完美的,并且不应该存在错误-在现实世界中,对于任何大小的程序,这肯定是不可能的。

其他答案涵盖了测试的范围。

我不明白为什么您认为消除错误是一个缺点。如果您不希望(已尽其所能)交付经过检查的代码,那么它为什么还要工作在软件上?

如果在任何时间点所有单元测试都通过了,那么在任何时间点都没有软件状态的大图。没有路线图/目标。

为什么要有路线图?

单元测试最初会检查功能是否正常,但随后(作为回归测试)会检查您是否无意间破坏了任何内容。对于现有单元测试的所有功能,没有路线图。已知每个功能都可以工作(在测试范围内)。如果该代码完成,则没有路线图,因为不需要对其进行更多工作。

作为专业工程师,我们需要避免镀金的陷阱。业余爱好者有能力浪费时间在有用的东西周围修整。作为专业人员,我们需要交付产品。这意味着我们可以使某项工作正常进行,验证其是否有效,然后继续进行下一项工作。


6

它提出了这样的想法,即代码应该是完美的,并且不应该存在错误-在现实世界中,对于任何大小的程序,这肯定是不可能的。

不对。您为什么认为这不可能?这是适用程序的示例:

public class MyProgram {
  public boolean alwaysTrue() {
    return true;
  }

  @Test
  public void testAlwaysTrue() {
    assert(alwaysTrue() == true);
  }
}

考虑将失败的单元测试是不利的。或者肯定会提出很难修复的单元测试。

在这种情况下,它可能不是单元测试,但是如果很复杂则是集成测试

如果在任何时间点所有单元测试都通过了,那么在任何时间点都没有软件状态的大图。没有路线图/目标。

是的,它之所以称为单元测试,是因为它检查一小段代码。

它阻止了在实现之前预先编写单元测试。

开发者 阻止编写任何测试,如果他们不明白它的好处根据其性质(除非它们来自质量检查)


“开发人员会根据其性质阻止编写任何测试” –这完全是胡说八道。我在一家从事TDD和BDD开发人员的整个公司工作。
RubberDuck

@RubberDuck我试图回答一个有问题的“事实”,但我太夸张了。我将更新
user7294900

“如果X不了解Y的好处,就会阻止X进行Y”,这几乎适用于任何X和Y,因此该声明可能不是特别有用。解释编写测试的好处,特别是提前进行,可能会更有意义。
迪克林

2
“任何大小的程序都不可能”并不表示“无论大小如何,所有程序”,而是“任何重要的程序(长度不短)”,您尝试的反例不适用,因为它不是。一个重要而有用的程序。
本·福格特

@BenVoigt我不认为我会给出“重要程序”作为答案。
user7294900

4

它提倡这样的想法:代码应该是完美的,并且不应该存在错误

绝对不是。它提出了一个想法,即您的测试不应失败,仅此而已。假设进行测试(甚至很多测试)能说明“完美”或“无错误”的说法是谬论。确定您的测试应该是浅或深是编写好的测试的重要组成部分,也是为什么我们要分别区分测试类别(“单元”测试,集成测试,“黄瓜”中的“方案”等)的原因。

考虑将失败的单元测试是不利的。或者肯定会提出很难修复的单元测试。

在测试驱动的开发中,必须强制每个单元测试首先失败,然后再开始编码。出于这个原因,它被称为“红绿循环”(或“红绿重构循环”)。

  • 在测试失败的情况下,您将不知道该代码是否实际由测试进行了测试。两者可能根本不相关。
  • 通过更改代码以使测试完全由红色变为绿色,仅此而已,您可以确信自己的代码可以执行应做的事情,而无需执行更多操作(您可能永远不需要)。

如果在任何时间点所有单元测试都通过了,那么在任何时间点都没有软件状态的大图。没有路线图/目标。

测试更像是一个微型目标。在测试驱动的开发中,程序员将首先编写测试(单数),然后明确实现一些代码的目标。然后进行下一个测试,依此类推。

在编写代码之前,测试的功能不应完整。

如果正确完成操作,使用一种语言并且使用适合此方法的测试库,它实际上可以极大地加快开发速度,因为错误消息(异常/堆栈跟踪)可以将开发人员直接指向他需要执行工作的位置下一个。

它阻止了在实现之前预先编写单元测试。

我不认为这句话是正确的。理想情况下,编写测试应该是实现的一部分

我在这里想念什么吗?为什么组织希望所有单元测试都能通过?

因为组织期望测试与代码相关。编写成功的测试意味着您已经记录了应用程序的某些部分,并证明了该应用程序可以完成(测试)所说的操作。一无所有,一无所有。

同样,进行测试的很大一部分是“回归”。您希望能够自信地开发或重构新代码。进行大量的绿色测试可以使您做到这一点。

这从组织层面到心理层面。知道自己的错误很可能会被测试发现的开发人员将更加自由地为自己需要解决的问题提供智能,大胆的解决方案。另一方面,没有测试的开发人员会在一段时间后陷入僵局(由于恐惧),因为他不知道自己所做的更改是否会破坏应用程序的其余部分。

这不是生活在梦想世界中吗?

不能。使用测试驱动的应用程序是纯粹的快乐-除非您出于某种原因(例如“更多的努力”等)不喜欢该概念,我们可以在另一个问题中进行讨论。

难道这实际上并不能阻止对代码的真正理解吗?

绝对不会,为什么呢?

您会发现许多大型的开源项目(对于这些项目而言,“理解”和代码专有技术的管理是非常紧迫的主题),除了测试之外,这些项目实际上将测试用作软件的主要文档,还为应用程序/库的用户或开发人员提供了在语法上正确,真实,可行的示例。这通常很出色。

显然,编写糟糕的测试是不好的。但这与测试本身的功能无关。


3

(根据我的原始评论)

所需的功能和将来的目标之间存在差异。测试是针对必需功能的:它们是精确,正式,可执行的,并且如果失败,则该软件将无法正常工作。未来的目标可能不是精确或正式的,更不用说可执行了,因此最好使用自然语言,例如问题/错误跟踪器,文档,注释等。

作为练习,尝试用“编译器错误”(或“语法错误”,如果没有编译器)替换问题中的“单元测试”。显然,发行版不应该包含编译器错误,因为它将无法使用。但是,编译器错误和语法错误是开发人员在编写代码时的正常状态。错误只有在完成后才会消失。这正是应该推送代码的时间。现在,将本段中的“编译器错误”替换为“单元测试” :)


2

自动化测试的目的是告诉您何时尽早破坏了某些东西。工作流程看起来像这样:

  1. 做出改变
  2. 建立并测试您的更改(理想情况下自动进行)
  3. 如果测试失败,则表示您破坏了以前可以正常使用的功能
  4. 如果测试通过,则您应该确信您的更改没有引入新的回归(取决于测试范围)

如果您的测试已经失败,则第3步将无法有效执行-测试将失败,但是您不知道那是否意味着您不进行调查就破坏了某些东西。也许您可以计算失败测试的数量,但是随后的更改可能会修复一个错误并破坏另一个错误,或者测试可能由于其他原因而开始失败。这意味着您需要等待一段时间才能知道是否已损坏,直到解决了所有问题或对每个失败的测试进行了调查。

单元测试能够尽早发现新引入的错误的能力是自动化测试中最有价值的东西-缺陷被发现的时间越长,修复的成本就越高。

它促进的想法,代码应该是完美的,没有错误应该存在
这是想出单元测试将失败有抑制作用

测试的东西,不工作不告诉你任何东西-写东西,单元测试的工作,或者你即将解决。这并不意味着您的软件没有缺陷,而是意味着您先前编写的单元测试中的任何缺陷都没有再次出现。

它阻止编写单元测试

如果它对您有用,那么请提前编写测试,直到通过后再将它们检入主/中继中。

如果在任何时间点所有单元测试都通过了,那么在任何时间点都没有软件状态的大图。没有路线图/目标。

单元测试不是用于设置路线图/目标的,也许可以使用积压的替代方法?如果您所有的测试都通过了,那么“大局”就是您的软件没有损坏(如果您的测试范围很好)。做得好!


2

现有的答案当然是好的,但是我还没有看到有人在这个问题上解决过这种基本的误解:

在任何时候,所有单元测试都必须通过

不可以。这肯定不是事实。在我开发软件时,NCrunch通常是棕色(构建失败)或红色(测试失败)。

当我准备将提交推送到源控制服务器时,NCrunch需要变为绿色(所有测试均通过),因为那时其他人可能会依赖我的代码。

这也涉及创建新测试的主题:测试应声明代码的逻辑和行为。边界条件,故障条件等。当我编写新的测试时,我尝试在代码中识别这些“热点”。

单元测试记录了我如何期望代码被调用-前提条件,期望的输出等。

如果测试随着更改而中断,我需要确定代码还是测试错误。


附带一提,单元测试有时与“测试驱动开发”并驾齐驱。TDD的原则之一是坏的测试是您的指南。如果测试失败,则需要修复代码以使测试通过。这是本周早些时候的具体示例:

背景:我编写并现在支持我们的开发人员使用的用于验证Oracle查询的库。我们进行的测试断言该查询符合某个期望值,这使情况变得很重要(在Oracle中不重要),并且只要无效查询完全符合期望值,就可以适当地批准无效查询。

相反,我的库使用Antlr和Oracle 12c语法解析查询,然后将各种断言包装在语法树本身上。诸如此类,它是有效的(没有引发解析错误),其所有参数均由参数集合满足,数据读取器读取的所有预期列均出现在查询中,等等。所有这些都是已转至在不同的时间生产。

周一,我的一位工程师向我发送了一个查询,该查询在周末失败了(或者应该在失败时成功了)。我的图书馆说语法不错,但是当服务器尝试运行它时,它就崩溃了。当他查看查询时,很明显为什么:

UPDATE my_table(
SET column_1 = 'MyValue'
WHERE id_column = 123;

我加载了该项目,并添加了一个断言该查询无效的单元测试。显然,测试失败了。

接下来,我调试失败测试,通过代码加强,我希望它抛出异常,并想通了,ANTLR的在开放的括号引发错误,只是没有在某种程度上前面的代码期待。我修改了代码,确认测试现在是绿色的(通过),并且没有其他人在该过程中出现问题,已提交并已推送。

这大概花费了20分钟,在此过程中,我实际上对库进行了重大改进,因为它现在支持以前被忽略的所有错误。如果我没有对该库进行单元测试,那么研究和解决该问题可能要花几个小时。


0

我不认为从先前的答案中得出的一点是,内部测试和外部测试之间存在差异(并且我认为许多项目对区分这两者的注意不够)。内部测试测试某些内部组件是否按预期方式工作;外部测试表明,该系统总体上按预期运行。当然,组件中的故障很可能不会导致系统故障(也许某个组件的某个功能未被系统使用,或者系统可能会从故障中恢复)。零件)。不会导致系统故障的组件故障不应阻止您释放。

我看到过太多的内部组件测试使项目瘫痪了。每次尝试实现性能改进时,都会破坏数十个测试,因为您在更改组件的行为时并未实际更改系统的外部可见行为。这导致整个项目缺乏敏捷性。我相信,与对内部组件测试的投资相比,对外部系统测试的投资通常具有更好的收益,尤其是当您谈论的是非常低级的组件时。

当您认为失败的单元测试并不重要时,我想知道这是否是您要考虑的?也许您应该评估单元测试的价值,并抛弃那些引起更多麻烦的单元测试,同时将更多精力放在验证应用程序外部可见行为的测试上。


我认为您所描述的“外部测试”通常在其他地方被描述为“集成”测试。
GalacticCowboy

是的,但是我遇到过术语上的差异。对于某些人来说,集成测试更多地是关于已部署的软件/硬件/网络配置,而我所说的是您正在开发的软件的外部行为。
迈克尔·凯

0

“但是任何时候所有单元测试都必须通过”

如果这是您公司的态度,那就是一个问题。在某个特定时间,即当我们声明代码已准备好移至下一个环境时,所有单元测试都应通过。但是在开发过程中,我们通常应该期望许多单元测试会失败。

没有一个理性的人期望程序员在第一次尝试时就能使他的工作变得完美。我们可以合理预期的是,他将继续努力直到没有已知问题为止。

“想出将失败的单元测试是令人沮丧的。或者肯定会提出很难修复的单元测试。” 如果组织中的某人认为他们不应该提及可能的测试,因为它可能会失败并导致他们进行更多的工作以对其进行修复,则该人完全没有资格胜任其工作。这是一种灾难性的态度。您是否想请一位医生说:“当我做手术时,我故意不检查针迹是否正确,因为如果发现针迹不对,我就必须回去重新做一遍,然后会减慢操作速度吗?”?

如果团队对在代码投入生产之前就发现错误的程序员怀有敌意,那么您对该团队的态度就会产生真正的问题。如果管理层惩罚那些发现错误并降低交付速度的程序员,那么您的公司很有可能破产。

是的,有时有些理性的人说:“我们快到了最后期限,这是一个小问题,现在不值得花时间修复它,这是不值得的。” 但是,如果您不知道,就无法理性地做出该决定。冷静地检查错误列表并分配优先级和时间表以修复它们是合理的。故意使自己对问题一无所知,所以您不必做出此决定是愚蠢的。您是否认为客户会因为您不想知道而无法找到?


-7

这是确认偏见的一个具体示例,其中人们倾向于寻找可以确认其现有信念的信息。

2,4,6游戏就是这种情况的一个著名例子。

  • 我脑子里有一个规则,即三个数字中的任何一个都会通过或失败,
  • 2,4,6是通过
  • 您可能会列出三个数字的集合,我将告诉您它们是否通过。

大多数人会选择一条规则,说“第一和第二个数字之间的距离与第二个和第三个数字之间的距离相同”。

他们将测试一些数字:

  • 4 8 8 通过
  • 20、40、60?通过
  • 2,1004,2006?通过

他们说:“是的,每一次观察都证实了我的假设,这一定是真的。” 并向发出谜语的人宣布他们的规则。

但是他们从来没有收到过任何三个数字中的任何一个“失败”。对于他们实际拥有的所有信息,规则可能只是“三个数字必须是数字”。

规则实际上只是数字按升序排列。人们通常只有在测试失败的情况下才能正确理解这个谜语。大多数人通过选择一个更具体的规则并仅测试符合此特定规则的数字来弄错它。

至于为什么人们会倾向于确认偏见,并可能认为单元测试失败是一个问题的证据,有许多心理学家可以比我更好地解释确认偏见,这基本上归结为人们不喜欢错了,而努力去真正地尝试证明自己错了。


2
它与问题有什么关系?根据定义,失败的单元测试问题的证据。
Frax

1
绝对可以进行要求被测系统进入故障模式的单元测试。这与从未看到测试失败不一样。这也是为什么将TDD指定为“
红色
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.