CPT304 SoftwareEngineeringII 软件工程 2 Pt.9 软件测试 (Software Testing)(下)

文章目录

  • [1. White Box / Structural Testing(白盒测试 / 结构测试)](#1. White Box / Structural Testing(白盒测试 / 结构测试))
    • [1.1 白盒测试的特点](#1.1 白盒测试的特点)
    • [1.2 Program Graphs / Control Flow Graph(程序图 / 控制流图)](#1.2 Program Graphs / Control Flow Graph(程序图 / 控制流图))
      • [1.2.1 程序图 / 控制流图的 4 种基本结构](#1.2.1 程序图 / 控制流图的 4 种基本结构)
      • [1.2.2 示例 1](#1.2.2 示例 1)
      • [1.2.3 DD-Path](#1.2.3 DD-Path)
        • [1.2.3.1 DD-Path 的正式定义](#1.2.3.1 DD-Path 的正式定义)
        • [1.2.3.2 DD-Path Graph(DD 路径图)](#1.2.3.2 DD-Path Graph(DD 路径图))
        • [1.2.3.3 示例 1](#1.2.3.3 示例 1)
      • [1.2.4 Test Coverage Metrics(测试覆盖率指标)](#1.2.4 Test Coverage Metrics(测试覆盖率指标))
        • [1.2.4.1 E.F. Miller's Test Coverage Metrics(Miller 提出的测试覆盖率指标)](#1.2.4.1 E.F. Miller’s Test Coverage Metrics(Miller 提出的测试覆盖率指标))
        • [1.2.4.2 Statement Coverage Testing(语句覆盖测试) 和 Predicate Coverage Testing(谓词/分支/判定覆盖测试)](#1.2.4.2 Statement Coverage Testing(语句覆盖测试) 和 Predicate Coverage Testing(谓词/分支/判定覆盖测试))
        • [1.2.4.3 DD-Path Graph Edge Coverage C₁](#1.2.4.3 DD-Path Graph Edge Coverage C₁)
        • [1.2.4.4 DD-Path Coverage Testing C₁P](#1.2.4.4 DD-Path Coverage Testing C₁P)
        • [1.2.4.5 Multiple Condition Coverage Testing(多条件覆盖测试)](#1.2.4.5 Multiple Condition Coverage Testing(多条件覆盖测试))
        • [1.2.4.6 Dependent DD-Path Pairs Coverage Testing C d C_d Cd(依赖 DD-Path 对覆盖测试)](#1.2.4.6 Dependent DD-Path Pairs Coverage Testing C d C_d Cd(依赖 DD-Path 对覆盖测试))
        • [1.2.4.7 Loop Coverage(循环覆盖测试)](#1.2.4.7 Loop Coverage(循环覆盖测试))
        • [1.2.4.8 示例 2](#1.2.4.8 示例 2)
      • [1.2.5 McCabe's Basis Path Testing(McCabe 基本路径测试)](#1.2.5 McCabe’s Basis Path Testing(McCabe 基本路径测试))
        • [1.2.5.1 Compute the program graph(画程序图 / 控制流图)](#1.2.5.1 Compute the program graph(画程序图 / 控制流图))
        • [1.2.5.2 Calculate the cyclomatic complexity(计算圈复杂度)](#1.2.5.2 Calculate the cyclomatic complexity(计算圈复杂度))
        • [1.2.5.3 Select a basis set of paths(选择一组基本路径)](#1.2.5.3 Select a basis set of paths(选择一组基本路径))
        • [1.2.5.4 McCabe 基本路径法的局限](#1.2.5.4 McCabe 基本路径法的局限)
    • [1.3 指导方针和观察结果](#1.3 指导方针和观察结果)

1. White Box / Structural Testing(白盒测试 / 结构测试)

白盒测试也叫:

英文 中文意思
Glass box testing 玻璃盒测试
Structural testing 结构测试
Clear box testing 透明盒测试
Open box testing 开放盒测试

测试人员可以看到程序内部结构和代码。

白盒测试是一种利用程序内部工作原理来设计测试数据的方法。

黑盒测试主要根据需求说明书来测试输出。

白盒测试是根据源代码的具体知识来定义测试用例,并检查输出。

1.1 白盒测试的特点

  1. Rigorous Definitions(定义更严格、更精确):在白盒测试中,测试人员知道软件内部逻辑,包括代码、算法和数据结构,所以可以更精确地定义要测试什么、怎么测试。
  2. Mathematical Analysis(可以进行数学分析):程序内部结构可以用数学方式表示,比如图、树、矩阵等。
  3. Useful Measurement(可以得到有用的度量指标):白盒测试可以收集很多关于代码质量的指标。

1.2 Program Graphs / Control Flow Graph(程序图 / 控制流图)

白盒测试一般都会先把代码画成程序图 / 控制流图,然后基于这个图做后面的分析和测试。

程序图是一种有向图。

也叫做控制流图。

如果程序图中有两个节点 i 和 j,那么只有在这种情况下,才会有一条从 i 指向 j 的边:节点 j 对应的语句,可以在节点 i 对应的语句之后立刻执行。

简单来说就是:如果程序执行完 i 后,下一步可能马上执行 j,那么图里就画一条 i → j 的箭头。

所以程序图就是把代码内部的执行流程画成一个有向图:节点表示语句,箭头表示执行顺序,用来帮助白盒测试分析所有可能路径是否都被测试到。

1.2.1 程序图 / 控制流图的 4 种基本结构

  1. If-Then-Else 结构
  2. Case / Switch 结构
  3. Pretest loop 前测试循环(While 循环)
    先判断条件,再决定要不要执行循环体。
  4. Posttest loop 后测试循环(do-until / do-while 循环)
    先执行循环体,再判断条件。

我们从这 4 个结构可以看出,node 代表的是一条可执行语句,或者一个语句片段 / 语句块,但并不严格等于代码的一行。例如程序声明、变量定义都不是真正的控制流程判断或主要执行步骤。

如下面的例子所示。

1.2.2 示例 1

这是我们上一章举的判断三角形的那个例子。

图中节点 对应代码
4 输出提示语
5 输入 a, b, c
6 输出 Side A
7 输出 Side B
8 输出 Side C
9 判断三角形不等式
10 IsATriangle = True
11 IsATriangle = False
13 判断 IsATriangle
14 判断是否等边
15 输出 Equilateral
16 判断是否不等边
17 输出 Scalene
18 输出 Isosceles
21 输出 Not a Triangle
23 程序结束

这里不可执行语句不会放进程序图里。

节点 4 到 8 是顺序结构(sequence)。

节点 9 到 12 是一个 if-then-else 结构。

节点 13 到 22 是嵌套的 if-then-else 结构。

节点 4 是程序图的起点(indeg=0,in-degree,入度)。

节点 23 是程序图的终点(outdeg=0,out-degree,出度)。

这个程序图没有循环,所以它是一个有向无环图。

1.2.3 DD-Path

最著名的一种结构测试方法,是基于一种叫做"decision-to-decision path"的结构,因此简称 DD-Path。

DD-Path 是从程序图 / 控制流图中提取出来的一段路径。

它不是完整程序路径,而是控制流图中的一小段,因此是控制流图的子路径。

它是控制流图中从一个决策节点(decision node)到另一个决策节点,或者从决策节点到结束节点(terminal node)的一段子路径。

1.2.3.1 DD-Path 的正式定义

从控制流图中抽出来的一段路径,用来简化程序图。

Case 1:如果一个节点入度为 0,它单独可以成为一个 DD-Path(indeg = 0)。

Case 2:如果一个节点出度为 0,它单独可以成为一个 DD-Path(outdeg = 0)。

Case 3:如果一个节点有多个入口,或者多个出口,它也可以单独作为一个 DD-Path(indeg ≥ 2 or outdeg ≥ 2)。

Case 4:如果一个节点只有一个入口和一个出口,它也可能作为单个 DD-Path(indeg = 1 and outdeg = 1)。

Case 5:一段尽可能长的连续路径,也可以作为一个 DD-Path(maximal chain of length ≥ 1)。

或者如下表所示,只要某一段路径符合其中一种情况,它就可以被看作一个 DD-Path。

Case 含义 是否可以单独算一个 DD-Path
Case 1 入度为 0 的入口节点 可以
Case 2 出度为 0 的结束节点 可以
Case 3 分支节点或汇合节点,入度 ≥ 2 或出度 ≥ 2 可以
Case 4 普通节点,入度 = 1,出度 = 1 可以
Case 5 一段连续的最大路径链 可以
1.2.3.2 DD-Path Graph(DD 路径图)

DD-Path Graph 是一个有向图。

但它和普通程序图不同:

节点代表什么
Program Graph / Control Flow Graph 节点是代码语句或语句片段
DD-Path Graph 节点是 DD-Path

也就是说,DD-Path Graph 的节点不是单行代码,而是一段路径。

图中的边 / 箭头表示 DD-Path 之间的控制流关系。

也就是说,如果程序执行完一个 DD-Path 后,可能接着执行另一个 DD-Path,那么它们之间就有一条箭头。

我们可以这样理解:

我们第一步将代码化成程序图。

第二步可以把程序图中的连续路径合并成 DD-Path。

第三步再把这些 DD-Path 当作节点,画成更简单的 DD-Path Graph。

所以整体关系是:源代码 → Control Flow Graph → DD-Path → DD-Path Graph

1.2.3.3 示例 1

我们回到我们之前的示例,我们现在可以按照刚刚说的把CFG(Control Flow Graph,控制流图)转换成 DD-Path Graph(决策到决策路径图)。

这里把原始控制流图 CFG 中的节点按照 DD-Path 规则进行合并和命名,得到一个更简化的 DD-Path Graph;右边表格说明每个 DD-Path 对应原控制流图中的哪些节点,以及它属于 DD-Path 定义中的哪一种情况。

1.2.4 Test Coverage Metrics(测试覆盖率指标)

使用 DD-Path 的原因,是它可以非常精确地描述测试覆盖情况。

只看需求说明(也就是上一章讲的黑盒测试),很难知道测试用例到底覆盖了程序内部多少代码。

重复测试(Redundancy):

比如我们设计了 10 个测试用例,但它们内部都走同一条程序路径,那就有很多重复。

测试空缺(Gaps):

比如程序里有一条错误处理路径,但是所有测试用例都没有走到它,这就是 gap。

测试覆盖率指标(Test Coverage Metrics)是一种衡量方法,用来判断一组测试用例覆盖了程序的多少部分。

1.2.4.1 E.F. Miller's Test Coverage Metrics(Miller 提出的测试覆盖率指标)

白盒测试中可以用不同层次的覆盖率指标来衡量测试是否充分。

从上到下,覆盖要求一般越来越严格,测试用例数量也可能越来越多。

  1. C₀ :Every statement(语句覆盖)
    比如程序里有 20 行可执行代码,测试用例让这 20 行都运行过一次,就满足 C₀。
    这是比较基础的覆盖率。
  2. C₁:Every DD-path(DD-Path 覆盖)
    每一条 DD-Path 至少被执行一次。
    C₁ 比 C₀ 更强,因为它不仅看语句有没有执行,还关注程序结构中的路径片段有没有覆盖。
  3. C₁p:Every predicate to each outcome(每个判断条件的每种结果都覆盖)
    每个判断条件至少走过 True 和 False 两种结果。
  4. C₂:C₁ coverage + loop coverage(DD-Path 覆盖 + 循环覆盖)
    在 C₁ 的基础上,还要考虑循环。
  5. C d C_d Cd:C₁ coverage + every dependent pair of DD-paths(DD-Path 覆盖 + 相关 DD-Path 对覆盖)
    除了每个 DD-Path 都覆盖,还要覆盖某些有依赖关系的 DD-Path 组合。
    也就是说不只看单独路径,还看相邻或相关路径之间的组合关系。
  6. C M C C C_{MCC} CMCC:Multiple condition coverage(多条件覆盖)
    如果一个判断条件里面有多个小条件,要测试这些小条件的组合。
  7. C i k C_{ik} Cik:Every program path up to k repetitions of a loop(覆盖循环重复 k 次以内的所有路径)
    测试所有程序路径,但循环最多只考虑重复 k k k次。
  8. C s t a t C_{stat} Cstat:Statistically significant fraction of paths(统计上有代表性的路径覆盖)
    不测试所有路径,而是选择一部分在统计上有代表性的路径。
  9. C ∞ C_∞ C∞:All possible execution paths(所有可能执行路径覆盖)
    测试程序所有可能的执行路径。
1.2.4.2 Statement Coverage Testing(语句覆盖测试) 和 Predicate Coverage Testing(谓词/分支/判定覆盖测试)

Statement Coverage(语句覆盖)测试的目标是设计一组测试用例,让程序里的每一条语句至少执行一次。

Predicate Coverage(谓词/分支/判定覆盖)也可以叫 Branch Coverage 或 Decision Coverage。

它的目标是让程序中的每个判断条件都至少取一次 True 和一次 False。

这里的判断条件可以是一个简单条件,也可以是一个复合布尔表达式,但把它整体当成一个判断单元。

比如 A or B

虽然里面有 A 和 B 两个小条件,但在 predicate coverage 里,可以把整个 A or B

看成一个整体判断。

它只关心整个表达式最终是 True 还是 False。

因此谓词覆盖相当于让 DD-Path 图里的每一条边都至少走一次。

我们再看一个示例:if (A or B) then C

这里判断条件是:A or B

要让它为 True,可以用:A = True, B = False

要让它为 False,可以用:A = False, B = False

所以这两个测试用例就可以让整个判断表达式分别得到 True 和 False。

1.2.4.3 DD-Path Graph Edge Coverage C₁

C₁ 是在 DD-Path 图中,让每一条边至少被测试走到一次。

也可以理解成前面说的 Predicate Coverage / Branch Coverage / Decision Coverage,每个判断条件的 True 分支和 False 分支都要走一次。

如下图所示例子。

这里用两个测试用例就够了。

测试用例 p1 p2
1 T T
2 F F

所以这里没有测试所有组合,所有组合其实有 4 种:

p1 p2
T T
T F
F T
F F
1.2.4.4 DD-Path Coverage Testing C₁P

C₁P 是比前面 C₁ 覆盖 更严格的一种覆盖标准。

C₁ / Edge Coverage 只要求每个判断节点的 True 分支和 False 分支都至少走一次。

C₁P 要求 P1 和 P2 的所有真假组合都要测试到。

也就是我们刚刚提到的 4 种组合。

组合 P1 P2
1 T T
2 T F
3 F T
4 F F
1.2.4.5 Multiple Condition Coverage Testing(多条件覆盖测试)

Multiple Condition Coverage Testing(多条件覆盖测试)比 C₁P 更严格。

如果一个判断条件里面有多个小条件,那么测试时要把这些小条件的所有组合都测到。

如果我们刚刚例子中的 P1 是 (A or B)的这种复合布尔表达式。

这里整个判断是 (A or B),只要两种情况,但是里面有两个小条件:A、B。

因此这里组合搭配会有 4 种情况。

A B A or B 的结果
True True True
True False True
False True True
False False False

多条件覆盖要求测试就会有 4 个测试用例。

因此Multiple Condition Coverage 不满足于只看整体结果,它还要求 A 和 B 的每种组合都测到,所以要 4 个。

所以这就会导致我们的测试用例呈指数增长。

如果有 n n n个小条件,每个条件都有 True / False 两种可能,那么组合数量就是 2 n 2^n 2n。

1.2.4.6 Dependent DD-Path Pairs Coverage Testing C d C_d Cd(依赖 DD-Path 对覆盖测试)

在简单的 C₁ 覆盖标准 中,我们只关心:

DD-Path 图里的每一条边有没有至少走过一次。

C d C_d Cd测试想进一步增强覆盖要求,不仅测每一条边,还要测试有依赖关系的 DD-Path 对。

因为有些 bug 不是"某一条边没走到"导致的,而是:

前面某个变量赋值之后,后面某个路径使用它时出错。

所以它更容易发现和变量传递、变量使用有关的错误。

如果两个 DD-Path 之间存在"定义 / 引用"关系,那么它们就是依赖的 DD-Path 对。

换句话说,一个 DD-Path 里定义了某个变量,另一个 DD-Path 里使用了这个变量。

比如:

DD1: x = 10

DD2: if x > 0

那么 DD1 和 DD2 就是 dependent DD-Path pair,因为:

DD1 定义 x

DD2 使用 x

所以 C d C_d Cd测试不仅要覆盖 DD-Path 图中的所有边,还要覆盖所有有依赖关系的 DD-Path 对。

因此 C d C_d Cd比 C₁ 更严格。

总结一下,两个 DD-Path 如果一个定义变量、另一个使用这个变量,它们就是 dependent DD-Path pair;C_d 覆盖要求不仅覆盖所有控制流边,还要覆盖这些数据依赖路径,从而发现变量赋值和使用过程中可能出现的错误。

1.2.4.7 Loop Coverage(循环覆盖测试)

如果程序里有循环,测试时不能只随便跑一次,要专门设计测试用例,让循环的不同执行情况都被覆盖。

循环结构一般有三种:

  1. Concatenated Loop(串联循环):
    两个循环前后相连,但互不包含。
    类似:
bash 复制代码
while 条件1:
    执行 A
while 条件2:
    执行 B
  1. Nested Loop(嵌套循环):
    一个循环里面还有另一个循环。
    类似:
bash 复制代码
while 条件1:
    while 条件2:
        执行 B
  1. Knotted Loop(交织循环 / 缠结循环):
    循环结构交叉缠在一起,不是简单的前后关系,也不是简单的嵌套关系。
    这种循环最复杂,测试也最麻烦,因为路径之间相互交叉。

循环条件本质上也是一个判断,所以测试时至少要覆盖它的两个结果:

循环条件结果 含义
True 进入循环 / 继续循环
False 退出循环 / 不进入循环

可以把循环次数当成一个变量,用边界值测试的方法来测。

比如循环最多执行 10 次,那么可以测试:

测试情况 含义
minimum 最少次数,比如 0 次
minimum+ 比最少多一点,比如 1 次
nominal 正常次数,比如 5 次
maximum- 接近最大次数,比如 9 次
maximum 最大次数,比如 10 次

如果做 robust testing,还可以测超出范围的情况。

如果是嵌套循环(nested loops),测试时应该先测最里面的循环,然后再逐层向外测。

原因是:内层循环通常会被外层循环反复触发,如果不先搞清楚内层循环是否正确,外层测试会变得很乱。

如果循环是交织循环(knotted loop),也就是交织循环 / 缠结循环,就需要用数据流分析测试方法。

如果两个循环之间有复杂跳转或变量依赖,就不能只靠简单的循环次数覆盖来测,而要分析:变量在哪里被定义?变量在哪里被使用?前一个循环的变量变化会不会影响后一个循环?

因此使用 data flow analysis testing(数据流分析测试)。

1.2.4.8 示例 2

下图展示了一个示例,它展示了语句覆盖 C₀ 和 分支/判定覆盖 C₁ 的区别。

语句覆盖 C₀ 的目标是:

让程序中的每个节点 / 每条语句至少执行一次。

SCPath1: 1-2-3(F)-10(F)-11-13

SCPath2: 1-2-3(T)-4(T)-5-6(T)-7(T)-8-9-3(F)-10(T)-12-13

这两个路径加起来,基本把所有节点都走到了。所以满足 C₀ 语句覆盖。

分支覆盖 / 判定覆盖 C₁ 的目标是:

每个判断节点的 True 和 False 分支都至少走一次。

所以它比 C₀ 更严格。

BCPath1: 1-2-3(F)-10(F)-11-13

BCPath2: 1-2-3(T)-4(T)-5-6(T)-7(T)-8-9-3(F)-10(T)-12-13

BCPath3: 1-2-3(T)-4(F)-10(F)-11-13

BCPath4: 1-2-3(T)-4(T)-5-6(F)-9-3(F)-10(F)-11-13

BCPath5: 1-2-3(T)-4(T)-5-6(T)-7(F)-9-3(F)-10(F)-11-13

这些路径的目的不是单纯执行所有节点,而是为了保证:

判断节点 True 是否测到 False 是否测到
3
4
6
7
10

1.2.5 McCabe's Basis Path Testing(McCabe 基本路径测试)

基本路径测试是 Tom McCabe 提出的一种白盒测试方法。

它的核心思想是先计算程序的逻辑复杂度,然后用这个复杂度来确定需要测试多少条基本路径。

常用的复杂度指标叫 Cyclomatic Complexity(圈复杂度)。

圈复杂度表示程序中有多少条相对独立的执行路径。

Basis Path Testing 的四个步骤:

  1. Compute the program graph(画程序图 / 控制流图)
  2. Calculate the cyclomatic complexity(计算圈复杂度)
  3. Select a basis set of paths(选择一组基本路径)
  4. Generate test cases for each of these paths(为每条路径设计测试用例)
1.2.5.1 Compute the program graph(画程序图 / 控制流图)

要做 McCabe's Basis Path Testing,首先需要一个程序的控制流图或 DD-Path 图,然后才能计算圈复杂度、选择基本路径。

下图是一个示例,这是一个 DD-Path 图。

这个程序只有一个入口节点 A,也只有一个出口节点 G。

1.2.5.2 Calculate the cyclomatic complexity(计算圈复杂度)

Cyclomatic Complexit(圈复杂度)表示程序中"线性独立路径"的数量。

路径越多,说明程序逻辑越复杂,需要测试的基本路径也越多。

也可以理解为根据程序中的判断点数量(the number of decisions)来衡量程序逻辑复杂度。

Cyclomatic Complexit(圈复杂度)的计算公式有两个:

V ( G ) = e − n + 2 p V(G) = e - n + 2p V(G)=e−n+2p(普通程序图,有入口和出口)

V ( G ) = e − n + p V(G) = e - n + p V(G)=e−n+p(闭合图,出口连回入口)

其中:

符号 含义
V(G) 圈复杂度
e edges,边的数量,也就是箭头数
n nodes,节点数量
p connected regions,连通区域数量

一般一个程序图只有一个连通区域,所以:p = 1

我们回到前面的例子。

我们用第一种算法(线性独立路径数量)进行计算。

V(G) = e - n + 2p = 10 - 7 + 2(1) = 5

因此这个程序有 5 条线性独立路径。

我们用第二种算法(线性独立回路数量)进行计算。

这里边数变成了 11,是因为它把原图人为加了一条边:出口节点 → 入口节点

V(G) = e - n + p = 11 - 7 + 1 = 5

因此计算结果仍然是5。

所以这两种公式虽然不同,但是计算结果一致,可以将这里的图进行转换,使普通程序图变为闭合图进行计算。

1.2.5.3 Select a basis set of paths(选择一组基本路径)

线性独立路径就是一条执行路径,这条路径相比之前选过的路径,至少包含一个新的语句块,或者一个新的判断条件结果。

简单说:新路径 = 至少带来一点之前没测过的新东西

McCabe's Baseline Method(基线路径法)是选择基本路径的一种方法。

它的思路是:先选一条正常执行路径作为 baseline path,然后每次改变其中一个判断条件,得到新的路径。

先选择一条 最正常、最典型的程序执行路径。然后每次反转一个判断条件,生成新的独立路径,直到所有需要考虑的分支变化都覆盖到。

下图给出之前的例子中,我们计算出的 5 条线性独立路径怎么使用 McCabe's Baseline Method(基线路径法)选出 5 条线性独立路径。

先选一条主路径,然后每次改变一个分支选择,得到新的独立路径。

比如蓝色路径中从 A 走向 B:

A → B

如果把这个选择翻转,就可能走:

A → D

于是得到红色或黄色路径。

再比如蓝色路径中从 B 走向 C:

B → C

如果改成:

B → E

就得到橙色路径:

A → B → E → F → G

再比如在 C 处,原来循环回 B:

C → B

如果改成直接退出到 G:

C → G

就得到绿色路径:

A → B → C → G

我们从这里也可以看出 McCabe 方法算出来的 圈复杂度 V(G)=5,意思不是"程序总共只有 5 条路径",而是这个程序至少有 5 条线性独立路径,需要选 5 条作为基本路径来测试。

McCabe 方法的思想是从很多可能路径中选出一组"基本路径",这些基本路径能够代表程序的主要控制结构。

1.2.5.4 McCabe 基本路径法的局限

McCabe 的基本路径法有两个主要薄弱点。

第一点就是我们刚刚提到的。

  1. 只要测试了所有基本路径,就足够了。其实不是!(Testing the set of basis paths is sufficient (it is not!))。
    这 5 条基本路径只是最基本的覆盖要求,不代表程序就完全没问题了。
  2. McCabe 方法为了定义"线性独立路径",需要把程序路径看成类似数学里的向量空间。
    这个说法比较抽象。
    可以简单理解为:McCabe 方法把每条程序路径当成一个"路径向量",然后判断:这条路径是不是独立的?它是不是包含新的边?它能不能和其他路径组成一组 basis paths?
    它借用了数学中 linear independence(线性独立) 和 basis(基) 的思想。
    而程序路径本身并不是真正的数学向量。

例如回到我们之前的程序图。

V(G) = 18 - 15 + 2(1) = 5

Original p1: A-B-C-E-F-H-J-K-M-N-O-Last (Scalene)

这是原始选定的 baseline path,基线路径。

这条路径对应的输出是:不等边三角形。

我们现在有:Flip p1 at B - p2: A-B-D-E-F-H-J-K-M-N-O-Last (Infeasible)

在路径 p1 中,节点 B 这里有一个判断 / 分支。

原来 p1 在 B 处走的是:

B → C

现在把它反过来,改走:

B → D

于是得到新路径 p2。

这条路径在图上看起来存在,但实际程序逻辑中不可能执行。

p2:

A-B-D-E-F-H-J-K-M-N-O-Last (Infeasible)

在 B 处翻转得到,但实际不可行。

p3:

A-B-C-E-F-G-O-Last (Infeasible)

在 F 处翻转得到,也不可行。

p4:

A-B-C-E-F-H-I-N-O-Last (Equilateral)

在 H 处翻转得到。

这条路径可行,对应:Equilateral,也就是等边三角形。

p5:

A-B-C-E-F-H-J-L-M-N-O-Last (Isosceles)

在 J 处翻转得到。

这条路径可行,对应:Isosceles,也就是等腰三角形。

这就是所谓的第二点把程序路径数学化、向量化,会忽略代码语义,可能产生 infeasible path。

对于第一点,这里测完 basis paths 不等于测完所有路径,也不等于测试充分。

McCabe 方法根据控制流图可以找出一组看起来独立的路径,但它只看图的连接关系,不一定理解代码的实际逻辑。因此,有些路径虽然在图上能画出来,但由于变量关系或条件逻辑矛盾,实际没有任何输入能走到。

回到刚刚的例子,如果程序走到了节点 C,那么根据程序逻辑,后面必须走到节点 H;如果程序走到了节点 D,那么后面必须走到节点 G。

这些逻辑约束会排除掉路径 2 和路径 3。

还需要补充一条"不是三角形"的基本路径。

最后真正可执行、逻辑上合理的路径有 4 条。

它们分别对应三角形问题的四种输出:

路径 输出
p1: A-B-C-E-F-H-J-K-M-N-O-Last Scalene 不等边三角形
p4: A-B-C-E-F-H-I-N-O-Last Equilateral 等边三角形
p5: A-B-C-E-F-H-J-L-M-N-O-Last Isosceles 等腰三角形
p6: A-B-D-E-F-G-O-Last Not a triangle 不是三角形

1.3 指导方针和观察结果

功能测试 / 黑盒测试主要关注程序功能和输入输出,但它和源代码内部结构距离比较远。

路径测试(Path testing)太关注控制流图本身,而不一定充分考虑代码真实逻辑。

基本路径测试告诉我们至少需要多少测试(lower bound)。不代表测完这些路径就完全充分。

路径测试提供了一些指标,可以用来反过来检查功能测试设计得好不好。

  1. 多个功能测试走同一条路径 → 可能冗余(Redundancy)。
  2. 没有达到 DD-Path 覆盖 → 说明功能测试有空缺(Gaps)。

覆盖率指标可以用来制定最低测试要求。

覆盖率指标还可以帮助我们决定哪些代码需要更严格测试。

相关推荐
wu_ye_m1 小时前
学习c语言第35天 函数声明和定义
c语言·开发语言·学习
清辞8531 小时前
Coze从入门到实战---第一、二章
大数据·人工智能·学习·语言模型
伊布拉西莫2 小时前
【流畅的Python】第20章:并发执行器 — 学习笔记
笔记·python·学习
jinglong.zha2 小时前
LScript-从零基础到商业变现的AI自动化学习平台
运维·学习·自动化
闪闪发亮的小星星2 小时前
STK_00 学习方案路线
学习
一楼的猫3 小时前
茄子写作助手——品牌搜索突破9万后的技术型品牌认知与官网入口指南
人工智能·学习·机器学习·chatgpt·ai写作
AOwhisky3 小时前
学习自测与解析:MySQL第五、六、七期核心知识点详解
运维·数据库·笔记·学习·mysql·云计算
niuniuyi~3 小时前
QT学习笔记
笔记·qt·学习
咸甜适中4 小时前
rust语言学习笔记Trait(十六)Error(错误)
笔记·学习·rust