具体实施方式
图1是本发明的一个实施例的总体构成示意图,如图1所示,本实施例包括下述装置:用于从现有的测试用例中选择近似测试用例的装置101;用于生成修改提示的装置102;用于统计逻辑覆盖的装置103,其中,装置102和装置103是可选的。
下面详细描述图1所示的装置101。图2是在本实施例,装置101的具体构成示意图,包括:用于将程序代码映射为结构对象的装置201;用于统计程序路径的装置202;用于对程序进行插装的装置203;用于记录逻辑覆盖信息的装置204;用于计算逻辑目标的近似测试用例的装置205,其中,装置203是可选的,如果省略该装置,可以用人工对程序进行插装。装置101的工作流程是:使用装置201将程序代码映射为结构对象,使用装置202统计程序路径,使用装置203对程序进行插装,使用用户提供的编译器编译并运行插装后的程序,使用装置204记录逻辑覆盖信息,使用装置205计算逻辑目标的近似测试用例。
下面详细描述图2所示的装置201,首先详细描述所述的结构对象,结构对象用于描述程序的逻辑结构。
本实施例把程序逻辑结构分为两种:顺序结构和分支结构。顺序结构对应于一个语句序列,在这个语句序列中,如果第一条语句执行了,那么在没有发生异常,也未因返回语句提前返回的情况下,其他所有语句也会执行,符合这一条件的所有连续的语句应视为属于同一个顺序结构而不能分拆,即不能把其中的一部分视为属于一个顺序结构,把另一部分视为属于另一个顺序结构。顺序结构具有一个入口点和一个出口点,其入口点为结构中的第一条语句,出口点为结构中的最后一条语句,但如果顺序结构中的某条语句是返回语句(如C++的return语句),执行到该语句时函数即返回,后面的语句不会执行,实际上,后面的语句是无效的,因此,该语句是该顺序结构的实际出口点。由于GOTO语句(如C++的goto语句)破坏了结构化设计并且在当今的开发中使用越来越少,因此,本实施例不考虑GOTO语句。
分支结构对应于一个语句序列,在这个语句序列中,含有判断和/或跳转语句或类似功能的语句,在不同的条件下,可能会执行或不执行或重复执行其中的某些语句。最典型的分支结构是选择结构,循环结构也属于分支结构。由于分支结构在不同的条件下会执行或不执行或重复执行某些语句,所以,分支结构至少包含两种可能的执行路线,这些执行路线叫做分支,也就是说,一个分支结构至少包含两个分支。为了简化问题,循环结构视为具有进入循环体和不进入循环体两个分支,不考虑循环的次数。
比较常用的选择结构有:
IF结构,如C/C++/Java的if(...).../eIse...;Pascal的if...then...else...;Basic的IF...THEN...END IF;该结构可含或不含ELSE分支,ELSE分支之前可以增加不限数量的ELSE IF分支。
SWITCH结构,如C/C++/Java的switch(...)...,Pascal的case...of...,Basic的SELECT CASE...,该结构可含多个CASE分支,可含也可不含DEFAULT分支。
比较常用的循环结构有:
FOR结构:如C/C++/Java的for(...)...,Pascal的for...do...,Basic的FOR...NEXT和FOR EACH...NEXT。
WHILE结构:如C/C++/Java的while(...)...,Pascal的while...do...,Basic的DO WHILE...LOOP,DO UNTIL...LOOP。
Do...WHILE结构:如C/C++/Java的do...while(...),Basic的DO...LOOPWHILE...,DO...LOOP UNTIL...,该结构至少执行循环体一次。
上述分支结构和编程语言的示例并非穷举,凡具有两个或两个以上执行路线的代码结构,都视为分支结构。
如上所述,循环结构视为具有进入循环体和不进入循环体两个分支,不考虑循环的次数,而Do...WHILE循环结构至少执行循环体一次,因此,它的不进入循环体的分支是不可达的。
分支结构也具有一个入口点和一个出口点,不论其内部的执行路线如何,在不发生异常的前提下,总是由入口点进入该结构,并由出口点离开该结构,这里有一个特例,如果结构内含有返回语句,执行到该语句时函数即返回,而不会从出口点离开该分支结构。
有些分支结构的某些分支是隐含的,如IF结构,如果最后没有ELSE分支,则包含一个隐含的分支,即使含有ELSE IF分支也是如此,此隐含的分支的执行条件和路线是:当IF和所有ELSE IF判定均为假时,直接跳转到结构的出口点。含有隐含分支的情况还有:循环结构的不进入循环体分支,该分支的执行条件和线路是:第一次进行循环条件判断时,该条件即为假,直接跳转到结构的出口点;SWITCH结构,当不含DEFAULT分支时,也含有一个隐含的分支,其执行条件和路线是:所有CASE分支的条件均不满足,不执行任一CASE分支,直接跳转到结构的出口点。
由于每一分支结构至少包含两条分支,可以说,分支结构是由分支构成的。分支包含两方面内容:分支判定和分支体,分支判定的计算结果一般只有真和假两个值。判定为真时,执行分支体,否则不执行分支体。隐含分支的分支判定和分支体都是空的。对于某一分支来说,其分支体具有一定的范围,称为作用域,一般使用特定的符号来定义作用域的开始和结束,例如:C/C++用花括号({}),PASCAL用begin和end来指定作用域的开始和结束。作用域内的所有语句,不论其结构如何,数量多少,均属于该分支体,因此,一个分支的分支体可以包含一个或多个顺序结构,也可以包含一个或多个分支结构,我们称为子结构,子结构又可以包含自己的子结构。
综上所述,本实施例把程序逻辑结构分为分支结构和顺序结构,一个分支结构至少包含两个分支,每一分支又可以进一步包含顺序结构和/或分支结构,我们把这种包含称为嵌套,嵌套的层次和数量没有限制。分支结构和顺序结构均有一个入口点和一个出口点,程序执行到任一结构时,在未发生异常的情况下,总是从入口点进入该结构,从出口点离开该结构,除非因执行到返回语句而提前返回。
本实施例用结构对象来对应描述上述程序逻辑结构:用分支树对象描述分支结构,用分支对象描述分支结构中的一个分支,用语句块对象描述顺序结构。其中,分支树对象嵌套至少两个分支对象,分支对象可以嵌套任意数量的分支树对象和/或语句块对象,嵌套的层次和数量没有限制,语句块对象不嵌套其他对象。另外,本实施例把目标程序的整体视为一个分支,称为顶层分支,用一个顶层分支对象来描述,该顶层分支对象与其他分支对象不同的是,它不嵌套于其他对象。后文所述“分支对象”,包括顶层分支对象和其他层次的分支对象。我们把分支树对象、分支对象、语句块对象统称为结构对象。
如果对象A嵌套了对象B,我们把对象A称为对象B的父对象,对象B称为对象A的子对象。从对象的内部数据的关系来说,对象A(父对象)嵌套对象B(子对象),是指对象A保存了对象B本身或对象A保存了对象B的指针或引用,或对象A保存了可以引用到对象B的其他数据,通过对象A可以引用对象B。语句块对象没有子对象,分支对象也可能没有子对象。顶层分支对象没有父对象,其他分支对象的父对象为分支树对象;分支树对象和语句块对象的父对象都是分支对象(包括顶层分支对象);我们把语句块对象的父对象称为该语句块所在的分支。
图3是在本实施例,上述各对象的内部数据构成的示意图及结构对象树示意图,其中图3A是语句块对象的内部数据构成示意图,图3B是分支树对象的内部数据构成示意图,图3C是分支对象的内部数据构成示意图,图3A到图3C中,实线画出的是固定的组成部分,点划线画出的是可有可无并且不限数量的组成部分,图3D是结构对象树示意图。
图3A是语句块对象的内部数据构成示意图,如图3A所示,语句块对象有一个代码字段,用于储存它所对应的程序代码,并有一个名称字段,用于识别不同的语句块对象,名称可以使用字母表示,如‘a’、‘b’、‘c’。
图3B是分支树对象的内部数据构成示意图,如图3B所示,分支树对象至少嵌套了两个分支对象,还可以嵌套更多的分支对象。分支结构中的每一分支实际上是该分支结构的不可分割的组成部分,没有这些分支,分支结构也就不存在,将分支独立出来作为一个对象来处理,仅是为了描述和实施上的简便,同时也更符合面向对象的思想。本实施例为对应于SWITCH结构的分支树增加一个保存外围代码的字段,称为外围代码字段,用于保存类似于switch(...)的外围代码,但不保存作用域开始符和结束符,我们把对应于SWITCH结构的分支树称为特殊分支树。
图3C是分支对象的内部数据构成示意图,如图3C所示,分支对象有一个分支判定字段用于储存它对应的分支判定,顶层分支没有对应的分支判定,因此该字段为空。分支对象可以嵌套任意数量的分支树对象和/或语句块对象,如果没有嵌套分支树对象和语句块对象,则该分支对象称为空分支对象,与语句块对象一样,分支对象还具有一个名称字段,用于识别不同的分支对象,名称可以使用字母表示,如‘a’、‘b’、‘c’。
上述结构对象构成了树状关系,称为结构对象树。图3D是结构对象树示意图,由图5A所示的代码映射获得的结构对象组成。
为了遍历上述每一结构对象,可对上述结构对象树进行后序扫描或前序扫描,后序扫描是指:从顶层分支对象开始计算,针对每一对象,先递归计算它的每一个子对象,子对象计算完成后,再计算该对象自身,由于语句块对象和空分支对象没有子对象,计算它们时直接计算自身,因此它们是递归终止条件。前序扫描与后序扫描相比,唯一区别在于,针对每一对象,前序扫描先计算该对象自身,再递归计算子对象。
下面进一步描述图2所示的装置201,该装置将程序代码映射为结构对象,包括:解析程序代码的逻辑结构,依据程序代码的分支结构生成对应的分支树对象及对应的分支对象,依据程序代码的顺序结构生成对应的语句块对象,并将所述分支树对象和所述语句块对象嵌套于对应层次的分支对象中,详细描述如下:
首先,将程序代码分解为代码片。所述代码片是指以结构特征来确定类别的代码单位,代码片类别有:判定、域符号、语句块,域符号又分为域开始和域结束。判定是指含有判断或跳转或类似关键字、用于控制其作用域内的语句执行或不执行的代码片,例如C++的if(...)、else if(...)、else、while(...)、switch(...)、caseN;Basic的IF...、ELSE IF...、ELSE、DO WHILE...。判定必须完整,例如,C++的if(...)是一个完整的判定,不论括号内的条件有多长,都属于该判定的组成部分,不能分拆。域符号是定义判定的作用域的代码,域开始紧接着判定,用于指定判定的作用域的开始,域结束用于指定判定的作用域的结束,典型的域符号如C++的“{”与“}”,PASCAL的“begin”与“end”;语句块是指对应于前文所述的顺序结构的代码序列,其中不能含有判定。位于某一代码片内的注释可忽略也可作为该代码片的一部分,位于代码片外的注释可忽略也可归于其后的代码片。图5B是代码片分类示例,如图5B所示,代码501、503、505对应的代码片为判定;代码502和507对应的代码片为域符号,其中代码502对应的代码片为域开始,代码507对应的代码片为域结束;代码504、506、508对应的代码片为语句块。
本实施例使用包含代码内容、类别、附加类别三个字段的数据结构来存储代码片,代码片保存在如链表或数组之类的数据容器中。分解代码时,针对判定类代码片,应判断是不是属于特殊分支树,及判断是不是属于一个分支树的后续分支,可根据判定的关键字分辨是否属于特殊分支树,如C++代码的switch关键字;后续分支也可根据判定的关键字来辨识,如针对C++代码,如果是else关键字引出的判定或else if关键字引出的判定,则属于后续分支,如果一个判定类代码片属于特殊分支树或后续分支(两者不可能并列),则记录在附加类别中。
有时,域符号是省略的,如C++代码,当作用域只有一条语句时,域符号是可省略的,再如,Basic的IF...THEN...结构中,THEN后不换行,其作用域为同一行的THEN后的语句,THEN是域开始,域结束省略;IF...THEN...END IF结构中,THEN和END IF是域开始和域结束,而IF...THEN...ELSE IF...THEN...ELSE...END IF结构中,ELSE IF之前省略了域结束,ELSE之前也省略了域结束。代码分解时,每个判定之后应保证具有匹配的域开始和域结束,凡域符号省略的,应补上相应的域符号,其代码为空。
分解代码时还要考虑隐含分支,当IF结构缺少ELSE分支,SWITCH结构缺少DEFAULT分支时,以及视为分支结构的循环结构,均含有隐含分支。应为隐含分支加上三个代码片:一个判定,一个域开始,一个域结束,它们的代码均为空。
不同的编程语言,代码分解依据的语法及关键字略有不同,具体分解过程可用编译技术中的词法分析和语法分析技术来实现,实现过程属于现有技术,在此不作详述。
完成代码分解后,进一步将代码片映射为结构对象。图4是本实施例将代码片映射为结构对象的流程示意图,如图4所示,401是输入的父对象,流程中所生成的分支树对象和语句块对象均嵌套于父对象中,父对象只能是分支对象。402是代码片队列,将代码片按代码的本来顺序,用先进先出的队列储存,称为代码片队列。后文所述的“弹出代码片”,是指从代码片队列中取出最前的一个代码片,并将该代码片从代码片队列中删除;后文所述“代码片”,当未作限定时,是指最新弹出的代码片;后文所述“压入临时队列”,是指将代码片保存到临时代码片队列的尾部。本实施例将图4所示的流程编写为一个递归调用的函数,用参数方式传递父对象和代码片队列,初始调用时,生成一个顶层分支对象作为父对象,代码片队列包含程序的所有代码片,当处理一个分支时,为了进一步处理分支体所对应的代码片,将该分支作为父对象,该分支的分支体对应的代码片保存在一个临时代码片队列中作为输入的代码片队列,进行递归调用。
步骤403判断代码片队列是否为空,如果为空则结束,否则执行步骤404,步骤404弹出代码片,在步骤405判断该代码片的类别,如类别为语句块,执行步骤411,如类别为判定,执行步骤421,如类别为域符号,由于此时不应该出现域符号,执行步骤490,步骤490报告错误并退出。
步骤411生成一个语句块对象,并嵌套在父对象中,本实施例在生成语句块对象时使用一个自动递增的字符变量作为每一个语句块对象的名称,如‘a’、‘b’、‘c’。步骤412保存代码,将代码片的代码内容拷贝到语句块对象的代码字段中,然后返回到步骤403处理下一代码片。
步骤421生成分支树对象,并嵌套到父对象中,在步骤422中根据代码片的附加类别判断该分支树是否属于特殊分支树,如果是特殊分支树,执行步骤431,否则执行步骤441。步骤431保存外围代码,将代码片的代码内容拷贝到特殊分支树对象的外围代码字段。步骤432判断代码片队列是否为空,如果为空则执行步骤490报错并退出,否则执行步骤433弹出代码片,并在步骤434判断该代码片的类别,如果类别为域开始,则返回执行步骤432(忽略外围代码的域开始,步骤472忽略外围代码的域结束),如果类别为判定则执行步骤441,如果类别不是域开始也不是判定,则执行步骤490报错并退出。
步骤441生成一个分支对象,嵌套于步骤421生成的分支树对象中,本实施例在生成分支对象时使用一个自动递增的字符变量作为每一个分支对象的名称,如‘a’、‘b’、‘c’。步骤442保存分支判定,将代码片的代码内容拷贝到该分支对象的分支判定字段。步骤443判断代码片队列是否为空,如果为空则执行步骤490报错并退出,否则执行步骤445弹出代码片,并在步骤446判断该代码片的类别,如果类别是域开始,则执行步骤447,否则执行步骤490报错并退出。步骤447生成一个用于判断域符号是否匹配的变量N并赋值为1,表示读到了一个域开始,步骤451判断代码片队列是否为空,如果为空则执行步骤490报错并退出,否则执行步骤452弹出代码片,并在步骤453中判断该代码片的类别,如果类别是域开始,则N加1,如果是域结束,则N减1,然后判断N的值是否为0,如果为0,表示对应的域结束已弹出,实现了域匹配,执行步骤462,否则在步骤454将代码片压入临时队列,并返回步骤451循环处理下一代码片。步骤462处理分支体代码,将步骤441生成的分支对象作为父对象,临时队列作为代码片队列,进行递归调用,然后清空临时队列。
步骤463判断代码片队列是否为空,如果为空则结束,否则在步骤471弹出代码片,并在步骤472判断该代码片的类别,如果类别是域结束,则返回执行步骤471,如果类别是判定,则执行步骤473,否则返回执行步骤405处理后续的代码片直到结束。步骤473根据代码片的附属类别判断该代码片是否属于上一分支的后续分支,如是则执行步骤441,否则表示当前的分支树已处理完毕,返回执行步骤405处理后续的代码片直到结束。
下面详细描述图2所示的装置202,该装置统计程序路径。路径是指程序的执行路线,本实施例所述的路径包括:语句组合和分支组合,也就是说,用语句组合和分支组合来描述路径。语句组合是指路径所执行的语句块的序列,由语句块名按路径的执行顺序组成;分支组合是指路径所经历的分支的序列,由分支名按路径的经历顺序组成;也就是说,语句组合记录路径执行的语句,分支组合记录路径经历的分支。我们把路径的语句组合中的语句称为路径的执行语句,把路径的分支组合中的分支称为路径的经历分支。另外,由于统计路径的需要,路径还分为两类:正常和返回,类别为返回的路径经过了一个含有返回语句的语句块,因此提前结束;类别为正常的路径在程序结束时结束。统计路径时,路径的缺省类别为正常,当一条路径经过一个含有返回语句的语句块时,将该路径的类别设为返回,该路径即结束。
装置202具体包括:后序扫描所述结构对象,针对每一结构对象,将一个输入路径集作为初始数据进行路径统计,顶层分支对象的输入路径集只含一条空路径;针对语句块对象,在输入路径集的每条路径中记录该语句块名;针对分支对象,在输入路径集的每条路径中记录该分支名,前一子对象的输出路径集作为下一子对象的输入路径集;针对分支树对象,将输入路径集的拷贝作为每一子对象的输入路径集。下面描述递归计算各结构对象时统计路径的详细流程:
图6是本实施例统计程序路径的流程示意图,其中,图6A是当对象为语句块时统计路径的流程示意图,图6B是当对象为分支时统计路径的流程示意图,图6C是当对象为分支树时统计路径的流程示意图。
图6A是当对象为语句块时统计路径的流程示意图。如图6A所示,步骤603扫描路径集,即扫描输入路径集601,针对每一条路径,在步骤604判断它的类别,如果类别为返回,则返回到步骤603处理下一条路径,否则在步骤605判断该语句块对象的代码是否含有返回语句,如果是,则执行步骤606将路径的类别设为返回再执行步骤607,否则直接执行步骤607,步骤607记录语句名,即在路径的语句组合的最后添加该语句块名。所有路径处理完成后即得到输出路径集602,输出路径集中的路径的数量与输入路径集一致。
图6B是当对象为分支时统计路径的流程示意图。当对象为分支时,依次计算该对象的子对象,前一子对象计算后的输出路径集,作为下一子对象的输入路径集,直到所有子对象计算完毕,最末子对象的输出路径集就是该分支对象的输出路径集。如图6B所示,步骤611记录分支,即在输入路径集的每条路径的分支组合的最后添加该分支名。步骤612扫描每一子对象,如果完毕或没有子对象则结束,否则执行步骤613。步骤613判断该子对象是不是语句块,如果是则执行步骤614,否则执行步骤615,步骤614递归执行图6A所示的计算,并将计算后的路径集作为下一子对象的输入路径集;步骤615递归执行图6C所示的计算,并将输出的路径集作为下一子对象的输入路径集。步骤614和步骤615执行后均返回到步骤612处理下一子对象直到结束。
图6C是当对象为分支树时统计路径的流程示意图。如图6C所示,步骤623剔除返回路径:将输入路径集621中类别为返回的路径拷贝到输出路径集622中,并从输入路径集621删除。步骤624扫描子对象(分支),读取一个分支后,步骤625将输入路径集拷贝一份作为该分支的输入路径集。步骤626针对该分支递归执行图6B所示的计算,然后返回到步骤624处理下一分支。所有分支计算完成后,步骤627将各分支的输出路径集汇总到整个分支树对象的输出路径集622中,计算结束。
顶层分支对象的输入路径集只含一个空路径,所述空路径是指语句组合和分支组合均为空的路径;顶层分支对象的输出路径集就是程序的路径集。路径集中的每条路径,均记录了该路径所执行的语句块对象序列和该路径所经历的分支对象序列。
当程序较复杂时,路径的数量会很庞大,要设计测试用例覆盖所有路径是不现实的。可以将一些特定的代码所对应的对象隐藏,统计路径时,忽略隐藏的对象及其直接或间接子对象,以便压缩路径的数量,例如,可以隐藏嵌套层次太深的对象。
下面详细描述图2所示的装置203,该装置对程序进行插装。插装是软件测试领域常用的技术,就是在被测试程序中插入代码,插装技术是现有技术,在此不对具体实施细节作详细描述,但不同的插装目的具有不同的插装函数和插装位置,下面详细描述本实施例的插装函数和插装位置。
插装函数就是插装代码调用的函数。本实施例插装的目的是监视语句或条件的执行状况,采用两个插装函数:语句监视函数和条件监视函数,语句监视函数用于监视语句的执行,在本实施例,语句监视函数在语句执行时传递语句的标识即语句块名,条件监视函数用于监视条件的计算或计算结果,在本实施例,条件监视函数在条件被计算时传递条件的值。我们把语句监视函数称为函数A,把条件监视函数称为函数B。本实施例可以为各种逻辑覆盖目标设计测试用例,对于语句覆盖、判定覆盖和路径覆盖,可以只使用函数A进行插装,对于条件式覆盖、条件值覆盖、条件值组合覆盖,则还应使用函数B进行插装。
函数A原形如下(伪代码):
VOID函数A(语句块名)
函数A可以没有返回值,只有一个参数:语句块名,用于识别所监视的语句块。插装时在各语句块对象对应的代码前面插入函数A的调用代码,该语句块名作为实参。
函数B原形如下(伪代码):
BOOL函数B(分支名,条件编号,条件值)
函数B返回一个布尔值,该返回值恒为真(TRUE)。三个参数:分支名,用于识别各分支;条件编号,用于识别一个分支里的各个条件;条件值,也是一个布尔值,有真和假两个取值。插装时,在各条件的前后两端分别插入函数B的调用代码,前端调用时,条件值实参为假,后端调用时,条件值实参为真;另外两个参数在前后端调用时一致,分支对象名作为第一个实参,分支中条件的编号作为第二个实参。调用函数B的代码和条件表达式用逻辑与相连,最好用括号把三者括起来。由于函数B总是返回一个真值,不会干扰判定及判定中各条件的计算过程和计算结果。
下面是插装的一个示例(伪代码,带下划线的为调用插装函数的代码):
插装前:
IF A>0AND B<=0THEN
语句块
END IF
插装后:
IF(
函数B(分支名,1,假)AND A>0AND
函数B(分支名,1,真))AND
(
函数B(分支名,2,假)AND B<=0AND
函数B(分支名,2,真))THEN
函数A(语句块名)
语句块
END IF
函数A运行时传递语句名,函数B运行时传递分支名、条件编号、条件值,因此,它们要实现的功能就是将这些信息发送给记录逻辑覆盖信息的装置。如果记录逻辑覆盖信息的装置属于另一个应用程序,传递数据时可以使用进程间数据交换技术,实现的途径有多种,如共享内存、网络、命名管道,也可以将数据保存到文件,由记录逻辑覆盖信息的装置主动读取,具体实现细节属于现有技术,在此不作详述。
为了运行被测试程序,需要有驱动程序,驱动程序设定输入数据,并调用被测试程序,使被测试程序得于执行。由于本实施例已有可执行测试用例,测试用例就构成了驱动程序,一般来说,一个测试用例运行被测试程序一次。例如下面的被测试代码(编程语言为C++,同样的方式适用于其他编程语言):
int CMyClass∷Add(int x,int y)
{
return x+y;
}
可以用下面的驱动代码来运行:
CMyClass obj1;
obj1.Add(0,0);
CMyClass obj2;
obj2.Add(1,1);
包括两个测试用例,使用不同的输入数据,分别驱动被测试程序执行一次。为了便于识别不同的测试用例,还可以在每个测试用例开始前向记录逻辑覆盖信息的装置发送一个消息。
对程序进行插装后,本实施例使用用户提供的编译器编译插装后的被测试程序及其驱动程序,得到可执行文件,运行可执行文件,即可运行被测试程序。
下面详细描述图2所示的装置204,该装置记录逻辑覆盖信息。被测试程序运行的时候,插装代码传递逻辑覆盖信息,装置204记录这些信息,具体包括:
记录语句覆盖信息:当一个语句块被执行时,函数A传递该语句块名,本实施例以一个测试用例所执行的语句块作为一组,记录语句块执行状况,称为语句覆盖记录,我们把一个测试用例所覆盖的语句块称为该测试用例的语句组合。
统计已覆盖路径及已覆盖路径的覆盖用例:被测试程序运行结束后,将路径的语句组合与各测试用例的语句组合进行比较,如果路径的语句组合是测试用例的语句组合的子集,则该路径已被该测试用例覆盖,称为已覆盖路径,该测试用例称为该路径的覆盖用例。由于路径统计时不考虑循环的次数,当循环结构里含有分支时,一个测试用例可能会覆盖多条路径,也就是说,一个测试用例可能是多条路径的覆盖用例。
如果插装时使用了函数B,还应记录条件覆盖信息。函数B传递的是条件执行时的条件值,针对每一条件,本实施例用集合(如数组或链表)来记录该条件在各个测试用例的执行结果,称为条件覆盖记录,每个测试用例占用一个集合项,各集合项的初始值为未覆盖,运行测试时,如果条件前端的函数B执行了而后端的函数B未执行,则记录的是前端函数B的条件值(假),如果两者都执行了,则用后端函数的条件值(真)改写记录值。对于一个分支,一个测试用例所记录的条件值的组合,称为该分支在该测试用例的条件值组合,或该测试用例的该分支的条件值组合。
下面详细描述图2所示的装置205,该装置计算逻辑目标的近似测试用例。逻辑目标就是所设计的测试用例意图覆盖的某种逻辑单位。例如,语句块、分支、路径、条件式、条件值、条件值组合,都可以作为逻辑目标,一般来说,逻辑目标是未覆盖的逻辑单位。
下面进一步说明逻辑目标。判定有两种计算结果:真和假,判定覆盖就是要覆盖每个判定的真和假两种可能的计算结果,由于取假分支也对应于一个分支对象,因此,判定覆盖就是覆盖所有分支对象,判定覆盖的逻辑目标是分支。
我们用一个分支作为示例,进一步说明与条件有关的逻辑目标,该分支的分支判定是:IF C1 AND C2 THEN。
条件式覆盖就是覆盖各分支的每个条件式,一个分支可能有一个或多个条件,例如:上述示例具有两个条件:C1和C2,条件式覆盖就是条件C1和条件C2均至少计算一次,不考虑计算结果。
每个条件有两种取值:真和假,我们用T来表示取真,用F来表示取假,并加一个下标表示条件的序号,如条件C1的取值表示为:T1、F1,条件C2的取值表示为:T2、F2。条件值覆盖就是要覆盖各分支的所有条件的所有取值,对于上述示例来说,就是要覆盖条件值T1、F1、T2、F2。
条件的取值有多种可能组合,上述示例的条件值组合有:T1T2、T1F2、F1T2、F1F2。列出条件值组合的方法属于现有技术,例如用多层循环可以得到分支的全部条件值组合,在此不作详述。一个条件值组合实际上包括两方面信息:分支的各个条件和每个条件的取值,例如,上述示例的条件值组合之一T1F2,是指条件C1取值为真,条件C2取值为假。在条件值组合中,有些条件是不可达的,本实施例将不可达的条件屏蔽。不可达条件就是程序执行时不计算的条件或者即使计算也没有意义的条件,例如在上述示例,如果条件C1的值为假,则条件C2是不计算的,因此,当条件C1为假时,条件C2是不可达的,我们用字母S来替换条件值组合中不可达的条件的取值,那么,上述示例的四个条件值组合:T1T2、T1F2、F1T2、F1F2应改为:T1T2、T1F2、F1S、F1S。屏蔽不可达条件可能会造成某些条件值组合重复,例如上述示例的条件值组合中,屏蔽不可达条件造成后两个条件值组合重复,我们把最后一个条件值组合删除,得到有效条件值组合:T1T2、T1F2、F1S。可用数理逻辑算法判断条件值组合中的不可达条件,属于现有技术,在此不作详述。条件值组合覆盖就是要覆盖各分支的所有有效条件值组合,对于上述示例来说,就是要覆盖它的三种条件值组合:T1T2、T1F2、F1S,其中,不可达的条件是可忽略的,实际上要覆盖的是:T1T2、T1F2、F1。后文所说的某一分支的条件值组合,均是指该分支的有效条件值组合。
装置205从已有的测试用例中计算近似测试用例,因此,近似测试用例是一个已有测试用例,该测试用例作最小修改即可覆盖所述逻辑目标,最小修改是指需修改的数据尽可能少。例如,一个被测试程序有三个参数,已有两个测试用例,逻辑目标是一个语句块,为了覆盖该语句块,测试用例1需要修改两个参数,测试用例2需要修改一个参数,则测试用例2就是该逻辑目标的近似测试用例。针对不同的逻辑目标,本实施例用相应的装置计算近似测试用例。图7是装置205的具体构成示意图,由于白盒测试通常不需要完成所有的逻辑覆盖,因此,装置205可以是图7所示的各装置的任意组合。
下面详细描述图7所示的装置701,该装置计算目标路径的近似测试用例:
逻辑目标是路径,我们把该路径称为目标路径,使用用于计算目标路径的近似路径的装置计算该目标路径的近似路径;目标路径的近似测试用例等于该目标路径的近似路径的覆盖用例。
用于计算目标路径的近似路径的装置具体包括:比较目标路径与各已覆盖路径的分支组合,重叠分支最多的已覆盖路径,就是目标路径的近似路径。重叠分支是从头开始的连续的相同分支。多条已覆盖路径重叠分支相同时,比较首个非重叠分支,首个非重叠分支在后优先和最接近优先,具体来说,比较首个非重叠分支,如果既有在后的候选路径又有在前的候选路径,则选择在后的候选路径;如果有多条在后的候选路径,则选择最前的;如果有多条在前的候选路径,则选择最后的,所述“在前”、“在后”,是指候选路径的首个非重叠分支与目标路径的首个非重叠分支相比较而言,所述“最前”、“最后”,是指候选路径的首个非重叠分支相比较而言,由于本实施例的分支名是自动递增的,并且所有首个非重叠分支均属于同一个分支树对象,比较分支名即可判别“在前”、“在后”与“最前”、“最后”。我们把目标路径的第一个非重叠分支称为阻塞分支,把近似路径的第一个非重叠分支称为临界分支。
下面详细描述图7所示的装置702,该装置计算目标分支的近似测试用例:
逻辑目标是分支,我们把该分支称为目标分支,用下述流程计算其近似测试用例:
选择所有经历目标分支的路径作为候选目标路径,即选择分支组合中包含目标分支的路径作为候选目标路径。
计算各候选目标路径的近似路径:针对每一候选目标路径,使用前述的用于计算目标路径的近似路径的装置计算其近似路径;
选择临界差最小的候选目标路径作为目标路径:计算各候选目标路径的临界差,临界差最小的候选目标路径为该目标分支的目标路径。临界差就是候选目标路径中目标分支的偏移与该候选目标路径的近似路径的临界分支的偏移的差,分支的偏移是指在一条路径中,该分支之前的分支的条数。例如:候选目标路径的分支组合为:abdfh,目标分支为f,其近似路径的分支组合为abekj,则候选目标路径中目标分支的偏移是3,近似路径的临界分支是e(第一个非重叠分支),其偏移是2,临界差为1。
目标分支的近似测试用例等于目标路径的近似路径的覆盖用例。
下面详细描述图7所示的装置703,该装置计算目标语句的近似测试用例:
逻辑目标是语句块,我们把该语句块称为目标语句,目标语句的近似测试用例等于该目标语句所在分支的近似测试用例。我们把目标语句所在的分支(即该语句块的父对象)称为目标分支,使用装置702计算目标分支的目标路径、近似路径、和近似测试用例,该目标分支的目标路径、近似路径、和近似测试用例就是目标语句的目标路径、近似路径、和近似测试用例。
下面详细描述图7所示的装置704,该装置计算目标条件值组合的近似测试用例:
逻辑目标是条件值组合,我们把该条件值组合称为目标条件值组合,使用用于计算目标条件值组合的近似测试用例的装置计算该目标条件值组合的近似测试用例。
用于计算目标条件值组合的近似测试用例的装置具体包括:
选择所有覆盖了目标分支的测试用例作为候选测试用例。目标分支是指目标条件值组合所在的分支。依据条件覆盖记录,如果一个测试用例的目标分支的条件值组合中,至少有一个条件的值为真或假,则该测试用例覆盖了目标分支。在完成了语句覆盖的基础上,符合条件的候选测试用例一定存在,如果不存在,应先完成语句覆盖。
选择相符条件值最多的候选测试用例作为目标条件值组合的近似测试用例。具体来说,针对每一候选测试用例,对比目标分支的该测试用例的条件值组合与目标条件值组合,相符条件值最多的候选测试用例为目标条件值组合的近似测试用例。
下面详细描述图7所示的装置705,该装置计算目标条件式的近似测试用例:
逻辑目标是条件式,我们把该条件式称为目标条件式,把目标条件式所在的分支称为目标分支,用下述步骤计算该目标条件式的近似测试用例:
选择所有目标条件式可达的条件值组合作为候选目标条件值组合:在目标分支的条件值组合中选出目标条件式可达的条件值组合,即目标条件式所对应的条件未被屏蔽的条件值组合作为候选目标条件值组合。
计算各候选目标条件值组合的近似测试用例:针对各候选目标条件值组合,使用前述的用于计算目标条件值组合的近似测试用例的装置计算该候选目标条件值组合的近似测试用例。
选择已覆盖的在前条件式最多的候选条件值组合作为目标条件值组合:计算各候选目标条件值组合的近似测试用例所覆盖的目标分支的在前条件的个数,最多者为目标条件值组合。在前条件个数就是在候选目标条件值组合中,目标条件式之前的条件的个数,不包括不可达条件;测试用例所覆盖的条件,是指目标分支在该测试用例的条件值组合中,条件值为真或为假的条件。
目标条件式的近似测试用例等于目标条件值组合的近似测试用例。
下面详细描述图7所示的装置706,该装置计算目标条件值的近似测试用例:
逻辑目标是条件值,我们把该条件值称为目标条件值,把目标条件值所在的分支称为目标分支,用下述步骤计算该目标条件值的近似测试用例:
选择所有包含目标条件值的条件值组合作为候选目标条件值组合:在目标分支的条件值组合中选出包含目标条件值的条件值组合,作为候选目标条件值组合。
计算各候选目标条件值组合的近似测试用例:针对各候选目标条件值组合,使用前述的用于计算目标条件值组合的近似测试用例的装置计算该候选目标条件值组合的近似测试用例。
选择已覆盖的在前条件值最多的候选目标条件值组合作为目标条件值组合:计算各候选目标条件值组合中已被近似测试用例所覆盖的在前条件值的个数,最多者为目标条件值组合。在前条件值就是在候选目标条件值组合中,目标条件值之前的条件值,不包括不可达条件;已被近似测试用例所覆盖的条件值,是指该条件值与近似测试用例的目标分支的条件值组合中相应条件值相符的条件值。
目标条件值的近似测试用例等于目标条件值组合的近似测试用例。
下面详细描述图1所示的装置102,该装置生成修改提示。修改提示用于指导对逻辑目标的近似测试用例进行修改,使修改后的测试用例可以覆盖该逻辑目标。修改提示由提示表达式构成,并分为两类:已满足条件和待满足条件,待满足条件是近似测试用例未满足的条件,依据待满足条件修改近似测试用例即可获得用以覆盖预期的逻辑目标的测试用例;已满足条件指出近似测试用例已满足的,并且修改后的测试用例仍然应满足的条件,如果待满足条件与已满足条件冲突,则该待满足条件是无效的,如果不存在有效的待满足条件,则该逻辑目标是无法覆盖的。待满足条件是修改提示的核心,已满足条件只是进一步限定的条件,并不是必需的。
所述提示表达式用于描述输入数据的范围或条件,包括:判定式、反判定式及条件式、反条件式。
首先描述判定式和条件式,判定式就是判定表达式,即判定中用于决定是否执行分支体的表达式,一个判定有一个判定式。条件式就是条件表达式,在一个判定中,有一个或多个条件,各条件具有条件式。判定和条件都只有两个计算结果:真或假。
我们用示例来进一步解释,如下面的分支判定:
IF A>0 AND B<0 THEN
A>0 AND B<0就是该判定的判定式,包含两个条件,条件1的条件式是:A>0;条件2的条件式是:B<0。
循环结构只关注是否会执行循环体一次,因此,循环结构的判定式是用于判断循环体是否至少会执行一次的表达式。WHILE结构的判定式比较容易理解,例如WHILE X>0 AND Y>0 DO...,判定式是X>0 AND Y>0,FOR结构比较复杂,有些FOR结构为了语法简洁,判定式常与其他表达式如初始化表达式、递增表达式混合,如C++的FOR结构:for(int i=0;i<10;i++)...,判定式是i<10;再如BASIC的FOR结构:FOR I=0 TO 10 THEN...,其判定式是I<=10,再如BASIC的FOR EACH Items DO...NEXT更特殊,判断循环体是否至少会执行一次的是Items中集合项是否不为空,因此可列出这样的判定式:Items.Count>0,由于判定式只是用于构成修改提示,而修改提示是一种提示信息,用户能理解并确定输入数据就行,因此,即使Items没有Count属性,这种判定式也是可行的。
反判定式就是判定式取反的结果,反条件式就是条件式取反的结果。取反就是将表达式中的等于操作符、关系操作符、逻辑操作符用相反的操作符替换,空表达式取反后仍为空,所述相反的操作符如:等于和不等于互为相反,大于和小于等于互为相反,小于和大于等于互为相反,逻辑与和逻辑或互为相反,逻辑非的相反操作符一般是省略的即空的。例如:
上述判定IF A>0 AND B<0THEN的反判定式是:A<=0 OR B>=0
条件1的反条件式是:A<=0
条件2的反条件式是:B>=0
再如,C++的判定if(C)...,其判定式是:C,反判定式是:!C,条件式是:C,反条件式是:!C。
本实施例使用装置201将程序代码映射为结构对象,由分支对象描述和处理判定,因此,我们把判定式、条件、条件式、反判定式、反条件式称为分支的判定式、条件、条件式、反判定式、反条件式。判定的取假分支也对应于一个分支对象,其判定式、条件、条件式、反判定式、反条件式均为空。例如,对于判定:IF A>0 AND B<0 THEN,如果没有ELSE IF分支,无论是否具有ELSE分支,均可对应于两个分支对象:分支a和分支b,分支b的判定式、条件、条件式、反判定式、反条件式均为空;分支a的判定式为:A>0 AND B<0,反判定式为:A<=0 OR B>=0;分支a具有两个条件,条件1的条件式是:A>0,反条件式是:A<=0,条件2的条件式是:B<0,反条件式是:B>=0。
下面详细描述本实施例计算各种分支对象的判定式的装置:
空分支或ELSE分支或DEFAULT分支或顶层分支的判定式为空。
DO...WHILE结构的两个分支的判定式均为空。由于DO...WHILE结构的两个分支中,第一个分支肯定会执行一次,第二个分支肯定是不可达的,它们的判定式在设计用于逻辑覆盖的测试用例时可忽略,因此把它们的判定式视为空。
CASE分支用SWITCH表达式与常量表达式组成等式作为判定式,例如C++的SWITCH结构:switch(X){case 0...case 1...case N...},可组合出各分支的判定式为:X==0/X==1/.../X==N。
FOR分支用条件表达式作为判定式,多个条件表达式用逻辑与连接,并可用条件变量的初始值代替该条件变量。例如,C++的FOR结构:for(int i=0;i<10;i++)...,条件表达式为i<10,条件变量为i,其初始值为0,可用条件变量的初始值代替该变量,因此,判定式为:0<10。再如Basic的FOR结构FOR I=0 TO 10THEN...,其中I=0 TO 10等价于C++的i=0;i<=10,可以得到判定式为0<=10。如果具有多个条件表达式则用逻辑与连接,如C++的FOR结构for(int i=0,int j=10;i<10,j>0;i++,j--)…,其判定式为:0<10&&10>0。本实施例将循环结构视为具有执行循环体和不执行循环体两个分支,循环的判定实际上只考虑第一次的计算结果,因此,当条件变量可确定初始值时,可用其初始值代替该条件变量。用条件变量的初始值代替该变量的好处是提高依据修改提示确定新的测试输入数据时的效率,例如:如果已满足条件为0<10,计算结果恒为真,确定测试输入数据时无须考虑该已满足条件,如果待满足条件为0>=10,计算结果恒为假,表示预期逻辑目标是无法覆盖的。
其他分支用判定中的条件表达式作为判定式,如C++的分支判定if(a<0&&B>0),其判定式为a<0&&B>0,再如Basic的分支判定IF A<0AND B>0 THEN其判定式为:A<0 AND B>0。
不同的编程语言有不同的语法,但所有判定都是通过计算出判定值来控制程序的执行路线,因此,所有分支都可以计算出判定式或列出可作为判定式的表达式,当然,有些分支的判定式为空。计算出分支的判定式后,即可依据判定式推出反判定式,条件式及反条件式,具体的计算过程属于现有技术,在此不作详述。
下面进一步详细描述装置102,图8是装置102的具体构成示意图,装置102可以包括图8所示的装置801和装置802的任意组合。
下面详细描述图8所示的装置801,该装置生成目标路径或目标语句或目标分支的近似测试用例的修改提示,具体包括:
针对目标路径的每一经历分支,依次选择该经历分支的在前姐妹分支的反判定式,及该经历分支自身的判定式。阻塞前或临界前属已满足条件,其他属待满足条件;已满足条件可忽略。目标路径是逻辑目标的目标路径,阻塞前是指:目标路径的阻塞分支之前,不包括阻塞分支,临界前是指:目标路径的近似路径的临界分支之前,包括临界分支,也就是说,据以选择提示表达式的分支中(包括目标路径的经历分支及经历分支的在前姐妹分支),如果出现了阻塞分支,从该阻塞分支开始,所选择的提示表达式就是待满足条件,或者,如果出现了目标路径的近似路径的临界分支,从该临界分支的下一分支开始,所选择的提示表达式就是待满足条件。目标路径、近似路径、阻塞分支与临界分支,在装置701、702和703中已作详细描述。由于已满足条件只是进一步限定的条件,因此,生成修改提示时可以忽略已满足条件。
上述生成的已满足条件由判定式或反判定式组成,如果分支的判定含有多个条件,那么已满足条件可能不够明确,影响修改提示的精确性。例如如下修改提示(C++语法):
已满足条件:a>0‖b<0
待满足条件:a==0‖x==1
仅从待满足条件看,把a的值改为0或把x的值改为1都可以得到新的测试用例,但已满足条件实际上有两种可能:a>0或b<0,如果a>0,那么把a的值改为0就破坏了已满足条件,新的测试用例将不能覆盖预期逻辑目标。为了得到更精确的修改提示,可以使用改进后的装置801,具体包括:
针对目标路径的每一经历分支,依次选择该经历分支的在前姐妹分支及该经历分支自身的提示表达式,其中,阻塞前或临界前的分支按下述步骤选择提示表达式:依据该分支在近似测试用例的条件值组合,选择取真值的条件的条件式,取假值的条件的反条件式,忽略不可达条件;其他分支按下述步骤选择提示表达式:在前姐妹分支选择反判定式,经历分支自身选择判定式。阻塞前或临界前属已满足条件,否则属待满足条件;已满足条件可忽略。近似测试用例是指逻辑目标的近似测试用例。使用改进后的装置801生成的上述修改提示,有两种可能结果:
已满足条件:a>0
待满足条件:a==0‖x==1
在这种情况下,只能把x的值改为1来得到符合预期的测试用例;
已满足条件:b<0
待满足条件:a==0‖x==1
在这种情况下,把a的值改为0或把x的值改为1都可以得到符合预期的测试用例。
依据装置801(包括改进前和改进后的装置801)生成的修改提示对近似测试用例进行修改,可得到可以覆盖预期逻辑目标的测试用例,但这种修改提示比较复杂,待满足条件可能较多,修改测试用例的工作量有时较大,为了克服这些缺点,装置801可以进一步包括:忽略阻塞分支之后的分支。也就是说,在选择了阻塞分支的判定式后即结束计算,忽略后面的分支。这样,待满足条件很少,修改测试用例的工作量很小,通常只需修改一个数据,但该测试用例覆盖的可能不是预期的逻辑目标,例如,当逻辑目标是一条路径时,实际覆盖的可能是另一条经过阻塞分支的路径,但也达到了提高路径覆盖率的目的,同时,逻辑目标可以随便选择,每次用最小的工作量提高逻辑覆盖率,具有很高的效率。
下面详细描述图8所示的装置802,该装置生成目标条件值组合或目标条件式或目标条件值的近似测试用例的修改提示,具体包括:
选择一条经历目标分支的已覆盖路径作为目标路径,针对该目标路径的每一前导分支,依次选择该前导分支的在前姐妹分支的反判定式,及该前导分支自身的判定式;针对目标条件值组合,依次选择取真值的条件的条件式,取假值的条件的反条件式,忽略不可达条件。目标分支是指逻辑目标所在的分支,也就是目标条件值组合所在的分支,目标条件值组合在装置704、705和706已作详细描述。选择一条经历目标分支的已覆盖路径作为目标路径,是指从已覆盖路径中选择一条分支组合包含了目标分支的路径作为目标路径,如果已完成语句覆盖,目标路径一定存在。
与装置801类似,为了得到更精确的修改提示,可以使用改进的装置802,具体包括:
选择一条经历目标分支的已覆盖路径作为目标路径,针对该目标路径的每一前导分支,依次选择该前导分支的在前姐妹分支及该前导分支自身在近似测试用例的条件值组合中取真值的条件的条件式,取假值的条件的反条件式,忽略不可达条件;针对目标条件值组合,依次选择取真值的条件的条件式,取假值的条件的反条件式,忽略不可达条件。
装置802(包括改进前和改进后的装置802)还可以进一步包括:忽略目标条件及目标条件后的条件。目标条件是指:对于条件值组合覆盖,无目标条件,对于条件式覆盖,目标条件式对应的条件为目标条件,对于条件值覆盖,目标条件值对应的条件为目标条件。对于条件式覆盖和条件值覆盖来说,目标条件式或目标条件值对应的条件及其后的条件的取值对于是否覆盖该条件式或条件值无影响,因此选择提示表达式时可以忽略这些条件。
生成的修改提示中,针对目标路径选择的提示表达式属已满足条件;针对目标条件值组合选择的提示表达式,条件值已被近似测试用例所覆盖的条件对应的提示表达式属已满足条件,其他属待满足条件。已被近似测试用例所覆盖的条件值,是指该条件值与近似测试用例的目标条件值组合所在分支的条件值组合中相应条件值相符的条件值。已满足条件可忽略。
为了得到更简洁的修改提示,可以使用化简修改提示的装置对修改提示进行化简,即删除或合并修改提示中语义重复或矛盾的部分。化简一般以一组姐妹提示表达式为单位。依据路径的一个经历分支及其在前姐妹分支生成的提示表达式,称为一组姐妹提示表达式。示例一:C++的SWITCH结构:switch(X){case0...case 1...case 2...},各分支的判定式为:X==0/X==1/X==2,如果路径经历了第三分支,则依据该经历分支及其在前姐妹分支生成的提示表达式为:X!=0/X!=1/X==2,显然,前两个提示表达式是多余的;示例二:C++的IF结构,if(X>=1000)...else if(X>=100)...else if(X>10)...else...,如果路径经历了第四分支,则依据该经历分支及其在前姐妹分支生成的提示表达式为:X<1000/X<100/X<=10,显然,前两个提示表达式是多余的。可以用字符串对比的方法进行粗略的化简:从后向前依次扫描各提示表达式,如果某一变量或表达式是在后提示表达式中某一等式的左操作数,则删除在前提示表达式中以该变量或表达式为左操作数的等式或不等式,所述等式是指用等于操作符连接左操作数和右操作数的表达式,所述不等式是指用不等于操作符连接左操作数和右操作数的表达式,如示例一;如果某一变量或表达式是在后提示表达式中某一关系式的左操作数,则删除在前提示表达式中以该变量或表达式为左操作数的同符号的关系式,所述关系式是指用关系操作符如大于、小于、大于等于、小于等于连接左操作数和右操作数的表达式,所述同符号是指关系操作符相同,小于和小于等于、大于和大于等于可以视为同符号,如示例二。使用词法分析和语法分析技术解析提示表达式的语义,然后删除或合并多余的表达式,可以进行精确的化简,具体实施步骤属于现有技术,在此不作详述。
修改提示由提示表达式构成,在多数情况下,依据修改提示可以确定新的测试数据,但修改提示并未描述代码间的依赖关系,例如下面的伪代码:函数名(X)
X=X+1
IF X<1THEN
目标语句
END IF函数结束
为了覆盖目标语句,修改提示包括提示表达式:X<1,根据该提示表达式,参数X可以取值为0,显然,由于代码间的依赖关系,X为0并不能覆盖目标语句。为了反映代码间的依赖关系,修改提示还可以包括提示代码,所述提示代码就是目标路径所执行的代码,目标路径是指装置801或装置802所述的目标路径。如前所述,路径具有语句组合,语句组合就是路径所执行的语句块对象序列,语句块对象保存了相应的代码,路径的语句组合所对应的代码就是路径的执行代码。一般来说,提示代码只需包括目标路径的前导代码,如果逻辑目标是语句、分支或路径,前导代码是指阻塞分支之前的代码,如果逻辑目标是条件式、条件值或条件值组合,前导代码是目标分支之前的代码,目标分支就是逻辑目标所在的分支。判断前导代码的方法是:扫描目标路径的语句组合,针对每一语句块对象,如果它的直接或间接父分支有一个是阻塞分支或目标分支,则该语句块对象就是前导代码的终结点,即前导代码是指路径的语句组合中该语句块对象之前的语句块对象所对应的代码。语句块对象的直接或间接父分支,是指该语句块对象的直接或间接父对象中的分支对象。
下面详细描述图1所示的装置103,该装置用于统计逻辑覆盖,即针对某种逻辑覆盖,统计其已覆盖部分和未覆盖部分。装置103所获得的统计结果,可用以显示已有测试的逻辑覆盖率,也可以列出未覆盖的逻辑目标供用户选择。
装置103具体包括下述装置的任意组合:
用于统计路径覆盖的装置,即统计已覆盖路径和未覆盖路径,具体步骤已在装置204中描述,不再重复。
用于统计语句覆盖的装置,语句覆盖就是覆盖装置201所获得的所有语句块对象,语句覆盖记录中所记录的语句均为已覆盖语句,其他为未覆盖语句。
用于统计分支覆盖的装置,已覆盖的路径所经历的分支,就是已覆盖分支,即已覆盖路径的分支组合中的所有分支都是已覆盖分支,所有已覆盖路径均未经历的分支为未覆盖分支。
用于统计条件式覆盖的装置,依据条件覆盖记录,如果该条件的各测试用例的值均为未覆盖,则该条件式未覆盖,否则该条件式已覆盖。
用于统计条件值覆盖的装置,依据条件覆盖记录,如果该条件在各测试用例的值至少有一个为真,则该条件的取真值已覆盖,否则未覆盖;用同样的方法统计条件的取假值。
用于统计条件值组合覆盖的装置,依据条件覆盖记录,各测试用例的条件值组合为已覆盖条件值组合,条件覆盖记录中未记录的条件值组合为未覆盖。
图9是本发明的一个实施例的屏幕图,如图9所示,901是近似测试用例,902是修改提示,其中903是待满足条件,904是已满足条件。如图9所示,待满足条件有两个条件式(C++语法):A==2和X>1,两者之间是逻辑或的关系,可以任选其一,但由于条件式A==2与已满足条件A<=1冲突,即如果把近似测试用例的输入数据中A的值改为2,将破坏已满足条件,所以条件式A==2不能使用;而条件式X>1不会与已满足条件冲突,因此,把近似测试用例的输入数据中,X的值改为大于1的数,即可获得可覆盖预期逻辑目标的测试用例。
以上实施例仅是本发明的较佳实施方式,仅用以说明发本明而非限制,对本发明进行修改、变形或者等同替换而不脱离本发明的精神和范围,均应涵盖于本
发明的范围之内。