Page 171 - 《软件学报》2026年第7期
P. 171
2856 软件学报 2026 年第 37 卷第 7 期
获取有多种方案, 包括回滚历史版本、挖掘开发分支、实施突变策略等. 历史版本是在程序开发过程中保留的过
往版本, 其典型用途是在程序的较新版本出现严重故障时进行回滚, 因此, 历史版本可能与被测程序相近并有可能
将错误程序上的失败测试用例转变为通过, 进而提供失败测试用例的通过执行. 开发分支通常在开发者尝试为程
序开发新功能或修复已有故障, 且不希望对现有开发活动造成干扰时被创建. 对于与待测错误程序不同的其他开
发分支, 同样有可能将错误程序上的失败测试用例转变为通过, 因此可以作为伪正确版本. 通过近期历史版本或不
同开发分支获取伪正确版本不会引入额外开销, 且上述两类渠道在实际程序开发中往往较容易得到 (例如, 开源项
目 Gson 具有超过 2 000 个历史版本, 开源项目 FastJson 具有超过 4 000 个历史版本和 10 个活跃的开发分支). 当
通过历史版本或不同开发分支无法获取失败测试用例对应的伪正确版本时, 实施突变策略为待测错误程序生成若
干变体, 将第 1 个把失败测试用例的执行结果转为通过的变体作为伪正确版本.
值得注意的是, 尽管伪正确版本在收集异常触发信息方面可以有效替代原始无缺陷版本, 根据观察, 在其上收
集的传统信息 (覆盖信息等) 却可能和在原始无缺陷版本上收集的存在显著差异. 因此, 伪正确版本生成后难以用
于基于传统信息的缺陷定位技术. 此外, INSPECT 方法作为一种轻量级的缺陷定位技术, 其各步骤的设计均尽量
避免不必要的运行开销. 在方法执行过程中, 一处可能的较高开销点在于如上所述的采用基于突变的方法生成伪
正确版本. 但是, 这是在通过历史版本或不同开发分支均无法获得伪正确版本时的兜底方案, 而实际上, 历史版本
和开发分支是软件演化与团队协作过程的产物, 在实际面向真实开源工程的缺陷定位任务中, 该类资源可以较为
容易地获取, 因此, INSPECT 的执行开销与传统基于覆盖信息的缺陷定位技术基本持平. 即便需要采用基于突变
的方法来生成伪正确版本, INSPECT 的整体开销也仅会上升至与传统 MBFL 技术相似的水平.
2.3 异常触发流收集及分歧点确定
2.3.1 异常触发流收集
为了更加全面地反映程序运行的中间状态, INSEPCT 按测试用例在执行时的实际顺序收集异常触发信息和
执行跟踪两类数据, 将二者合称为“异常触发流”, 作为后续开展缺陷定位的源信息 (即, 异常触发信息是异常触发
流的一个子部分). 异常触发信息包括对应检查点的位置以及在检查点上是否触发异常, 执行跟踪信息包括测试用
例执行经过的每一条程序语句. 异常触发流的结构如表 1 所示, 其中, 蓝色字体表示异常触发信息, 黑色部分表示
程序执行跟踪.
表 1 异常触发流结构
独立受检 异常触发流
- Statements_Begin_1
1 Begin: Checkpoint#i
- Statements_End_1
1 End: Checkpoint#i_triggeredOrNot
- Statements_Begin_2
2 Begin: Checkpoint#i
- Statements_End_2
2 End: Checkpoint#i_ triggeredOrNot
… …
- Statements_Ending
在异常触发信息中, “Begin: Checkpoint#i”表示程序执行进入所植入的检查点 Checkpoint#i (对应图 2 中的第 3
行), 以“End: Checkpoint#i”为开头的行表示程序执行退出检查点 Checkpoint#i (对应图 2 中的第 10 行), 其中 i 是每
个所植入检查点的唯一标识. 考虑到单个检查点 (Try-catch 块) 可能在一次程序执行中被多次进入, 采用 “独立受
检”区分程序执行过程中每次进入的检查点, 例如, 在表 1 中, “独立受检 1”“独立受检 2”分别表示程序执行进入的
第 1、2 个检查点, 依此类推 (“-”表示对应待测程序自身语句, 非独立受检), 每个独立受检对应的 Checkpoint#i
可能相同或不同. 在程序退出检查点时, 会在“End: Checkpoint#i_ triggeredOrNot”行中以变量 triggeredOrNot (对应

