基于分解的基于模型的测试生成方法
Abstract:文章主要是通过把一个系统模型分解成合适的可单独分析的子系统模型来解决模型检验的状态爆炸问题。
Intro:介绍了一下概念和文章内容
❓基于模型的测试(MBT):「测试」 - 基于模型的测试 - 未完成 - 知乎 (zhihu.com)
❓状态爆炸问题: MBT是基于对系统状态空间的穷举搜索吗,那状态数量就会跟着并发分量指数倍的涨,系统一复杂并发分量一多,直接穷举在实际操作中就是不可行的,这就是状态爆炸问题。
这篇论文提出来的一个解决方法,首先,给定一个测试目标,通过利用模型变量的依赖性把系统分解成子系统,这些子系统构成了一个树,然后通过访问这个树来生成子系统的测试,最后合并给整个系统测试。大概好像是这么个说法。
BASIC DEFINITIONS:一些基本定义
关于过渡系统的:
1.过渡系统 Transition system:M<A,P,Θ>。A是由G(G是由V构成的,V是变量vi的合集)、D(每个变量vi对应的值域)、关系和函数、解释函数构成的,代表的是系统的瞬时配置。P是下一时刻变量的集合。Θ是变量的初始状态。(感觉这里这条博客解释的比较容易看懂,反正就是搞成有限/无限状态机的原理那么理解)
example1/code1里给的是一个用M定义的安全注射系统(SIS)的例子,所有的定义照着这个例子看会好理解点。
Code 1. Transition system example - SIS
signature A://关于A的定义
V = {SafInject,Overridden,Press,WaterPress,Valve}
//五个变量:安全注入,推翻,压力,水压,阀门
D_SafInject = {OFF,OFF_VALVE,ALERT,ON}
//安全注入的取值范围:关/关阀门/报警/开
D_Overridden = boolean
//推翻的取值范围:是/否
D_Press = {Low,Mid,High,Unknown}
//压力的取值范围:低/中/高/未知
D_WaterPress = {0,...,1000}
//水压的取值范围
D_Valve = {open,closed}
//阀门的取值范围:开/关
program P://关于P的定义
SafInject':=
//SafInject变量下一时刻将变为(根据下面判断)
if Press = Low ⋀ ┐Overridden ⋀ Overridden' then OFF_VALVE
//如果当前压力是低,且当前没有被推翻,下一时刻被推翻,SafInject下一时刻就要变成关阀门了
……//剩下不写了一个思路,别的都是有依据变的,水压和阀门下一时刻状态是随机的
initial state Θ://关于初始状态变量值Θ的设置
SafInject = OFF, Overridden = FALSE, Press = Low, WaterPress = 0, Valve = closed
2.计算步骤 Computational step:按P和当前变量算下一时刻变量。
3.系统执行 System execution:状态转换,状态就是通过计算步骤得到的这样。
4.变量依赖 Variable dependency:两个变量有直接依赖关系,用DirDep(v)表示变量v的直接依赖集。
5.依赖图 Dependency graph:是针对过渡系统M来定义的,根据所有变量的DirDep(v)来构建的,这里依赖图是可以有循环的,比如x':=y,y':=x这种,他俩相互依赖。然后,Dep(v)定义的是v依赖的除了自身之外的所有变量的集合(不一定是直接依赖,有依赖路径能从v到w就可以)。
example1的依赖图:
关于model-based testing的:
6.测试 Test:用来覆盖给定的测试目标(需要的系统行为),测试目标由测试谓词表示。
7.测试谓词 Test predicate:模型上的一个公式,用于确定是否达到特定的测试目标。
8.覆盖率标准 Coverage criterion:一个函数C,如果C生成的每个测试谓词在test suite中的测试序列的至少一个状态下都满足,则test suite满足覆盖标准。常见的就是取值覆盖、条件覆盖啥的,MCDC(modefied condition/decision coverage)指修订的条件/判定覆盖,在每个判定中的每个条件都曾经独立的影响判定的结果至少一次(判定覆盖 - MBA智库百科 (mbalib.com))。比如如果用example1里的例子就会生成以下谓词:F(SafInject=OPEN), F(SafInject=CLOSED), F(Overridden), F(┐Overridden), F(Press=Low), ...., F(Press=Unknown), F(WaterPress=0), ...,F(WaterPress = 1000),F(Valve = open), F(Valve = closed)
然后模型检查器会通过生成的测试案例来测试模型,返回反例(没看懂,为啥要叫反例)。具体是给一个测试谓词,用检查器来监测陷阱属性,如果属性为false,证明测试谓词可行,返回的反例是覆盖测试谓词的测试,如果属性为true,说明不可行,没有可以覆盖它的测试,如果在没提供任何结果的情况下终止,就是状态爆炸了,用户不知道测试谓词能不能被覆盖。
SYSTEM DECOMPOSITION:系统分解
把M分成子系统,因为子系统的依赖性依赖于变量依赖性,所以必须分析DG(定义5),不能把相互依赖的变量拆了。
9. 变量集合V,V的子集W,除W之外的变量介个EXT(W)=V\W,作为W中的变量v:如果v不直接依赖EXT(W)中的任何变量,那v属于W的内部变量集合INT(W);如果v只依赖于EXT(W)的一部分,那v属于W的输入变量集合INP(W)。下图是一个例子,方格的圆圈是因为它既依赖EXT又依赖INT,所以既不是INP也不是INT。但图上只是例子,在本文的分解过程里会保证只有INP和INT的。
分解过程
子系统的构建
给定M、tp、Dectp、可以给Dectp里的每一个Vi构建一个子系统Mi,其中Mi的三元分别是Ai:V-Vi;Pi:对于INT(Vi)是根据P算的,对于INP(Vi)是随机的;Θi:对于INT(Vi)是和Θ里一样的,对于INP(Vi)是随机的。
子系统依赖关系树
算法1可以保证给定两个子系统M1和M2,最多会有1个公共变量v,输入在一个子系统,内部在另一个子系统,这决定了子系统Mi之间的依赖关系。
10.子系统依赖 Subsystems dependency:如果INP(Vi) ∩ INT(Vj)={v},就说明Mi依赖于Mj,v叫做Mi到Mj的连接变量,表示为L(Mi,Mj)。这样DirDep(Mi)就会直接返回Mi直接依赖的所有子系统。
11.子系统依赖树 Subsystems Dependency Tree SDT:根节点是M1,M1是包含了tp中变量的子系统。每个节点Mi的子节点是DirDep(Mi)。叶子节点是DirDep(Mi)为空集的Mi。
还是拿例1那个SIS当例子,当给定tp:var(tp)={SafInject}的时候,得到分解的子系统M1和M2(代码如下),连接变量L(M1,M2)=Press。具体怎么得到的来不及看了回头再说。
上图是变量依赖图的分解和对应的子系统依赖树。
TEST GENERATION BY DECOMPOSITION:通过分解来生成测试
因为要生成覆盖整个系统的测试谓词tp的测试,所以用顺序遍历依赖树SDTtp,给每个子系统构建属于它的测试,然后把这些部分合并在一起得到最终测试。
上图是通用子系统K和测试谓词p的生成,最后得到的输出是ρ。首先让模型检测器来生成一个能覆盖p的测试ρ,如果这个测试ρ是可行的,那么对于K的每个直接依赖的子系统Mj,步骤如图上所示。inputSeq得到的是从Mj到K的输入序列,然后根据inputSeq来计算Mj的测试谓词reqInputsTp,用,in是输入序列里的变量,X是下一时刻连接符(好像是吧,没太看懂)。然后,它会递归地拿reqInputsTp作为测试谓词来处理子系统Mj,得到的结果就是关于Mj和它依赖的子系统的测试ρj。得到ρj之后,用一个merge函数来把它合并。
上述技术叫做StrongTP,它的健全性(生成的每个测试都是完整系统的有效执行)的是在另一篇文献里证明的。同时那篇文献还证明了它是不完整的,就是说还有StrongTP未覆盖的测试谓词。
举个例子,如果还是拿SIS来说,code2和code3那几张图就是对应的分解系统,给定测试谓词F(SafInject=OFF_VALVE)的话,一开始得到的那个(针对M1)能覆盖测试谓词的测试ρ就是,然后Press作为它和M2的连接变量,得到的inputSeq就是(Low,Unknown),对应的关于M2的测试谓词就是
,这个测试谓词对M2是可行的,所以再得到针对M2的能覆盖谓词的测试ρ
,把两个ρ合并,得到的就是整个系统的测试ρ
。
然后这个方法还有一个叫WeakTP的版本,这个版本的完整性会好一点。StrongTP是要求构建相同长度的序列的,WeakTP的话,K的子系统(也就是孩子节点)是可以产生比K上的测试更长的测试的,K上的测试会扩展到更长来匹配它。然后它把这个公式换成了
,
(在B变成真之前A在至少一个状态下连续为真),
接着举例子就是,给定SIS测试谓词F(SafInject=ALERT),M1的测试ρ就是,inputSeq就是(Low,Normal),但是,如果用StrongTP的话,测试谓词就是
,press前一时刻是low下一时刻是normal是不可能的,因为press是根据waterpress决定的吗,但是waterpress的取值要求是不可能一步涨300的,所以这个inputseq对于M2来说就是不适用的。用WeakTP的话测试谓词就是
,这个就可以用,M2的ρ就是
,合并之后的整个系统的ρ就是
。
WeakTP也不是完整的,但至少比StrongTP完整(能覆盖更多测试谓词)。
EXPERIMENTS:实验
用的是NuvSMV验证系统,baseline是不同来源的87个NuvSMV模型,其中62个是NuSMV distribution来的,20个是网上搞来的,5个是用AsmetaSMV转换来的。
这个图是用来介绍baseline的大小和复杂性的,散点图里的点是变量数量和状态数量,大部分都是变量越多状态越多。
对于所有的模型,是根据决策覆盖率(DC)和值覆盖率(VC)生成的测试谓词,所有测试谓词的并集表示为AllTP。(条件覆盖和MCDC啥的不能用,是以后的研究内容)
上面这个表是给所有的模型生成的测试谓词的数量和选出来的测试谓词的数量。
实验结果
首先从系统分解的角度,因为文章的解决思路是通过分解系统,让模型检查器处理的状态空间缩小来应对状态爆炸的问题,所以这里计算的是这个方法能在多大程度上减少被测系统的大小(状态数量),计算公式是,sd是分解系统的大小(取所有分解得到的子系统的大小的最大值),sg是全局系统的大小。下图是对于每个模型的平均大小缩减,平均来说分解后的系统比原始系统小46.5%。
从测试谓词覆盖率的角度,实验评估的是StrongTP和WeakTP和普通的不分解的技术比起来能提高多少覆盖率,普通的不分解的技术选的是NoDecomp。表2是这三个技术生成的可行和不可行的测试谓词的数量,StrongTP和WeakTP的可行测试谓词的数量增加了4.5%,超时的谓词数量减少了17.5%。表3是关于测试谓词可行、不可行和超时情况的百分比的变化,其中覆盖的测试谓词的百分比增加了3.1,因超时而未覆盖的降低了3.75,但也增加了0.65的不可行测试谓词。
上图是对于每个模型,三种技术的可行不可行超时测试谓词的百分比变化,随着模型增大,改进程度会降低,因为模型太大的话分解的子系统也不会小。然后对于有些模型,它的可行测试谓词的数量会减少,不可行的会增加。
上图是怎么对可行/不可行/超时进行分类的,不写了,没看懂。
最后,从生成测试耗费的时间的角度来说,因为状态爆炸问题主要的后果就是,模型的检查时间会跟着模型增大成指数级增长,所以这部分实验评估的是StrongTP、WeakTP和NoDecomp的测试生成时间。表4报告的是三种技术花费的时间,可以看出来StrongTP减少了13.94%,WeakTP减少了14.15%。
但是时间的减少取决于模型的大小,下图是每个模型生成时间的百分比变化,模型的排序是按大小也就是状态数递增排的,StrongTP和WeakTP可以减少大模型的生成速度,但是会增加小模型的生成速度,因为模型小的时候分解的开销是大于收益的。
这一章最后就是一点统计分析,主要就是说可行/不可行/超时的这个数量是具有统计学意义的。
THREATS TO VALIDITY:对有效性的威胁
外部有效性:实验用的是NuSMV,实验结果可能不能推广到所有的过渡系统,但是并不会威胁结果的有效性,因为从过渡系统到NuSMV的映射非常简单,NuSMV可以作为不同过渡系统形式的良好代表。
内部有效性:
第一个威胁:采取了一些预防措施来确保结果只取决于新提出来的技术,首先是自动检查了每个生成的测试是否是实际的系统执行,然后是在同一台计算机上用相同的模型检查器上做的实验,还有就是去除了10个无法应用这个基于分解的技术的模型,所以对这三个技术,它们的基准是完全一样的。
第二个威胁:选了30分钟作为测试谓词超时的标准,这个标准是通过统计分析得出来的,对于这三个技术,都没有【未超时的测试谓词数量随着超值阈值增加而增加】的情况,所以实验报告了具有最大超时的结果,因为能最大限度地增加被覆盖的测试谓词数量。
第三个威胁:值覆盖测试谓词数量的阈值的设置,不会在结果中引入偏差。(没看懂)
构造有效性:本文不考虑测试中的实现,也不能评估生成的测试对实现的有效性;因为分解了系统,所以没涵盖的测试谓词对测试有效性可能特别重要,但文章也没办法评估。但是从实验结果来说,假设测试谓词同样重要,那没有覆盖的测试谓词百分比,也是远低于使用NoDecop超时的测试谓词比例的,所以StrongTP和WeakTP的表现还是优于NoDecomp的。
RELEATED WORK:(?)最后一章才写,有病。
COI:通过去除不影响的变量来减少模型的大小,本文的技术包含了COI。
CEGAR:数据抽象技术,创建了具体数据值和一些抽象数据值之间的映射来减少空间状态,但是它是不适合用来测试的。
把程序划分成子程序:一个子程序结束状态包含传给下一个子程序的信息,和本文不一样,本文的模型是并行的。
SMART:程序中调用的所有函数都被单独测试,最后构建完整的测试。但是它有时候对某些输入的约束无法表达(比如哈希函数),会带来一些问题。
Conclusion:总结(直接翻译的,不看也行)
我们提出了一种基于模型检查的测试生成方法,试图缓解状态爆炸问题。该方法首先根据系统变量相关性将被测系统分解为子系统树;分解保证了树中的每个子系统与其父亲共享一个变量:这个变量是父亲子系统从子系统接收的输入。测试生成包括按预先顺序访问树,为每个子系统生成测试,并将这些测试合并在一起,以获得整个系统的测试。这种方法是合理的,尽管并不完整。我们提出了两个版本的方法(子系统中必须涵盖的测试目标不同),提供了不同程度的完整性。实验表明,所提出的方法能够将测试目标的覆盖率提高约3个百分点。此外,该方法加快了14.15%的生成时间。
我们的方法允许在单个子系统上并行化测试生成。作为未来的工作,我们计划利用这个机会,尽管这需要在提取输入序列和合并子系统测试方面扩展当前技术。此外,我们计划将该方法与其他覆盖标准(如MCDC和条件覆盖)进行评估。