首页 > 知识&问答
语句,分支和路径覆盖的定义和好处
发布时间:2024-11-07 13:42:39 / 浏览量:
语句,分支和路径覆盖的定义和好处 代码覆盖率是一种衡量您在软件上执行的测试水平的方法。收集覆盖率指标是一个简单的过程:检测代码并针对检测版本运行测试。这会产生数据,显示出您执行了(或更重要的是未执行)哪些代码。覆盖率是单元测试的完美补充:单元测试告诉您代码是否按预期执行,而代码覆盖率则告诉您还需要测试什么。
大多数开发人员了解此过程并同意其价值,通常将覆盖率定为100%。尽管100%的覆盖率是一个令人钦佩的目标,但100%错误类型的覆盖率可能会导致问题。典型的软件开发工作是根据语句数或要测试的分支数来衡量覆盖率。即使100%的语句或分支覆盖率,关键的bug仍可能存在于代码逻辑中,从而使开发人员和管理人员都产生了错误的安全感。
100%的覆盖率怎么会不足?因为语句和分支的覆盖范围无法告诉您代码中的逻辑是否已执行。语句和分支覆盖范围非常适合发现未执行的代码块中发现的明显问题,但通常会遗漏与决策结构和决策交互相关的错误。另一方面,路径覆盖是一种更强大且更全面的技术,可帮助尽早发现缺陷。
在讨论路径覆盖率之前,让我们看一下语句和分支覆盖率的一些问题。
声明范围语句覆盖范围标识了方法或类中的哪些语句已执行。这是一个简单的度量标准,并且存在许多开源产品来衡量此覆盖范围。最终,语句覆盖的好处是它能够识别哪些代码块尚未执行。但是,语句覆盖率的问题在于,它不能识别源代码中的控制流构造所引起的错误,例如复合条件或连续的开关标签。这意味着您可以轻松获得100%的覆盖率,并且仍然存在明显的,未捕获的错误。
下面的示例演示了这一点。在这里,该returnInput()方法由七个语句组成,并且有一个简单的要求:其输出应等于其输入。
接下来,我们可以创建一个满足需求并获得100%语句覆盖率的JUnit测试用例。
中存在一个明显的错误returnInput()。如果靠前个或第二个决策的结果为true,而另一个决策的结果为false,则返回值将不等于方法的输入。一个精明的软件开发人员会立即注意到这一点,但是声明覆盖率报告显示了100%的覆盖率。如果经理看到100%的覆盖率,则可能会产生错误的安全感,确定测试已完成,然后将错误代码发布到生产中。
意识到声明覆盖范围可能不符合要求,我们的开发人员决定继续采用更好的测试技术:分支覆盖范围。
分支机构覆盖率分支是决策的结果,因此分支机构的覆盖范围只是衡量已测试了哪些决策结果。这听起来很棒,因为与简单的语句覆盖相比,它对源代码有更深入的了解,但是分支覆盖也可能使我们有更多需求。
确定方法中的分支数很容易。布尔决策显然有两个结果,对与错,而开关对每种情况都有一个结果–不要忘记默认情况!因此,一种方法中决策结果的总数等于该方法需要覆盖的分支数加上该方法中的入口分支(毕竟,即使是带有直线代码的方法也具有一个分支)。
在上面的示例中,returnInput()有七个分支–方法条目的三个true,三个false和一个不可见分支。我们可以用两个测试用例覆盖六个正确和错误的分支:
两项测试均验证了我们的要求(输出等于输入),并且它们产生了100%的分支覆盖率。但是即使分支覆盖率达到100%,我们的测试仍未找到该错误。同样,经理可能会认为测试已完成,并且该方法已准备好投入生产。
我们精明的开发人员认识到,我们缺少通过测试方法的某些可能途径。在上面的示例中,我们没有测试TRUE-FALSE-TRUE或FALSE-TRUE-TRUE路径,我们可以通过添加两个以上的测试来检查这些路径。
此方法只有三个决策,因此测试所有八种可能的路径都很容易。但是,对于包含更多决策的方法,可能路径的数量呈指数增长。例如,只有十个布尔决策的方法具有1,024条可能的路径。祝一切顺利!
因此,实现100%的语句和100%的分支覆盖率可能还不够,对于一个复杂的方法,穷举测试每个可能的路径可能也不可行。有什么选择?
基础路径覆盖率路径表示从方法开始到退出的执行流程。具有N个决策的方法具有2^N个可能的路径,并且如果该方法包含循环,则它可能具有无限数量的路径。幸运的是,我们可以使用一种称为圈复杂度的度量来减少我们需要测试的路径数量。
方法的循环复杂性是该方法的较早决定加一个较早决定的数量。圈复杂度有助于我们通过一种方法定义线性独立路径的数量(称为基集)。线性独立性的定义不在本文讨论范围之内,但是总而言之,基本集是最小的路径集,这些路径集可以组合在一起以通过一种方法创建所有其他可能的路径。
与分支覆盖范围一样,测试基本路径集可确保您测试每个决策结果,但是与分支覆盖范围不同,基本路径覆盖范围可确保您彼此独立地测试所有决策结果。换句话说,每个新的基本路径正好“翻转”一个先前执行的决定,而使所有其他执行的分支保持不变。这是使基本路径覆盖范围比分支覆盖范围更健壮的关键因素,并使我们能够看到更改一个决策如何影响方法的行为。
让我们使用相同的示例进行演示。
为了达到100%的基本路径覆盖率,我们需要定义我们的基本集。此方法的圈复杂度为4(一个加决策数),因此我们需要定义四个线性独立的路径。为此,我们选择一条任意的靠前条路径作为基线,然后一次翻转一个决策,直到我们有了基础。
路径1:
任何方法都可以满足我们的基准要求,因此我们为决策的结果(表示为TTT)选择了正确的方法。这是我们基础集中的靠前条路径。
路径2:
为了找到下一条基本路径,我们将(仅)翻转基线中的靠前个决策,为我们所需的决策结果提供FTT。
路径3:
我们在基线路径中翻转第二个决定,为我们的第三条基本路径提供TFT。在这种情况下,靠前个基准决策将保持固定不变,并带有真实的结果。
路径4:
最后,我们在基线路径中翻转了第三个决定,为我们的第四条基本路径提供了TTF。在这种情况下,靠前个基准决策将保持固定不变,并带有真实的结果。
因此,我们的四个基本路径是TTT,FTT,TFT和TTF。让我们组成测试,看看会发生什么。
在示例代码中,找到我们的语句和分支覆盖工作所遗漏的错误。此外,基本路径的数量与决策的数量呈线性增长,而不是呈指数增长,从而使所需测试的数量与实现完整分支覆盖范围所需的数量保持一致。实际上,由于基本路径测试涵盖了方法中的所有语句和分支,因此它有效地包含了分支和语句的覆盖范围。
但是,为什么我们不测试其他可能的路径呢?请记住,基本路径测试的目标是彼此独立地测试所有决策结果。测试这四个基本路径可以实现此目标,从而使其他路径变得多余。如果您以FFF作为基线路径开始,那么您将以(FFF,TFF,FTF,FFT)为基础,从而使TTT路径变得多余。这两个基础集都同样有效,并且都满足我们独立的决策结果标准。
创建测试数据在此示例中,实现100%的基本路径覆盖范围很容易,但是在现实世界中全面测试基本路径集将更具挑战性,甚至是不可能的。因为基本路径覆盖率测试了方法中决策之间的交互,所以您需要使用导致执行特定路径的测试数据,而不仅仅是分支决策所必需的单个决策结果。注入数据以强制沿着特定路径执行是很困难的,但是您可以记住一些编码实践,以简化测试过程。