摘要: 航空发动机控制软件在升级过程中使用 Cantata 工具开展单元测试活动时,存在着未变更函数的 Cantata 测试脚本需重新人工隔离插桩,致使时间和人力耗费的问题。通过研究 Cantata 自动生成测试脚本的过程及插桩特点,提出了一种基于 C#的 Cantata 工具变更过程改进方法。该方法通过 C#语言结合正则表达式进行代码分析,识别出升级过程中的变更函数和全局变量,并按照 Cantata 插桩格式,自动完成测试脚本更新工作。详细介绍了该方法的设计过程,并在某型航空发动机控制软件升级过程中进行实践应用。实践结果表明,该方法可准确识别源码信息并完成变更前后的差异比对,能正确快速实现未变更函数的自动隔离插桩工作,有效解决了人力和时间消耗的问题,对回归测试效率有极大提升。
本文源自测控技术2021-02-01《测控技术》杂志,月刊,于1982年经国家新闻出版总署批准正式创刊,由中国航空工业集团有限公司主管,中国航空工业集团北京长城航空测控技术研究所主办的学术性刊物,本刊在国内外有广泛的覆盖面,题材新颖,信息量大、时效性强的特点,其中主要栏目有:综述、航空装备保障与维修技术专题、数据采集与处理等。
关键词: DO-178C; 嵌入式系统; 单元测试; C#
航空发动机控制软件( 简称控制软件) 是一种嵌入式软件,根据《民航机载软件适航标准 DO-178》的规定,属于安全关键软件[1]。为满足适航认证,控制软件必须要达成 DO-178C 提出的各项指标要求。其中,针对低层需求与高层需求的符合性这一目标,执行单元测试并建立追溯关系是一种常见的实现手段。
针对嵌入式软件开发环境和运行环境不一致的现象,单元测试具有可在宿主机环境下执行、能提早测试介入时机且最大程度降低测试活动对目标环境依赖性等优点; 然而,单元测试也存在着的测试驱动难编写、测试程序难管理、测试结果难界定等问题。对此,市场上出现了一批如 Cppunit、Jnuit 等开源测试框架[2]和 cantata、TestBed、Qt、TBrun 等单元测试软件[3 - 5]。
其中 QA Systems 公司推出的 cantata 单元测试工具,可提供基于 DO-178B 的覆盖率分析,目前已在国内外航空航天软件的单元测试活动中得到广泛应用[6 - 10]。该工具针对 C /C + + 语言,通过使用 EGT 分析器提取源码信息,结合插桩器和自动封装技术,实现测试脚本一键生成; 通过提供测试用例管理器,实现测试用例便捷管理; 通过运行结果自动比对、覆盖结果树形分析技术,缩短测试验证时间,确保验证结论的正确性和完整性。
然而,由于 cantata 工具在执行单元测试活动时所有过程均需基于服务器执行,而且工具对被测单元的隔离插桩过程与被测单元所属文件有直接关联。这就导致软件升级时,若被测单元所属文件发生变化,即使被测单元未发生更改,原有的单元测试用例也需要重新隔离插桩才能通过。由于隔离插桩过程需要人工操作,另外受到服务器响应和处理时间长的制约,在实际执行控制软的 cantata 单元测试回归时,需要耗费大量时间和人力来更新未变更函数的隔离插桩。
目前,国内外的相关研究文献,主要集中在对 cantata 单元测试方法和工具使用的介绍说明[6 - 10]。针对本文提出的这一问题,尚未有具体解决方案。笔者提出了一种基于 C#的 cantata 工具变更过程改进方法。通过分析 cantata 工具的单元测试插桩结果,提炼出工具的插桩规则,进而结合 C#与正则表达式,完成源代码变更前后差异分析,并依照提炼的插桩规则,自动修改测试用例管理器中的测试脚本和被插桩后的代码,实现未变更函数的自动隔离插桩。从而解决人工操作繁琐,解决隔离插桩过程对 cantata 服务器强依赖的问题。
1 基于 C#的 cantata 变更过程改进方法
1. 1 整体方案分析
cantata 工具执行单元测试过程主要由源代码隔离插桩、测试脚本生成、可执行文件构建和运行这 4 个活动组成,各活动均由服务器执行并将相应结果返回给用户,具体如图 1 所示。
由于 cantata 工具使用 c 文件作为自动封装的最小单位( 一个自动封装可包含多个 c 文件) ,当被测单元所处自动封装中的 c 文件出现全局变量、函数或外部函数调度变化时,即使被测函数不存在任何变更,也必须更新自动封装,重新执行隔离插桩,才能确保单元测试用例执行通过。
cantata 测试用例管理器通过管理 test _FuncX. c、 test. mk 和 ipg. cop 这 3 个文件来管控生成的测试脚本和被插桩代码。
其中 test. mk 是测试脚本的 makefile 信息,用来指导 Build 构建器编译生成可执行文件; ipg. cop 是测试级别配置文件,描述需要被隔离的全局变量和函数,指导 cantata 工具自动生成被测函数 FuncX( ) 的隔离插桩信息; test_FuncX. c 是被测函数 FuncX( ) 的测试脚本,用来存储被测函数的环境定义、覆盖率分析方式、测试用例和隔离插桩接口等信息。
据此,在进行工具二次开发时,通过执行代码分析,识别出变更后代码新增、删除的全局变量、函数及函数调用信息,再将这些信息按照 cantata 单元测试的格式要求,更新到 test_FuncX. c、test. mk 和 ipg. cop 中去,实现对未变更单元的测试脚本和被插桩代码的更新。二次开发后的 cantata 单元测试原理如图 2 所示。由图2 可以看出,基于 C#的 cantata 工具二次开发可以脱离对服务器的依赖,自动识别源码的变更信息,完成测试脚本和被插桩代码的修改。
1. 2 cantata 插桩规则提炼
通过分析 cantata 单元测试结果,对 cantata 插桩规格进行了提炼。图 3 展示了某待测试文件 XX. c 的文件结构,包含有全局变量 GLB _ a、函数 FuncX( ) 和 FuncY( ) ,其中 FuncY( ) 调用了一个外部函数 UT_a ( ) 。
当 FuncX( ) 作为被测函数时,cantata 工具对 test_ FuncX. c、test. mk 和 ipg. cop 文件插桩规则如下。
test_FuncX. c 中,待隔离函数 FuncY( ) 的插桩规则如下函数隔离规则: / * Iaolate for function FuncY * / void ISOLATE_FuncY( void) { REGISTER_CALL( “FuncY”) ; IF_INSTANCE( “default”) { return; } LOG_SCRIPIT_ERROR( “Call instance not defined. ”) ; Return; }
② test_FuncX. c 中,全局变量 GLB_a 量的隔离规则: / * Global data * / int GLB_a; / * Expected variables for global data * / int expected_GLB_a; static void initialse_global_data( ) { TEST _ SCRIPT _WARNING( “Verify initialse _global _ data ( ) \ n”) ; INITIALISE( GLB_a) ; } static void initialse_expected_global_data( ) { TEST _ SCRIPT _ WARNING( “Verify initialse _ expected _ global_data( ) \ n”) ; COPY_TO_EXPECTED( GLB_a,expected_GLB_a) ; } static void check_global_data( ) {TEST_SCRIPT_WARNING( “Verify check_global_data( ) \ n”) ; CHECK_MEMORY( “GLB_a”,&GLB_a,&expected_GLB _a,sizeof( expected_GLB_a) ) ; }
ipg. cop 中,待隔离函数 funcY( ) 和 UT_a( ) 的插桩规则: “- - sm: - - isolate: FunY( ) ” “- - sm: - - isolate: UT_a( ) #FunY( ) ” ④ ipg. cop 中,全局变量 GLB_a 的隔离规则: “- - sm: - - access_variable: ”XX. c”: GLB_a”
1. 3 二次开发方法设计
基于 C#进行 cantata 工具二次开发时,主要难点在于源代码分析和变更差异比对。对此,以文件为单位,设计了文件信息的数据结构,具体的文件信息类图如图 4 所示。数据结构通过对头文件引用、宏、数据结构、全局变量、函数声明、函数等信息进行分类存储,实现变更差异的快速识别和比对。
文件信息提取流程如图 5 所示。为便于使用正则表达式提取源码中的有效信息,首先需对源码进行规格化处理,具体为剔除源码中由条件编译忽略的代码、注释代码、不规范和冗余的空格信息。由于条件编译的判断条件多使用宏信息,故需先对源码进行一次宏定义分析,再按照定制的形式进行规格化处理,导出规格化后的源码。
源码规格化后,按照各数据结构类型特点设计相应的正则表达式,依次提取头文件引用、宏定义、基本数据类型、特殊数据类型、函数声明、全局变量、函数信息,完成源码的文件数据结构提取。其中特殊数据类型特指枚举、位域结构体和结构体类型,另外考虑到同义宏的存在,设计了递归方法执行同义宏的分类和存储。
完成变更前后源码的文件信息提取后,以文件为单位采用循环遍历的方式,判断并记录对应文件中所有全局变量、函数及函数调用的变更状态( 共设计 3 种状态: 增加、删除、无变化) 。依据记录的变更状态,按照 cantata 隔离插桩格式要求,更新用例管理器中未变化函数的单元测试用例脚本,实现未变更部分的自动隔离插桩。
2 项目应用实践与结果分析
在某项目升级过程中,应用基于 C #的 cantata 工具二次开发方法。通过选中变更前后源码及 cantata 测试用例管理器的位置,一键运行后,完成受升级影响的非变更测试用例隔离插桩的自动修改。具体运行界面如图 6 所示。
更改结果显示,此次变更前后源码共涉及 17 个文件、9 个 全 局 变 量、81 个 函 数 的 变 更。使 用 Beyond Compare 工具比对变更前后源码并人工分析,结果显示与 C #的 cantata 工具二次开发方法提取的结果一致,信息提取功能和变更比对功能正常。
此次变更前共计有 436 个单元测试脚本,变更前后共影响到 123 个测试脚本的关联修改,修改量占比 28. 2% 。以其中一个关联修改的测试脚本为例,进行分析:
task. c 文件共有 5 个函数,比对变更前后的源码,其中仅 task_bigLoop( ) 函数里新增了函数调度 ISM _ Excute25ms( ) ,同步会影响该 c 文件中其他 4 个未变更函数测试脚本的隔离插桩。观察分析对应未变更函数自动修改后的测试脚本可见,测试脚本中均按照格式要求完成了脚本修改,具体结果如图 7 所示。
完成 81 个变更函数对应的测试脚本修改后,在 Score 环境下批跑所有的 444 个测试脚本,导出结果如图 8 所示。结果显示所有自动隔离插桩的函数均通过,其中 11 个未通过的函数均为特殊实现原因导致覆盖率无法满足的函数,与自动隔离插桩过程无关。
基于 cantata 服务器进行人工手动隔离插桩时,平均每个测试脚本需花费大约 10 min。使用二次开发方法后,平均只需要不到 3 min 即可完成所有未变更函数测试脚本隔离插桩工作。以本次 123 个测试脚本的关联修改为例,二次开发方法可有效节省约 20. 45 人时,测试工作效率有极大提升。
综上结果证明,基于 C#的 cantata 工具二次开发方法可准确识别变更前后的源码信息并完成差异比对,能正确并快速实现未变更函数的自动隔离插桩工作。
3 结束语
通过分析航空发动机控制软件升级过程,在依赖 cantata 工具进行单元测试回归时,存在未变更函数的测试脚本需重新人工手动隔离插桩,导致时间和人力耗费的问题。提出了一种基于 C#的 cantata 工具二次开发方法,项目实践与分析结果表明,该方法能准确识别变更信息,正确并快速实现未变更函数的自动隔离插桩。极大提升了基于 cantata 进行升级过程的单元测试效率,为达成 DO-178C 中低层需求与高层需求的符合性这一目标提供了有力支撑。
目前基于 C#的 cantata 工具二次开发方法已在 3 个项目的 5 次升级过程中得到应用,结果均正确可靠。但相较于市面上常见的源码分析工具( 如 Eclipse CDT 提供的 API) ,本方法尚不支持函数内部语法分析,也未与同类型代码分析工具进行优劣比对分析,可作为后续研究的一个方向。
论文指导 >
SCI期刊推荐 >
论文常见问题 >
SCI常见问题 >