你怎么知道你的单元测试是否正确?

时间:2009-02-18 15:25:22

标签: unit-testing tdd

我在职业生涯的不同阶段只进行过小型单元测试。每当我再次开始潜入它时,总是让我感到麻烦,如何证明我的测试是正确的。我如何判断我的单元测试中没有错误?通常我最终运行应用程序,证明它有效,然后使用单元测试作为一种回归测试。建议的方法是什么和/或您对此问题的处理方法是什么?

编辑:我也意识到你可以编写易于理解的小型粒度单元测试。但是,如果你认为小巧,精细的代码是完美无缺的,那么你可以编写小型,精细的程序而不需要进行单元测试。

Edit2:对于参数“单元测试是为了确保你的更改不会破坏任何东西”和“只有在测试与代码具有完全相同的缺陷时才会发生这种情况”,如果测试过度适用怎么办?通过错误的测试可以传递好的和坏的代码。我的主要问题是单元测试有什么用处,因为如果您的测试存在缺陷,您无法真正提高您对代码的信心,无法真正证明您的重构有效,并且无法真正证明您符合规范?< / p>

16 个答案:

答案 0 :(得分:15)

单元测试应表达您正在测试的任何“合同”。它或多或少是放入代码的单位规范。因此,根据规范,单元测试是否“正确”应该或多或少显而易见。

但我不会太担心单元测试的“正确性”。它们是软件的一部分,因此它们也可能是不正确的。单元测试的重点 - 来自我的POV - 是他们确保软件的“合同”不会被意外打破。这就是使单元测试如此有价值的原因:您可以在软件中进行挖掘,重构某些部分,更改其他部分的算法,并且您的单元测试会告诉您是否有任何损坏。即使不正确的单元测试也会告诉你。

如果您的单元测试中存在错误,您会发现 - 因为单元测试失败而测试的代码证明是正确的。那么,修复单元测试。没什么大不了的。

答案 1 :(得分:7)

嗯,Dijkstra有名的说:

  

“测试显示存在,而不是   缺少错误“

IOW,你会如何编写函数add(int,int)的单元测试?

IOW,这是一个艰难的。

答案 2 :(得分:7)

有两种方法可以帮助确保单元测试的正确性:

  • TDD:首先编写测试,然后编写要测试的代码。这意味着你看到他们失败了。如果您知道它至少检测到某些类的错误(例如“我还没有在我想要测试的函数中实现任何功能”),那么您就知道它并非完全没用。它可能仍会让其他一些错误消失,但我们知道测试不是完全错误。
  • 进行大量测试。如果一个测试允许一些错误滑过,它们很可能会导致错误进一步下行,导致其他测试失败。当你注意到并修复有问题的代码时,你就有机会检查为什么第一个测试没有按预期捕获错误。

最后,当然,保持单元测试非常简单,以至于它们不太可能包含错误。

答案 3 :(得分:3)

对于这个问题,您的代码必须以一种巧合导致测试通过的方式出错。这最近发生在我身上,在那里我检查一个给定的条件(a)导致一个方法失败。测试通过(即方法失败),但是它通过了,因为另一个条件(b)导致了失败。仔细编写测试,并确保单元测试测试一件事。

一般来说,不能编写测试来证明代码没有错误。他们是朝着正确方向迈出的一步。

答案 4 :(得分:3)

我有同样的问题,并且已经阅读了评论,这是我现在的想法(适当归功于之前的答案):

我认为问题可能是我们都采用了单元测试的表面目的 - 证明代码是正确的 - 并将这个目的应用于测试本身。除了单元测试的目的不是为了证明代码是正确的之外,这很好。

与所有重要的努力一样,你永远不能百分百肯定。单元测试的正确目的是减少错误,而不是消除它们。最具体的是,正如其他人所指出的那样,当您稍后进行更改时,可能会意外破坏某些内容。单元测试只是减少错误的一种工具,当然不应该是唯一的。理想情况下,您将单元测试与代码审查和可靠的质量保证相结合,以便将错误减少到可容忍的水平。

单元测试比你的代码简单得多;如果您的代码执行任何重要操作,则无法使代码像单元测试一样简单。如果你编写的“小的,精细的”代码很容易证明是正确的,那么你的代码将由大量的小函数组成,你仍然需要确定它们是否都能正常协同工作。

由于单元测试不可避免地比他们测试的代码更简单,因此他们不太可能有bug。即使你的一些单元测试有问题,总的来说它们仍然会提高主代码库的质量。 (如果你的单元测试是如此错误,以至于这不是真的,那么你的主要代码库也可能是一个热闹的堆栈,而且你完全搞砸了。我认为我们都在假设一个基本的能力水平。)

如果您想要应用第二级单元测试来证明您的单元测试是正确的,那么您可以这样做,但它会受到收益递减的影响。用虚假的数字来看它:

假设单元测试可将生产错误数量减少50%。然后编写元单元测试(单元测试以查找单元测试中的错误)。假设这会发现您的单元测试出现问题,将生产错误率降低到40%。但编写元单元测试需要花费80%的时间来编写单元测试。 80%的努力只能获得另外20%的收益。也许编写meta-meta-unit测试会给你另外5个百分点,但现在再次花费80%的时间来编写元单元测试,因此64%的编写单元测试的努力(给出了你50%)你还有5%。即使有更多的自由数字,也不是花费时间的有效方式。

在这种情况下,很明显,超过编写单元测试的点是不值得的。

答案 5 :(得分:2)

我想首先编写测试(在编写代码之前)是确保测试有效的一种很好的方法。

或者您可以为您的单元测试编写测试......:P

答案 6 :(得分:2)

  1. 单元测试代码的复杂度比实际代码(或应该更少)(通常少于数量级)
  2. 您在单元测试中编写与实际代码中的错误完全匹配的错误的机会远远少于编写实际代码中的错误(如果您编写的单元测试中的错误不匹配)真实代码中的错误应该会失败)。当然,如果您在实际代码中做出了错误的假设,那么您可能会再次做出相同的假设 - 尽管单元测试的思维模式仍应该减少甚至是这种情况
  3. 正如已经提到的那样,当你编写单元测试时,你已经(或者应该)拥有不同的思维模式。在编写真实代码时,您正在考虑“我该如何解决这个问题”。在编写单元测试时,您正在考虑“我如何测试可能会破坏的每一种方式”
  4. 正如其他人已经说过的,这不是关于你是否可以证明单元测试是正确和完整的(尽管测试代码几乎肯定会更容易),因为它将错误数量减少到非常低的数量 - 并且把它推得越来越低。

    当然,必须要有一个点,你对自己的单位测试的信心足以依赖它们 - 例如在进行重构时。达到这一点通常只是经验和直觉的一种情况(虽然有代码覆盖工具可以提供帮助)。

答案 7 :(得分:2)

首先让我首先说单元测试不仅仅是测试。它更多的是关于应用程序的设计。要查看此操作,您应该在显示单元测试时将相机与显示器放在一起并记录编码。您将意识到在编写单元测试时您正在做出很多设计决策。

如何知道我的单元测试是否良好?

您无法测试逻辑部分时段!如果你的代码说2 + 2 = 5并且你的测试确保2 + 2 = 5那么对你来说2 + 2是5.要编写好的单元测试你必须很好地理解你正在使用的域。当你知道你想要完成什么时,你将编写好的测试和良好的代码来完成它。如果你有很多单元测试并且你的假设是错误的,那么迟早你会发现你的错误。

答案 8 :(得分:1)

你不说。一般来说,测试会比他们测试的代码更简单,因此他们的想法很简单,就是他们 可能会比实际代码更容易出错。

答案 9 :(得分:1)

这是每个使用单元测试的人的错误。如果我不得不给你一个简短的回答,我会告诉你要始终相信你的单元测试。但我会说,这应该以你以前的经验为后盾:

  • 您是否有任何由手动测试报告的缺陷且单元测试没有捕获(尽管它有责任),因为您的测试中存在错误?
  • 过去你有过假阴性吗?
  • 您的单元测试是否足够简单?
  • 您是在新代码之前编写它们还是至少并行编写它们?

答案 10 :(得分:1)

你不能证明测试是正确的,如果你正在尝试,你就是在做错了。

单元测试是第一个屏幕 - 烟雾测试 - 就像所有自动化测试一样。他们主要是告诉你,你在之后做出的改变是否会破坏内容。即使在100%覆盖率下,它们也不是质量证明。

该指标确实让管理层感觉更好,有时这本身就很有用!

答案 11 :(得分:1)

这是TDD的优势之一:代码可以作为测试的测试。

您可能会犯同等错误,但根据我的经验,这种情况并不常见。

但是我确实遇到了这样一种情况:我写了一个只能让它通过的测试,这告诉我测试是错误的。

当我第一次学习单元测试时,在我做TDD之前,我还会在编写测试后故意破坏代码,以确保它像我预期的那样失败。当我不知道测试被打破时。

我真的很喜欢Bob Martin对此的描述相当于复式簿记。

答案 12 :(得分:1)

Dominic提到“为了这个问题,你的代码必须以一种巧合导致你的测试通过的方式出错。”您可以使用一种技术来查看这是否是一个问题mutation testing。它会对您的代码进行更改,并查看它是否会导致单元测试失败。如果没有,那么它可能表明测试不是100%彻底的区域。

答案 13 :(得分:1)

单元测试是您的要求具体化。我不了解你,但我喜欢在开始编码之前指定要求(TDD)。通过编写它们并像对待任何其他代码一样处理它们,您将开始有信心在不破坏旧功能的情况下引入新功能。为确保您需要所有代码并且测试实际测试代码,我使用pitest(其他语言存在其他变异测试变体)。对我来说,未经测试的代码是错误的代码,但它可能是干净的。

如果测试测试复杂的代码并且本身很复杂,我经常为我的测试编写测试(example)。

答案 14 :(得分:0)

如上所述,最好的方法是在实际代码之前编写测试。如果适用,还可以找到您的测试代码的实际例子(数学公式或类似),并将单元测试和预期输出与之比较。

答案 15 :(得分:0)

编辑:我也意识到你可以编写易于理解的小型粒度单元测试。但是,如果你认为小巧,精细的代码是完美无瑕的,你可以编写小型,精细的程序而不需要进行单元测试。

单元测试的想法是测试最细微的东西,然后将测试堆叠在一起以证明更大的情况。如果您正在编写大型测试,那么您会失去一些好处,尽管编写更大的测试可能会更快。