【问题标题】:What type of errors could my code still contain even if I have 100% code coverage?即使我有 100% 的代码覆盖率,我的代码仍可能包含哪些类型的错误?
【发布时间】:2010-10-08 00:50:24
【问题描述】:

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

【问题讨论】:

    标签: unit-testing code-coverage


    【解决方案1】:

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

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

    即使是单个单元测试也会将此方法的代码覆盖率提高到 100%,但所述单元测试不会告诉我们哪些代码在工作,哪些代码不工作。这可能是一个完全有效的代码,但没有测试边缘条件(例如当 b0.0 时)单元测试充其量是不确定的。

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

    收听this 进行有趣的讨论。

    【讨论】:

    • 在哪些平台上除法双打会导致“不正确”的行为?如果给定 0 或 NaN 输入,大多数会正确返回 IEEE Infinity 或 NaN,而其他的则具有异常机制,这只是要涵盖的另一个代码路径。
    • @Pete Kirkham:这并不意味着划分是正确的做法。也许你想要一个/(b * b)。谁知道?
    【解决方案2】:

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

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

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

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

    【讨论】:

      【解决方案3】:

      嗯?任何一种普通的逻辑错误,我猜?内存损坏、缓冲区溢出、普通的旧错误代码、赋值而不是测试,不胜枚举。覆盖范围仅此而已,它让您知道所有代码路径都已执行,而不是它们是否正确。

      【讨论】:

        【解决方案4】:

        我还没有看到它提到过,我想添加这个线程,代码覆盖率确实告诉你代码的哪一部分是没有错误的。

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

        【讨论】:

          【解决方案5】:

          1. “数据空间”问题

          你的(坏的)代码:

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

          你的测试:

          f(200,100);
          

          实际使用中的错误:

          f(200,0);
          

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

          2。针对自己的错误进行测试

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

          例如规范文档说“打印所有素数到 n”并且你打印所有素数到 n不包括 n。你的单元测试会测试你的错误想法。

          3.未定义的行为

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

          ...

          【讨论】:

            【解决方案6】:

            总是会出现运行时异常:内存已满、数据库或其他连接未关闭等...

            【讨论】:

              【解决方案7】:

              考虑以下代码:

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

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

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

              【讨论】:

                【解决方案8】:

                如果您的测试包含错误,或者您正在测试错误的东西,那么代码覆盖率没有任何意义。

                作为相关切线;提醒一下,我可以很简单地构造一个满足以下伪代码测试的 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 的奖励业力,他指出了我正在考虑的漏洞

                【讨论】:

                • 可以返回空列表,或者只是 any 排序列表 - 它不检查输出是否与输入相关。
                • 该死的,我以为这在几分钟内仍然是个谜。不出所料,乔恩,你发现了漏洞;)
                【解决方案9】:

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

                【讨论】:

                  【解决方案10】:

                  在最近的 IEEE 软件论文“两个错误和无错误软件:自白”中,罗伯特·格拉斯认为,在“现实世界”中,由于缺少逻辑或组合(不能使用代码覆盖工具来防范)而不是逻辑错误(可以)。

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

                  论文的参考是here,我找到了一个粗略的总结here

                  【讨论】:

                    【解决方案11】:

                    在我的机器上工作

                    许多事情在本地机器上运行良好,我们不能保证在暂存/生产上运行。代码覆盖率可能不会涵盖这一点。

                    【讨论】:

                      【解决方案12】:

                      测试中的错误:)

                      【讨论】:

                        【解决方案13】:

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

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

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

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

                        【讨论】:

                          【解决方案14】:

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

                          还有,算术溢出,比如

                          int result = int.MAXVALUE + int.MAXVALUE;
                          

                          代码覆盖率仅涵盖现有代码,无法指出您应该在哪里添加更多代码。

                          【讨论】:

                            【解决方案15】:

                            我不知道其他人的情况,但我们没有接近 100% 的覆盖率。我们的“这永远不应该发生”的 CATCH 都没有在我们的测试中得到锻炼(嗯,有时他们会这样做,但随后代码会被修复,所以他们不会再发生了!)。恐怕我不担心从未发生过的 CATCH 中可能存在语法/逻辑错误

                            【讨论】:

                              【解决方案16】:

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

                              【讨论】:

                                【解决方案17】:

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

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

                                【讨论】:

                                  【解决方案18】:

                                  代码覆盖率没有多大意义。重要的是是否涵盖了所有(或大部分)影响行为的参数值。

                                  例如,考虑一个典型的 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 个结果)。它仍然不是没有错误的。添加测试以涵盖异常/错误情况也是值得的。在上述情况下,如果你尝试compareTo(10, Integer.MAX_INT),它会失败。

                                  底线:尝试根据行为将输入划分为不相交的集合,对每个集合中的至少一个输入进行测试。这将增加真正意义上的覆盖率

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

                                  【讨论】:

                                    【解决方案19】:

                                    执行 100% 的代码覆盖率,即 100% 的指令、100% 的输入和输出域、100% 的路径、100% 的任何你想到的,你的代码中仍然可能有错误:缺少功能

                                    【讨论】:

                                      【解决方案20】:

                                      正如这里的许多答案中提到的,您可能拥有 100% 的代码覆盖率,但仍然存在错误。

                                      除此之外,您可以有 0 个错误,但您的代码中的逻辑可能不正确(不符合要求)。代码覆盖率或 100% 无错误根本无法帮助您。

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

                                      1. 有一个清晰的功能规范
                                      2. 制定针对 (1) 编写的测试计划并进行同行评审
                                      3. 针对 (2) 编写测试用例并进行同行评审
                                      4. 根据功能规范编写代码并进行同行评审
                                      5. 根据测试用例测试您的代码
                                      6. 进行代码覆盖率分析并编写更多测试用例以实现良好的覆盖率。

                                      请注意,我说的是“好”而不是“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*.
                                      

                                      【讨论】:

                                        【解决方案21】:

                                        几乎任何东西。

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

                                        • 结构化基础测试数据流测试是指测试通过程序的每个逻辑路径。下面的人为代码有四个路径,具体取决于 A 和 B 的值。100% 的语句覆盖率可以通过仅测试四个路径中的两个来实现,可能是 f=1:g=1/ff=0:g=f+1。但是f=0:g=1/f 会给出除以零的错误。您必须考虑 if 语句和 whilefor 循环(循环体可能永远不会执行)以及 select 的每个分支switch 语句。

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

                                        • 错误猜测 - 对经常导致错误的输入类型进行明智的猜测。例如边界条件(关闭一个错误)、无效数据、非常大的值、非常小的值、零、空值、空集合。

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

                                        【讨论】:

                                          猜你喜欢
                                          • 1970-01-01
                                          • 1970-01-01
                                          • 2017-03-30
                                          • 1970-01-01
                                          • 1970-01-01
                                          • 2011-06-15
                                          • 1970-01-01
                                          • 2016-05-25
                                          • 1970-01-01
                                          相关资源
                                          最近更新 更多