我自由地将行号添加到您的代码中,以便能够提供更好的解释。您在评论中提到您对语句覆盖感兴趣。您的代码示例中的语句位于第 07、09 和 11 行以及第 18、20 和 22 行。当然,if
语句本身也是语句(因此得名),但这些语句将在每次执行时执行相应的功能。
在函数的一次执行中oneParameter
,将执行以下条件语句之一:在第 07 行、第 09 行或第 11 行。这是因为if-else if-else
语句的排他性。类似地,在一次函数调用中twoParameter
,将执行第 18 行、第 20 行或第 22 行中的语句。
因此,要涵盖所有语句,您必须调用每个函数 3 次。控制所采用的实际分支的参数在这两种情况下都是shape
参数。这意味着,其他参数的值与将执行哪个语句无关。一组简单的调用可能是:
oneParameter("A", 0.0);
oneParameter("B", 0.0);
oneParameter("any other", 0.0);
twoParameter("N", 0.0, 0.0);
twoParameter("M", 0.0, 0.0);
twoParameter("any other", 0.0, 0.0);
这是实现 100% 语句覆盖率的最小调用集的示例。令人惊讶的是,这些只是调用——甚至没有对结果进行任何评估。当我们谈论测试覆盖率时,隐含的假设是,相应的代码行不仅被执行,而且相应的结果作为测试的一部分被评估。这可能如下所示:
assertEquals(0.0, oneParameter("A", 0.0));
assertEquals(0.0, oneParameter("B", 0.0));
assertEquals(-1.0, oneParameter("any other", 0.0));
assertEquals(0.0, twoParameter("N", 0.0, 0.0));
assertEquals(0.0, twoParameter("M", 0.0, 0.0));
assertEquals(-1.0, twoParameter("any other", 0.0, 0.0));
尽管它现在具有 100% 的语句覆盖率并且还执行结果评估,但它仍然远不是一个高质量的测试套件。原因如下: 执行单元测试的主要目标是发现代码中的错误。然而,上面的一组测试并不适合发现任何有趣的错误。为您提供一些此测试套件找不到的可能错误示例:
- 错误地,函数
oneParameter
中"A"
的计算"B"
被交换了。
- 操作中的
twoParameter
函数被错误地交换了。"N"
*
+
- 在函数
twoParameter
for中,表达式与"N"
相乘,而不是与x1
相乘。x2
x1
x1
- 在函数
twoParameter
中"M"
,常量被设置为0.05
而不是正确的0.5
。
这些只是可能的错误的示例。因此,认真寻找可能的错误的目标需要超越覆盖范围。实际上,甚至可能有部分代码根本不适合使用单元测试进行测试——尽管在您的简单示例中并非如此。此类代码部分的示例是无法访问的代码(例如,为提高健壮性而添加的 switch 语句的默认分支),或仅包含与其他组件交互的代码,在这种情况下,集成测试是更适合的方法。因此,达到 100% 覆盖率的目标通常是没有意义的,即使您真诚地努力追求质量卓越的单元测试套件。
然而,对于有兴趣确保他们在代码中处理所有相关场景的开发人员来说,覆盖率是一个有价值的信息。可悲的是,从管理的角度判断测试套件的质量时,覆盖率远没有那么有价值:在这种情况下,它通常会降低到仅仅一个百分比,并且开发人员被迫创建测试,直到达到一定的覆盖率百分比- 但通常没有给他们足够的培训、时间和鼓励来正确地进行测试。因此,为了达到 80% 的覆盖率,所有琐碎的代码(getter 和 setter 等)都可能会被“测试”以增加覆盖率。然而,测试最复杂和最难测试的 20% 的代码,由于没有时间而被推迟(尽管这很可能是隐藏错误的代码)。而且,即使是已经覆盖的 80%,也可能像我上面展示的最小测试套件一样进行了糟糕的测试。