即使我有100%的代码覆盖率,我的代码仍然可以包含哪些类型的错误?

时间:2009-02-17 07:34:56

标签: unit-testing code-coverage

即使我有100%的代码覆盖率,我的代码仍会包含哪些类型的错误?我正在寻找具体的例子或链接到这些错误的具体例子。

21 个答案:

答案 0 :(得分:37)

拥有100%的代码覆盖率并不像人们想象的那么好。考虑一个例子:

double Foo(double a, double b)
{
    return a / b;
}

即使是单个单元测试也会将此方法的代码覆盖率提高到100%,但上述单元测试不会告诉我们哪些代码正在运行,哪些代码没有。这可能是一个非常有效的代码,但是没有测试边缘条件(例如b0.0时)单元测试最多也是不确定的。

代码覆盖率只告诉我们单元测试执行的是什么,而不是它是否正确执行。这是一个重要的区别。仅仅因为一行代码由单元测试执行,并不一定意味着该行代码按预期工作。

听取this进行有趣的讨论。

答案 1 :(得分:11)

代码覆盖率并不意味着您的代码在任何方面都没有错误。这是对您的测试用例覆盖源代码库的程度的估计。 100%的代码覆盖率意味着每行代码都经过测试,但程序的每个状态肯定都没有。在这个领域正在进行研究,我认为它被称为有限状态建模,但它实际上是一种试图探索程序的每个状态的蛮力方式。

做同样事情的更优雅的方式被称为抽象解释。 MSR(微软研究院)基于抽象解释发布了一个名为CodeContracts的东西。查看Pex,他们非常强调切割器测试应用程序运行时行为的方法

我可以编写一个非常好的测试,这将给我很好的报道,但不能保证该测试将探索我的程序可能具有的所有状态。这是编写非常好的测试的问题,这很难。

代码覆盖率并不意味着良好的测试

答案 2 :(得分:8)

呃?我猜是什么样的普通逻辑错误?内存损坏,缓冲区溢出,普通旧错误代码,分配 - 而不是测试,列表继续。覆盖范围仅限于此,它可以让您知道所有代码路径都已执行,而不是它们是正确的。

答案 3 :(得分:6)

正如我还没有看到它提到的那样,我想添加一个代码覆盖率的线程告诉你代码的哪些部分是无bug的。

它只会告诉您代码的哪些部分保证未经测试。

答案 4 :(得分:5)

<强> 1。 “数据空间”问题

你的(坏)代码:

void f(int n, int increment)
{
  while(n < 500)
  {
    cout << n;
    n += increment;
  }
}

你的考试:

f(200,100);

现实世界中使用的错误:

f(200,0);

我的观点:您的测试可能涵盖代码的100%,但它不会(通常)覆盖所有可能的输入数据空间,即所有可能的输入值的集合。

<强> 2。根据自己的错误进行测试

另一个经典的例子是你在设计中做出错误的决定,并根据你自己的错误决定测试你的代码。

E.g。规范文档说“打印所有素数到 n ”并打印所有素数到 n 不包括 n < / em>的。你的单元测试测试你错误的想法。

第3。未定义的行为

使用未初始化变量的值,导致无效的内存访问等,并且您的代码具有未定义的行为(使用C ++或任何其他考虑“未定义行为”的语言)。有时它会通过你的测试,但它会在现实世界中崩溃。

...

答案 5 :(得分:3)

始终存在运行时异常:内存填满,数据库或其他未关闭的连接等...

答案 6 :(得分:3)

  

可在我的机器上运行

很多东西在本地机器上运行良好,我们无法确保在Staging / Production上工作。代码覆盖范围可能不包含此内容。

答案 7 :(得分:3)

在最近的IEEE软件论文“两个错误和无错误的软件:忏悔”中,罗伯特·格拉斯认为,在“现实世界”中,他所谓的缺失逻辑或组合学(不能使用代码覆盖工具而不是逻辑错误(可以)。

换句话说,即使有100%的代码覆盖率,您仍然面临遇到这类错误的风险。你可以做的最好的事情是 - 你猜对了 - 做更多的代码审查。

该论文的引用是here,我发现了一个粗略的摘要here

答案 8 :(得分:3)

代码覆盖率通常只会告诉您覆盖了函数中有多少分支。它通常不报告可以在函数调用之间采取的各种路径。程序中的许多错误发生是因为从一种方法切换到另一种方法是错误的,而不是因为方法本身包含错误。此表单的所有错误仍然可以100%代码覆盖率存在。

答案 9 :(得分:3)

如果你的测试包含错误,或者你正在测试错误的东西,那么代码覆盖并不意味着什么。

作为相关切线;我想提醒你,我可以简单地构造一个满足以下伪代码测试的O(1)方法:

sorted = sort(2,1,6,4,3,1,6,2);

for element in sorted {
  if (is_defined(previousElement)) {
    assert(element >= previousElement);
  }

  previousElement = element;
}
对Jon Skeet的奖励业绩,他指出了我正在考虑的漏洞

答案 10 :(得分:3)

请考虑以下代码:

int add(int a, int b)
{
  return a + b;
}

此代码可能无法实现某些必要的功能(即不符合最终用户要求):“100%覆盖率”不一定测试/检测应该实现但不实现的功能。

此代码可用于某些但不是所有输入数据范围(例如,当a和b都非常大时)。

答案 11 :(得分:2)

测试中的错误:)

答案 12 :(得分:1)

参数验证,又名。空检查。如果您接受任何外部输入并将它们传递给函数但从不检查它们是否有效/ null,那么您可以实现100%覆盖率,但如果以某种方式将null传递给函数,您仍将获得NullReferenceException,因为这是您的数据库给出的你。

也算术溢出,比如

int result = int.MAXVALUE + int.MAXVALUE;

代码覆盖率仅涵盖现有代码,它无法指出应添加更多代码的位置。

答案 13 :(得分:1)

好吧,如果你的测试没有测试覆盖的代码中发生的事情。 如果您使用此方法向属性添加数字,例如:

public void AddTo(int i)
{
NumberA += i;
NumberB -= i;
}

如果你的测试只检查NumberA属性而不是NumberB,那么你将有100%的覆盖率,测试通过,但是NumberB仍然会包含错误。

结论:100%的单元测试不能保证代码没有错误。

答案 14 :(得分:1)

我不了解其他任何人,但我们没有接近100%的覆盖范围。没有我们的“这应该永远不会发生”CATCH在我们的测试中被运用(好吧,有时他们会这样做,但是代码得到修复,所以他们不再有!)。我担心我不担心永远不会发生CATCH中的语法/逻辑错误

答案 15 :(得分:1)

您的产品在技术上可能是正确的,但不能满足客户的需求。

答案 16 :(得分:1)

仅供参考,Microsoft Pex试图通过探索您的代码并找到“边缘”案例(如除零,溢出等)来提供帮助。

此工具是VS2010的一部分,但您可以在VS2008中安装技术预览版。非常值得注意的是,该工具找到了它找到的东西,但是,IME,它仍然不会让你一直“防弹”。

答案 17 :(得分:0)

执行100%的代码覆盖率,即100%指令,100%输入和输出域,100%路径,100%无论您怎么想,您的代码中仍然可能存在错误: 缺少功能

答案 18 :(得分:0)

代码覆盖率并不重要。重要的是是否涵盖了影响行为的所有(或大多数)参数值。

例如,考虑一个典型的compareTo方法(在java中,但适用于大多数语言):

//Return Negative, 0 or positive depending on x is <, = or > y
int compareTo(int x, int y) {
   return x-y;
}

只要您对compareTo(0,0)进行测试,就可以获得代码覆盖率。但是,您需要至少3个测试用例(对于3个结果)。它仍然没有bug。添加测试以支付异常/错误条件也是值得的。在上述情况下,如果您尝试compareTo(10, Integer.MAX_INT),则会失败。

底线:尝试根据行为将输入分区为不相交的集合,测试每组中至少一个输入。这将在真正意义上添加更多 coverage

同时检查QuickCheck等工具(如果适用于您的语言)。

答案 19 :(得分:0)

正如在这里的许多答案中所提到的,你可以拥有100%的代码覆盖率并且仍然存在错误。

最重要的是,你可以有0个错误,但代码中的逻辑可能不正确(不符合要求)。代码覆盖率,或100%无错误无法帮助您完成。

典型的企业软件开发实践可能如下:

  1. 有明确的功能说明书
  2. 制定针对(1)编写并经过同行评审的测试计划
  3. 针对(2)编写测试用例并让他们进行同行评审
  4. 根据功能规范编写代码并进行同行评审
  5. 针对测试用例测试代码
  6. 进行代码覆盖率分析并编写更多测试用例以实现良好的覆盖率。
  7. 请注意,我说“好”而不是“100%”。 100%的覆盖率可能并不总是可行的 - 在这种情况下,你的能量最好用于实现代码的正确性,而不是覆盖一些不起眼的分支。在上面的步骤1到步骤5中,不同类型的事情可能会出错:错误的想法,错误的规范,错误的测试,错误的代码,错误的测试执行......底线是,单独的步骤6不是最重要的一步这个过程。

    错误代码的具体示例,没有任何错误并且具有100%覆盖率:

    /**
     * Returns the duration in milliseconds
     */
    int getDuration() {
        return end - start;
    }
    
    // test:
    
    start = 0;
    end = 1;
    assertEquals(1, getDuration()); // yay!
    
    // but the requirement was:
    // The interface should have a method for returning the duration in *seconds*.
    

答案 20 :(得分:0)

几乎任何事情。

您是否阅读过Code Complete? (因为StackOverflow说你真的should。)在第22章中,它说“100%语句覆盖率是一个好的开始,但这还不够”。本章的其余部分解释了如何确定要添加的其他测试。这是一个简短的品酒师。

  • 结构化基础测试数据流测试意味着测试程序中的每条逻辑路径。根据A和B的值,下面的设计代码有四条路径。通过仅测试四条路径中的两条,可能f=1:g=1/ff=0:g=f+1,可以实现100%的语句覆盖率。但是f=0:g=1/f会给出零除错误。您必须考虑 if 语句和 while for 循环(循环体可能永远不会被执行)和选择的每个分支切换语句。

      

    If A Then
      f = 1
      Else
      f = 0
      End If
      If B Then
      g = f + 1
      Else
      g = f / 0
      End If

  • 错误猜测 - 通知猜测通常会导致错误的输入类型。例如边界条件(关闭一个错误),无效数据,非常大的值,非常小的值,零,空值,空集合。

即使如此,您的要求中也可能出现错误,测试中出现错误等 - 正如其他人所提到的那样。