软件测试中的圈复杂度及其示例

⚡ 智能摘要

圈复杂度是托马斯·麦凯布于 1976 年开发的一种软件度量指标,用于计算程序中独立路径的数量。它通过控制流图计算得出,并给出实现完全分支覆盖所需的测试用例数量。

  • 📐 两种公式: 从图中可以看出 V(G) = E – N + 2,或者从决策点的数量可以看出 V(G) = P + 1。
  • 🧮 直接含义: 该值等于独立路径的最大数量,因此也等于所需的测试用例数量。
  • 🗺️ 图论基础: 节点代表处理步骤,边代表它们之间的控制流。
  • 🟢 1到10: 结构化、编写良好的代码,具有高可测试性和低维护成本。
  • 🟠 21到40: 代码非常复杂,可测试性很低,重构的成本通常低于测试的成本。
  • 🛠️ 工具: SonarQube, Visual Studio Code Metrics、Radon 和 Lizard 会自动计算。

软件测试中的循环复杂度

McCabe 的圈复杂度是什么?

软件测试中的循环复杂度 是用于测量软件程序复杂度的测试指标。它是软件程序源代码中独立路径的定量度量。循环复杂度可以通过使用控制流图或针对软件程序中的函数、模块、方法或类来计算。

独立路径定义为至少有一条边在任何其他路径中都未遍历过的路径。

该度量标准由 Thomas J. McCabe 于 1976 年开发,它基于程序的控制流表示。控制流将程序描述为由节点和边组成的图。

在图中,节点表示处理任务,而边表示节点之间的控制流。

McCabe 的圈复杂度

程序流程图表示法

程序的流程图表示法定义了通过边连接的多个节点。以下是 if-else、While、until 等语句的流程图以及流程的正常顺序。

程序的流程图符号

如何计算圈复杂度

数学表示:

从数学角度来看,它是图论中所有独立路径的集合。 Code 程序的复杂度可以用以下公式定义——

V(G) = E - N + 2

哪里,

E – 边数

N – 节点数

V (G) = P + 1

其中 P = 谓词节点的数量(包含条件的节点)

示例–

i = 0;
n=4; //N-Number of nodes present in the graph

while (i<n-1) do
j = i + 1;

while (j<n) do

if A[i]<A[j] then
swap(A[i], A[j]);

end do;
j=j+1;

end do;

该程序的流程图如下

计算圈复杂度

通过数学计算,

  • V(G) = 9 – 7 + 2 = 4
  • V(G) = 3 + 1 = 4 (条件节点为1,2、3和XNUMX节点)

基组四个独立的执行路径:

  • 1,7
  • 1,2,6,1,7
  • 1,2,3,4,5,2,6,1,7
  • 1,2,3,5,2,6,1,7

环路复杂性的性质

以下是圈复杂度的属性:

  1. V(G) 是图中独立路径的最大数量
  2. 电压(G)>=1
  3. 如果 V (G) = 1,则 G 有一条路径
  4. 常用的指导原则是,单个模块的 V(G) 应保持在 10 或以下。

该指标对软件测试有何用处

基础路径测试是白盒测试技术之一,它保证在测试过程中至少执行一条语句。它检查程序中每一条线性无关的路径,这意味着…… 所需的测试用例数量等于程序的圈复杂度。.

这个度量之所以有用,是因为循环复杂度(M)的特性——

  1. M 可以是实现分支覆盖的测试用例数量(上限)
  2. M 可以是图中路径的数量。(下限)

考虑这个例子——

If (Condition 1)
Statement 1

Else
Statement 2

If (Condition 2)
Statement 3

Else
Statement 4

该程序的圈复杂度为8-7+2=3。

由于复杂度已计算为 3,因此对于上述示例,完成路径覆盖需要三个测试用例。

应遵循的步骤

计算圈复杂度和测试用例设计应遵循以下步骤。

第四步 – 使用代码中的节点和边构建图

第四步 – 识别独立路径

第四步 – 循环复杂度计算

第四步 – 测试用例设计

一旦基本集合形成, 测试用例 应写入以执行所有路径。

更多关于 V (G)

如果程序较小,可以手动计算循环复杂度。如果程序非常复杂,则需要使用自动化工具,因为这涉及更多流程图。根据复杂度数字,团队可以得出需要采取的措施。

下表概述了复杂度数 v (G) 及其对应含义:

复杂度数
1到10

结构化且编写良好的代码

高可测试性

成本和精力较少

11到20

复杂代码

中等可测试性

成本和精力中等

21到40

非常复杂的代码

可测试性低

成本和精力投入高

> 40

根本无法测试

成本和精力非常高

计算圈复杂度的工具

有许多工具可用于确定应用程序的复杂性。一些复杂性计算工具用于特定技术。复杂性可以通过程序中的决策点数量来确定。决策点是源代码中的 if、for、for-each、while、do、catch、case 语句。

工具示例包括

  • 欧克林特 – C 和相关语言的静态代码分析器
  • SonarQube – 报告了超过 25 种语言的循环复杂性和认知复杂性
  • Visual Studio Code 指标 – .NET 程序集的内置圈复杂度分析
  • Radon 和 Lizard – 用于命令行复杂度分析的分析器 Python 以及分别针对多语言项目
  • 指标 – 查找指标 Java 相关申请

环路复杂性的应用

圈复杂度可以证明在以下方面非常有用

  • 帮助开发人员和测试人员确定独立的路径执行
  • 开发人员可以保证所有路径都至少测试过一次。
  • 帮助我们更加专注于未被发现的路径
  • 提高代码覆盖率 软件工程
  • 评估与应用程序或程序相关的风险
  • 在周期早期使用这些指标可以降低项目的风险

如何降低圈复杂度

高复杂度值只是一个信号,而非最终结论。四次重构就能实现实践中大部分的复杂度降低。

  • Extract 方法。 将分支移到单独的函数中,可以将复杂性分散到两个模块中。系统整体的复杂性不变,但每个单元都可以独立测试。
  • 用查找表替换条件链。 冗长的 if-else-if 语句链测试同一个变量,最终会变成一个映射或开关,将多个决策点合并为一个。
  • 使用保护性条款。 尽早返回无效输入可以消除单个大型 if-else 块创建的嵌套,而不会改变行为。
  • 用多态性取代条件语句。 当条件判断基于类型时,将每个分支移到它自己的类中,就可以完全消除这种判断。

之前,V(G) = 4:

if (user != null) {
    if (user.isActive()) {
        if (user.hasRole("admin")) {
            return grantAccess();
        }
    }
}
return denyAccess();

之后,行为保持不变,但嵌套结构被移除:

if (user == null) return denyAccess();
if (!user.isActive()) return denyAccess();
if (!user.hasRole("admin")) return denyAccess();
return grantAccess();

关于该指标的注意事项。 圈复杂度统计的是决策次数,而不是难度。一个包含 20 个简单 case 的 switch 语句得分为 21,但易于阅读;而一个深度嵌套的块得分为 8,可能更难理解。应该利用这个数值来寻找需要审查的代码,而不是将其作为作弊的目标。

常见问题

每个模块最多十个代码块是常见的指导原则。代码块数量在 11 到 20 个之间时,代码虽然复杂但仍可管理。超过 20 个代码块后,可测试性急剧下降;超过 40 个代码块后,该模块通常被认为无法按原样进行测试。

两者结果相同。手动计算时,P + 1 更快,因为它只需要计算决策点。工具使用 E – N + 2,因为它们已经构建了控制流图。

不一定。圈复杂度统计的是决策数量而非难度,因此一个只有二十个简单情况的扁平化 Switch 电路得分很高,而且易于阅读。可以将这个数字作为复习的提示。

他们将其与变更频率和缺陷历史结合起来,对哪些模块风险最大进行排名,并将审查和测试工作集中到最有可能失败的代码上。

是的。人工智能助手会提出保障条款,例如trac使用 ted 方法和查找表来减少计数。使用现有的测试套件验证行为,因为改变逻辑的重构会适得其反。

总结一下这篇文章: