基于有限状态机的测试(五):关键技术(自适应区分序列、识别序列)
要验证现实状态机模型B和理想状态机模型A的相似性,以及B中各个状态迁移的正确性,核心问题在于如何判断被测对象所处的状态。围绕这一问题发展出来的种种技术,就成为了基于有限状态机的测试方法中的关键部分。
本篇我们继续讨论这些关键技术。
7. 复位序列
严格来说,自适应区分序列并不是一个输入序列,而是一棵决策树。例如,对于图7所示状态机A:
图7
其自适应区分序列如图8所示:
图8
决策过程如下:
(1) 首先尝试给A输入a。如果输出是0,说明初始状态可能是s1、s3或s5,当前状态可能是s2(对应初始状态s1)、s4(对应初始状态s3)或s6(对应初始状态s5);
(2) 然后输入a,输出必然为1,当前状态可能是s3(对应初始状态s1)、s5(对应初始状态s3)或s1(对应初始状态s5);
(3) 然后输入b,输出必然为0,当前状态可能是s4(对应初始状态s1)、s6(对应初始状态s3)或s1(对应初始状态s5);
(4) 然后输入a:
(4.1) 如果输出为0,则当前状态只能是s2(对应初始状态s5),这就找到了一个叶节点,说明状态机的初始状态是s5。也就是说,当输入序列aaba对应的输出是0100的时候,就能识别出状态机的初始状态是s5;
(4.2) 如果输出为1,则当前状态可能是s5(对应初始状态s1)或s1(对应初始状态s3)。
(4.3) 继续输入b,输出必然为0,当前状态可能是s6(对应初始状态s1)或s1(对应初始状态s3)。
(4.4) 继续输入a:
(4.4.1) 如果输出为1,则当前状态只能是s1(对应初始状态s1)。也就是说,当输入序列aababa对应的输出是010101的时候,就能识别出状态机的初始状态是s1;
(4.4.2) 如果输出为0,则当前状态只能是s2(对应初始状态s3)。也就是说,当输入序列aababa对应的输出是010100的时候,就能识别出状态机的初始状态是s3。
最初输入a的时候,如果输出是1,则进入决策树的右侧分支,后续过程和左侧分支类似。
并非所有最简状态机都存在自适应区分序列。比如图6所示的最简状态机就不存在自适应区分序列,因为决策树既不能从输入a开始,也不能从输入b开始。
图6
一个状态机有可能并不存在预设区分序列,但却存在自适应区分序列。图7所示状态机就是这样的一个例子。
图7
假设这个状态机存在预设区分序列,那么预设区分序列只能从a开始,因为初始状态和当前状态的可能集合都是{s1, s2, s3, s4, s5, s6},如果从b开始,就会让当前可能状态s1、s2、s6发生归并。而输入a之后,初始状态区分和当前状态区分都是,这时,下一个输入字符是什么呢?仍然只能是a,因为一旦输入b,就会让当前可能状态集合{s2,s4,s6}中发生归并。连续输入两个a之后,初始状态区分为
,并没有实现进一步的区分。后续输入更多字符的情况也是类似的。因此这个状态机不存在预设区分序列。
如果一个输入a不会导致当前可能状态集合C发生归并,我们称输入a对C而言是“有效”的,也就是说,,都有
或
。在自适应区分序列的输入决策过程中,每一步所面对的当前可能状态集合都是唯一确定的,每一步输入都只需要对这个集合是有效的。而预设区分序列的工作过程中,由于缺少输出的反馈信息,每一步所面对的当前可能状态集合有多个,每一步输入必须对所有这些集合都是有效的。这就是为什么有的状态机存在自适应区分序列,却不存在预设区分序列的原因。
仍然以图7所示状态机为例,来说明判定自适应区分序列是否存在的方法。
- 输入第一个a之后,当前状态区分为
,其中包括两个当前可能状态集合。对第一个集合{s1,s3,s5}而言,输入b是有效的,且输入b之后,s1仍然在该集合中,s3和s5则会迁移到第二个集合中。这样,当前状态区分就更新为
;
中包括三个当前可能状态集合,对第三个集合{s2,s4,s6}而言,输入a是有效的,且输入a之后,s2和s4会迁移到第二个集合中,s6则会迁移到第一个集合中。这样,当前状态区分就更新为
;
中包括四个当前可能状态集合,对第二个集合{s3,s5}而言,输入b是有效的,且输入b之后,s3会迁移到第三个集合中,s4则会迁移到第四个集合中。这样,当前状态区分就更新为
;
中包括五个当前可能状态集合,对第四个集合{s2,s4}而言,输入a和b都是有效的。如果输入b,s2会迁移到第一个集合中,s4则会迁移到第三个集合中。这样,当前状态区分就更新为
。至此,每一个当前可能状态集合中都只有一个状态,这时我们就能确定自适应区分序列是存在的。
显然,如果理想模型A存在自适应区分序列,那么其任一状态都存在唯一输入输出序列。但反之不然。假定其状态si的唯一输入输出序列是xi,那么该状态的划分集可以取为{xi}。由于自适应区分序列是从唯一的根节点生长出来的决策树,任意两个状态的区分序列都会有共同的前缀,所以是划分序列簇(由此也可知,唯一输入输出序列不能用于构造划分序列簇,因为不同状态的唯一输入输出序列不一定有共同的前缀)。因此,可以利用每个状态自己的唯一输入输出序列验证现实模型B与A的状态相似性,以及B是否正确实现了每个迁移。
仍然以图1所示状态机A为例:
图1
其自适应区分序列如图9所示:
图9
则状态s1的唯一输入输出序列x1=ab,状态s2的唯一输入输出序列x2=a,状态s3的唯一输入输出序列x3=ab。以下输入序列可以从初始状态s1开始遍历A的所有状态(最终回到s1),并验证B与A的相似性:
实际上,验证各个状态和迁移的输入序列中经常存在冗余,比如两个输入序列是完全相同的,或者一个输入序列是另一个输入序列的前缀。去除这些冗余,检查序列就可以得到简化。看另一个例子,假设A如图10所示:
图10
A的自适应区分序列如图11所示:
图11
则状态s1的唯一输入输出序列x1=bb,状态s2的唯一输入输出序列x2=b,状态s3的唯一输入输出序列x3=bbb,状态s4的唯一输入输出序列x4=bbb。同时,假设B具备重置能力,重置输入字符为r。则如下一组输入序列可以分别验证s1、s2、s3、s4在B中存在对应的相似状态:
{rbb, rbb, rabbb, rbbbbb}
如下一组输入序列可以验证各个迁移的正确性:
{rbb, rbbbbb, rbbabbb, rbbaab, rabbb, raab, rabbb, rbab, rbbbbbb}
合并这些输入序列中的重复或互相包含的部分,最终得到检查序列为:raabrabbbrbabrbbabbbrbbbbbb
8. 识别序列
为了验证B与A相似,我们需要针对A的每一个状态si,测试其划分集Zi中的每一个输入序列,确认B的输出是否与A相同。假定Zi中有两个输入序列z1和z2,那么我们就需要先让B进入某个状态si',然后给B输入z1,再让B回到si',最后给B输入z2。如果这个过程中B的输出都跟A相同,那么si'就与si相似。但问题是,如果B不能输出状态消息,也没有重置能力,甚至不存在区分序列,那么如何确保输入z1之后,B能回到si'呢?
这时候,我们还有一种办法,就是利用“识别序列”。
仍然以如图1所示状态机A为例:
图1
我们的目标是验证A的状态s1与B中某个状态相似。已知s1的划分集是{a, b}。设B的初始状态是q0,给B输入aaa,使B的状态依次迁移至q1、q2、q3,并且观察到对应输出为000。由于B最多只有3个不同的状态(一致性测试的基本假设),q0~q3中必然有两个状态是相同的,假设这两个状态是qi和qj(i≠3且j≠3)。因为B进入qi和qj之后收到的输入都是a,所以这两个状态各自的后继状态qi+1和qj+1也是相同的。以此类推,可知最后一个状态q3也必然与q0~q2中某个状态qk相同。既然当B处于qk状态时,输入a对应的输出是0,那么,在B处于q3状态时输入a,对应的输出也必然是0。因此,我们只需要测试当B处于q3状态时,给B输入b的情况。如果这时B的输出是1,我们就足以断定B的状态q3与A的状态s1相似。因此,aaab就是状态s1的识别序列。如果给B输入aaab对应的输出是0001,那么输入aaa之后B的状态就是与s1相似的状态s1’。
如果A有n个状态,状态si的划分集Zi={z1,z2},设,则序列
是状态si的识别序列。
不失一般性,设A的状态si的划分集,
,
,即是说,
可以让A从si状态先迁移到
状态,再回到si状态。定义如下输入序列:
则序列是状态si的识别序列。如果这个序列测试通过了,我们就可以断定,B在输入
之后进入的状态,与A的si状态是相似的。
设状态si的识别序列是,且
,则如下序列可以验证B是否与A相似:
如何利用识别序列验证迁移的正确性呢?既然可以确保让B进入与si相似的状态
(在B的输出与A相符的前提下),那么对于迁移
的验证,我们就可以先利用
让B进入
,然后给B输入a,验证输出是否为0,继而使用
的划分集验证后继状态是否为
。