自适应测试:基于非确定性有限状态机的确定性实现测试方法
1. 引言
在软件开发中,从给定的有限状态机(FSM)生成测试用例以确保“完全”故障覆盖是一个重要的研究领域。对于具有 n 个状态的规范机和包含最多 m 个状态(m ≥ n)的所有 FSM 的故障域,所谓的 m 完全测试套件可以实现完全故障覆盖,它能检测故障域中任何可由 FSM 建模的实现中的所有故障。如果实现不遵循选定的一致性关系(通常是跟踪等价或包含),则认为存在故障。
现有的生成 m 完全测试的方法(带有重置操作)通常使用以下三个测试片段:
-
转移序列/策略
:用于到达规范 FSM 中的状态。
-
遍历序列
:用于扩展转移序列。当 m = n 时,确保规范机和实现机的转移覆盖;当 m > n 时,还会检查实现机中是否存在额外状态。
-
状态识别或区分序列/策略
:用于检查上述序列前缀所到达的状态。
在确定性规范的情况下,已经提出了多种方法,如 W、Wp、HSI、H 以及最近的 SPY 方法。尽管这些方法在状态标识符的类型上有所不同,但它们都要求在规范的每个状态之后应用长度为 m - n + 1 的所有输入序列的遍历集。然而,当规范存在不可区分的状态时,应使用不同的遍历集,并且不再需要通过转移序列到达规范的每个状态。
当规范是非确定性的,且符合要求的实现 FSM 是其约简时,实现可能比规范具有更少的跟踪,并非规范的所有状态都能与实现的状态匹配。这意味着在推导 m 完全测试套件时,m 的值甚至可能小于 n。因此,在确定每个测试片段以及它们的组合方式时,必须考虑这一事实。
本文的主要贡献是提出了一种方法,用于推导测试片段,并以自适应的方式将它们组合和执行,以测试最多具有 m 个状态的给定确定性实现 FSM。该方法使得实现通过测试当且仅当它是规范的约简。与现有方法相比,它使用了到达和区分状态所需的自适应测试片段,避免了推导可能庞大的预设 m 完全测试。
2. 通用定义
有限状态机(FSM)S 是一个 5 元组 (S, s0, I, O, hS),其中:
- S 是有限状态集,s0 是初始状态。
- I 和 O 分别是有限非空且不相交的输入和输出集。
- hS 是转移关系,hS ⊆ S × I × O × S,其中 4 元组 (s, i, o, s′) ∈ hS 表示一个转移。
有时,我们会将 S 中的另一个状态 s 视为初始状态,这样的 FSM 记为 S/s。输入序列 α ∈ I* 在 S 的状态 s 中是定义的输入序列,如果它标记了从状态 s 开始的一系列转移。S 在状态 s 中的跟踪是标记从状态 s 开始的一系列转移的输入 - 输出对的字符串。
FSM S 可以分为以下几种类型:
| 类型 | 定义 |
| — | — |
| 平凡的 | hS = ∅ |
| 完全指定的(完全 FSM) | 对于每个 (s, i) ∈ S × I,存在 (o, s′) ∈ O × S 使得 (s, i, o, s′) ∈ hS |
| 部分指定的(部分 FSM) | 对于某些 (s, i) ∈ S × I,输入 i 在状态 s 中未定义,即对于所有 (o, s′) ∈ O × S,(s, i, o, s′) ∉ hS |
| 确定性的(DFSM) | 对于每个 (s, i) ∈ S × I,最多存在一个转移 (s, i, o, s′) ∈ hS 对于某些 (o, s′) ∈ O × S |
| 非确定性的(NFSM) | 对于某些 (s, i) ∈ S × I,存在至少两个转移 (s, i, o1, s1), (s, i, o2, s2) ∈ hS,使得 o1 ≠ o2 或 s1 ≠ s2 |
| 可观察的 | 对于每个两个转移 (s, i, o, s1), (s, i, o, s2) ∈ hS,有 s1 = s2 |
| 单输入的 | 在每个状态中最多有一个定义的输入,即对于每个两个转移 (s, i1, o1, s1), (s, i2, o2, s2) ∈ hS,有 i1 = i2 |
| 输出完全的 | 对于每个 (s, i) ∈ S × I 使得输入 i 在状态 s 中定义,对于每个输出都存在从 s 出发的带有 i 的转移 |
| 无环的 | TrS 是有限的 |
给定在状态 s 中定义的输入序列 α,outS(s, α) 表示 S 在状态 s 下对 α 的响应可能产生的输出序列集。对于可观察的 FSM S,对于跟踪 β ∈ Tr(S/s),s - after - β 表示 S 从状态 s 执行跟踪 β 后到达的状态。如果 s 是初始状态 s0,则用 S - after - β 表示。FSM S 是初始连通的,当且仅当对于任何状态 s ∈ S,存在跟踪 β 使得 S - after - β = s。死锁状态是指没有输入定义的状态,跟踪 β ∈ Tr(S) 是 S 的完成跟踪,如果 S - after - β 是死锁状态。
在本文中,我们仅考虑完全初始连通的可观察规范机。可以使用标准的自动机确定化过程将给定的 FSM 转换为可观察的。我们根据跟踪定义了完全 FSM 状态之间的几种关系:
-
跟踪等价
:如果 TrS(s1) = TrS(s2),则状态 s1 和 s2 是(跟踪)等价的。
-
跟踪包含
:如果 TrS(s2) ⊆ TrS(s1),则 s2 是 s1 的跟踪包含(是 s1 的约简),记为 s2 ≤ s1。
-
r - 兼容
:如果存在一个完全 FSM 的状态是 s1 和 s2 的约简,则 s1 和 s2 是 r - 兼容的,记为 s1 ≃ s2。
-
r - 可区分
:如果任何完全 FSM 的状态都不能是 s1 和 s2 的约简,则 s1 和 s2 是 r - 可区分的,记为 s1 ≄ s2。
我们还使用机器之间的关系。给定 FSM S = (S, s0, I, O, hS) 和 P = (P, p0, I, O, hP),如果 TrP(p0) ⊆ TrS(s0),则 FSM P 是 S 的约简;如果 P ⊆ S,p0 = s0 且 hP ⊆ hS,则 FSM P 是 S 的子机。
为了描述两个机器(状态)的共同行为,我们使用交集操作。两个机器 S 和 P 的交集 S ∩ P(也称为乘积)是一个 FSM (Q, q0, I, O, hS∩P),其中状态集 Q ⊆ S × P,初始状态 q0 = s0p0,转移关系 hS∩P 满足 (sp, i, o, s′p′) ∈ hS∩P ⇔ (s, i, o, s′) ∈ hS & (p, i, o, p′) ∈ hP。交集 FSM S ∩ P 仅保留两个机器的共同跟踪,即对于 S ∩ P 的每个状态 sp,有 TrS∩P(sp) = TrS(s) ∩ TrP(p),因此 TrS∩P = TrS ∩ TrP。
3. 推导测试片段
3.1 状态前导序列
现有的所有测试生成方法都依赖于能够到达规范状态并匹配实现机和规范机状态的测试。对于确定性 FSM,每个这样的测试完全由一个输入序列定义。当规范是非确定性的,且实现允许比规范具有更少的跟踪时,并非规范的所有状态都能与实现的状态匹配。因此,我们需要确定规范 FSM 中哪些状态必须在任何正确的实现中被“实现”。
定义 1 :给定 FSM S = (S, s0, I, O, hS),状态 s ∈ S 是确定可达的,如果 S 的任何约简都有一个跟踪将 S 带入状态 s。
命题 1 :FSM S 的状态 s 是确定可达的,当且仅当 S 有一个单输入无环子机 S′,其唯一的死锁状态是 s,并且对于 S′ 中某个状态定义的每个输入,该状态具有 S 中所有带有该输入的转移。
这样的子机可以在测试中用于自适应地将给定机器带入确定可达的状态,称为该状态的前导序列。
定义 2 :给定确定可达的状态 s ∈ S,S 的一个单输入无环子机,其唯一的死锁状态是 s,并且对于子机中某个状态定义的每个输入,该状态具有 S 中所有带有该输入的转移,称为状态 s 的前导序列,记为 Ps = (R, r0, I, O, hPs)。
具有所有完成跟踪的单输入投影的前导序列的状态在 [8] 中被称为确定性可达状态。在确定性(初始连通)机器中,每个状态都是确定性可达的;任何非确定性机器至少有一个确定性(因此是确定)可达的状态,即初始状态。
我们提出了一种方法来检查状态 s 是否确定可达,如果是,则推导前导序列 Ps。
算法 1:为给定状态构造前导序列
输入:FSM S 和 s ∈ S,s ≠ s0
输出:如果状态 s 是确定可达的,则输出前导序列
构造 FSM (R, r0, I, O, hRs) 如下:
R := {s};
hRs := ∅;
While 存在状态 s' ∉ R 和输入集 Is',使得对于每个输入 i ∈ Is',(s', i, o, s'') ∈ hS,s'' ∈ R 对于所有 o ∈ outS(s', i)
R := R ∪ {s'};
hRs := hRs ∪ {(s', i, o, s'') | i ∈ Is' 且 o ∈ outS(s', i)};
End While;
If s0 ∉ R 则返回消息 “状态 s 不是确定可达的” 并停止
Else 令 (R, r0, I, O, hRs),其中 r0 := s0,为得到的 FSM;
从初始状态开始,从每个有多个定义输入的状态中移除具有相同输入的所有输出转移,直到每个状态只有一个定义的输入,从而得到一个单输入子机,其唯一的死锁状态是 s;
删除从初始状态不可达的状态;
返回得到的机器作为状态 s 的前导序列并停止。
该算法的思想是,首先通过分析反向可达性选择所有可能构成前导序列的输入,因为在这个阶段我们不知道哪些输入能从初始状态到达所需状态;然后通过分析正向可达性,在每个状态保留一个输入。由于任何前导序列都是具有 n 个状态的规范机的子机,因此前导序列中任何跟踪的长度永远不会超过规范的状态数,即最多需要 n - 1 个输入来转移到确定可达的状态。
给定前导序列 Ps,令 T(Ps) 为其完成跟踪的集合。给定规范 FSM S 的确定可达状态集 K,且初始状态 s0 ∈ K,这些状态的所有前导序列的所有完成跟踪的并集 UK 是 K 的一个覆盖。这个覆盖将用于构造另一个测试片段,即遍历集。
例如,考虑图 1(a) 中的 FSM S,它有四个状态 1、2、3 和 4,初始状态为 1,有三个输入 a、b 和 c,以及两个输出 0 和 1。通过上述算法,我们可以为每个确定可达的状态推导前导序列,并计算覆盖集。
下面是推导前导序列的流程 mermaid 图:
graph TD;
A[开始] --> B[初始化 R = {s}, hRs = ∅];
B --> C{是否存在 s' ∉ R 和 Is'};
C -- 是 --> D[更新 R 和 hRs];
D --> C;
C -- 否 --> E{s0 是否在 R 中};
E -- 否 --> F[输出 “状态 s 不是确定可达的”];
E -- 是 --> G[转换为单输入子机];
G --> H[删除不可达状态];
H --> I[输出前导序列];
F --> J[结束];
I --> J;
通过以上步骤,我们可以有效地推导状态前导序列,为后续的测试片段组合和执行提供基础。
3.2 状态区分
在确定了能够到达规范机中确定可达状态的前导序列后,接下来需要考虑如何区分这些状态。这对于确保实现机与规范机的一致性至关重要。
我们通过构建一个规范机的自乘积机器来实现状态区分。自乘积机器能够精确地刻画所有可能的状态区分策略。从这个自乘积机器中,我们可以得到一个称为规范分离器的 FSM,而状态分离器则是规范分离器的子机器。
设规范 FSM 为 (S=(S, s_0, I, O, h_S)),其自乘积机器 (S\times S=(Q, q_0, I, O, h_{S\times S})),其中:
- (Q = S\times S) 是状态集。
- (q_0=(s_0, s_0)) 是初始状态。
- 转移关系 (h_{S\times S}) 满足 (((s_1, s_2), i, o, (s_1’, s_2’))\in h_{S\times S}\Leftrightarrow (s_1, i, o, s_1’)\in h_S) 且 ((s_2, i, o, s_2’)\in h_S)。
规范分离器是基于自乘积机器构建的,它能够捕捉到所有可能区分规范机中不同状态的策略。状态分离器则是从规范分离器中提取出来的,用于实际区分特定状态的子机器。
例如,对于两个状态 (s_1) 和 (s_2),我们可以在规范分离器中找到相应的子机器,该子机器能够通过一系列输入 - 输出序列来区分 (s_1) 和 (s_2)。
状态区分的重要性在于,在测试过程中,我们需要确保实现机到达的状态与规范机的状态是一致的。通过状态区分,我们可以验证实现机在执行相同输入序列时是否产生与规范机相同的输出,从而判断实现机是否符合规范。
3.3 遍历集推导
遍历集的推导是为了确保在测试过程中能够覆盖规范机的所有可能转移。对于给定的非确定性 FSM (S),我们需要推导合适的遍历集。
遍历集的作用是在到达规范机的某个状态后,通过应用遍历集中的输入序列,检查实现机在这些输入下的行为是否与规范机一致。当 (m = n) 时,遍历集确保规范机和实现机的转移覆盖;当 (m > n) 时,还会检查实现机中是否存在额外状态。
推导遍历集的方法基于规范机的结构和状态可达性。我们需要考虑规范机中每个状态的可能输入和输出,以及状态之间的转移关系。
例如,对于规范机 (S) 中的状态 (s),我们需要确定从 (s) 出发的所有可能输入序列,并根据这些输入序列生成相应的输出序列。遍历集应该包含这些输入 - 输出序列的组合,以确保在测试过程中能够全面覆盖规范机的行为。
3.4 测试定义与完整性
我们将测试定义为一个 FSM,它结合了状态前导序列、状态区分和遍历集的信息。这个测试 FSM 能够自适应地执行测试,根据实现机的输出动态选择下一步的输入。
设测试 FSM 为 (T=(T, t_0, I, O, h_T)),其中:
- (T) 是测试状态集,包含了规范机状态的前导序列、状态区分和遍历集的相关信息。
- (t_0) 是测试的初始状态。
- (h_T) 是测试转移关系,根据实现机的输出和规范机的行为动态确定下一步的转移。
测试的完整性是指测试能够检测到故障域中所有可能的故障。对于一个规范 FSM (S) 和故障域中最多具有 (m) 个状态的实现 FSM,一个 (m) 完全测试套件应该能够检测到所有不符合规范的实现。
我们通过将状态前导序列、状态区分和遍历集组合成测试 FSM,确保测试的完整性。在测试过程中,测试 FSM 会根据实现机的输出动态调整测试策略,从而有效地检测实现机是否符合规范。
4. 自适应测试方法
自适应测试方法的核心思想是根据实现机的当前输出动态选择下一步的输入。这种方法避免了预设 (m) 完全测试可能带来的庞大测试用例集,提高了测试效率。
下面是自适应测试的详细步骤:
1.
初始化
:设置测试 FSM (T) 的初始状态 (t_0),并将实现机 (P) 重置到初始状态 (p_0)。
2.
执行测试
:
- 从测试 FSM (T) 的当前状态 (t) 出发,根据转移关系 (h_T) 选择一个输入 (i)。
- 将输入 (i) 发送给实现机 (P),并记录实现机的输出 (o)。
- 根据输出 (o) 和测试 FSM (T) 的转移关系 (h_T),更新测试 FSM 的状态 (t)。
3.
终止条件
:
- 如果测试 FSM (T) 到达一个终止状态,且实现机 (P) 的行为与规范机 (S) 一致,则测试通过。
- 如果在测试过程中发现实现机 (P) 的行为与规范机 (S) 不一致,则测试失败。
下面是自适应测试的 mermaid 流程图:
graph TD;
A[初始化测试 FSM 和实现机] --> B[选择输入 i];
B --> C[发送输入 i 到实现机];
C --> D[记录实现机输出 o];
D --> E[根据 o 更新测试 FSM 状态];
E --> F{是否到达终止状态};
F -- 是 --> G{实现机行为是否与规范机一致};
G -- 是 --> H[测试通过];
G -- 否 --> I[测试失败];
F -- 否 --> B;
通过自适应测试方法,我们可以根据实现机的实际行为动态调整测试策略,从而更有效地检测实现机是否符合规范。
5. 示例说明
为了更详细地说明上述方法,我们使用一个具体的示例。假设我们有一个规范 FSM (S),其状态集 (S = {s_0, s_1, s_2}),输入集 (I = {a, b}),输出集 (O = {0, 1}),转移关系 (h_S) 如下表所示:
| 当前状态 (s) | 输入 (i) | 输出 (o) | 下一状态 (s’) |
|---|---|---|---|
| (s_0) | (a) | (0) | (s_1) |
| (s_0) | (b) | (1) | (s_2) |
| (s_1) | (a) | (1) | (s_2) |
| (s_1) | (b) | (0) | (s_0) |
| (s_2) | (a) | (0) | (s_0) |
| (s_2) | (b) | (1) | (s_1) |
-
状态前导序列推导
:
- 对于状态 (s_1),我们可以使用算法 1 推导其前导序列 (P_{s_1})。从状态 (s_1) 开始反向分析,找到能够到达 (s_1) 的输入序列。
- 假设通过算法 1 得到 (P_{s_1}) 的完成跟踪为 ((a, 0)),表示从初始状态 (s_0) 输入 (a) 得到输出 (0) 后可以到达状态 (s_1)。
-
状态区分
:
- 构建规范机 (S) 的自乘积机器 (S\times S),并从中得到规范分离器。
- 为了区分状态 (s_1) 和 (s_2),我们可以在规范分离器中找到相应的子机器。例如,通过输入序列 ((a)),状态 (s_1) 输出 (1),状态 (s_2) 输出 (0),从而实现状态区分。
-
遍历集推导
:
- 根据规范机 (S) 的结构和状态可达性,推导遍历集。对于每个状态,确定所有可能的输入序列。
- 例如,对于状态 (s_0),遍历集可以包含输入序列 ((a, b)),以检查从 (s_0) 出发的所有可能转移。
-
自适应测试
:
- 使用上述推导的状态前导序列、状态区分和遍历集构建测试 FSM (T)。
- 按照自适应测试的步骤,根据实现机的输出动态选择输入,进行测试。
6. 结论
本文提出的自适应测试方法为基于非确定性有限状态机的确定性实现测试提供了一种有效的解决方案。通过推导状态前导序列、状态区分和遍历集,并将它们组合成一个测试 FSM,我们能够实现自适应测试,根据实现机的输出动态调整测试策略。
这种方法避免了预设 (m) 完全测试可能带来的庞大测试用例集,提高了测试效率。同时,通过严格的理论分析和示例说明,我们验证了该方法的有效性和可行性。
在未来的研究中,可以进一步探索如何优化自适应测试方法,提高测试覆盖率和故障检测能力。例如,可以研究如何更有效地推导状态前导序列和遍历集,以及如何在测试过程中更好地利用状态区分信息。此外,还可以将该方法应用于更复杂的系统测试中,如实时系统和分布式系统。
超级会员免费看
5万+

被折叠的 条评论
为什么被折叠?



