基于Defects4J的多故障程序搜索与适应度函数优化研究
在软件开发过程中,多故障程序的检测与修复以及自动化程序修复(APR)中的适应度函数优化是重要的研究方向。本文将围绕如何在Defects4J中搜索多故障程序,以及比较传统布尔适应度函数和细化非布尔适应度函数在搜索式自动化程序修复中的效果展开探讨。
1. 在Defects4J中搜索多故障程序
1.1 故障示例分析
在Defects4J中,多数故障版本是按修订日期按时间顺序索引的,较低的ID表示最近修复的故障。例如,Math - 5比Math - 6修复得晚,所以Math - 6的故障源代码版本(Math - 6b)可能包含Math - 5故障。
以下是Math - 5的开发者补丁和故障揭示测试用例:
// 开发者补丁(Listing 1.1)
--- a/src/main/java/org/apache/commons/math3/complex/Complex.java
+++ b/src/main/java/org/apache/commons/math3/complex/Complex.java
@@ -304,7 +304,7 @@
public Complex reciprocal () {
if (real == 0.0 && imaginary == 0.0) {
- return NaN;
+ return INF;
}
// 故障揭示测试用例(Listing 1.2)
public void testReciprocalZero () {
Assert.assertEquals(Complex.ZERO.reciprocal (), Complex.INF);
// Error message: junit.framework.AssertionFailedError : expected :<(NaN , NaN)> but was:<(Infinity , Infinity)>
}
在Math - 6b中,
Complex.java
的第305行包含Math - 5故障:
// Listing 1.3
304 if (real == 0.0 && imaginary == 0.0) {
305 return NaN; // Math - 5b
306 }
当将
testReciprocalZero
测试用例移植到Math - 6b时,测试失败并显示与Math - 5b相同的错误消息,表明Math - 6b至少包含Math - 5和Math - 6两个故障。
1.2 搜索多故障版本
为了构建多故障数据集,需要检查哪些故障存在于哪些先前的故障版本中。
-
搜索策略
:对于项目中的每个故障N,按从最新版本到较旧版本的顺序,依次检查故障是否存在于每个先前的故障版本BM(M.id > N.id)中。一旦N在BM中未被揭示,搜索就停止。例如,Lang - 3在Lang - [4,16]b中被揭示,但在Lang - 17b中未被揭示,此时搜索立即停止并开始下一个故障(Lang - 4)的搜索。
-
存在性检查
:为了确定故障N是否存在于BM中,将N的所有故障揭示测试移植到BM。当且仅当满足以下三个条件时,确认N存在于BM中:
1. 测试用例方法移植到的所有目标测试类文件在BM中存在。
2. 所有移植的测试用例成功编译并在BM上失败。
3. BM中的错误消息与BN中的相同。
如果故障N和M的故障揭示测试用例相互重叠,还需要在M的修复版本上执行N的故障揭示测试,以确保重叠的测试用例在没有M的情况下仍然因N而失败。
以下是搜索流程的mermaid流程图:
graph TD;
A[开始] --> B[选择故障N];
B --> C[选择先前故障版本BM(M.id > N.id)];
C --> D[移植N的故障揭示测试到BM];
D --> E{是否满足存在性条件};
E -- 是 --> F[确认N存在于BM];
F --> G{是否还有先前版本};
G -- 是 --> C;
G -- 否 --> H[选择下一个故障N];
H --> B;
E -- 否 --> H;
1.3 构建多故障主题
搜索完成后,得到一组对E,当且仅当N存在于BM中时,(N, M) ∈ E。对于Defects4J中的每个故障M,BM中发现的故障集F(BM)定义为F(BM) = {M} ∪ {N | (N, M) ∈ E}。如果|F(BM)| > 1,则BM是一个多故障主题。
1.4 实现细节
整个搜索过程被容器化并自动化。使用javaparser检测移植过程中目标测试方法的行范围。在Docker容器中,可以通过以下命令检出多故障版本:
python3.6 checkout.py Math - 1 - 2 - 3 - w /tmp/Math - 1 - 2 - 3
这样会检出与Math - 3b相同的源代码,并增加Math - 1和Math - 2的故障揭示测试用例。
1.5 搜索结果
- 多故障主题 :对五个项目的故障版本进行分析,结果显示在326个故障程序中,95.4%(311/326)实际上包含多个故障。其中,126个故障版本至少有10个故障,22个故障版本至少有20个故障。例如,Closure - 90b包含24个故障。
- 故障寿命 :计算每个故障的寿命,即从检测到该故障的最旧先前故障版本的日期到应用补丁的修订日期之间的天数。故障寿命从0天到超过三年不等,例如Lang - 41的寿命为1187天。这种寿命的差异表明在任何给定时间存在多个故障的可能性不容忽视。
2. 搜索式自动化程序修复中适应度函数的优化
2.1 自动化程序修复概述
自动化程序修复(APR)旨在自动修复有故障的软件,减少人工干预。搜索式APR通过对程序进行变异来迭代生成候选补丁,并使用适应度函数编译和测试每个候选补丁,以评估其是否能为测试套件中的所有输入产生预期输出。传统上,布尔适应度函数(仅考虑测试用例的通过或失败)被广泛使用,最近,更细化的非布尔适应度评估方法被提出。
2.2 背景:细化适应度函数的方法
-
ARJA - e
:ARJA使用多目标优化搜索,考虑补丁大小(f1)和加权失败率(f2)。加权失败率定义如下:
[f2(x) = \frac{|{t \in T_{pos} : x \text{ fails } t}|}{|T_{pos}|} + w * \frac{|{t \in T_{neg} : x \text{ fails } t}|}{|T_{neg}|}]
ARJA - e在此基础上进一步扩展,使用更细化的适应度函数:
[f2(x) = \frac{\sum_{t \in T_{pos}} h(x, t)}{|T_{pos}|} + w * \frac{\sum_{t \in T_{neg}} h(x, t)}{|T_{pos}|}]
其中,(h(x, t) = \frac{\sum_{e \in E(x, t)} d(e)}{|E(x, t)|}),d(e)是每个断言的平均断言距离。 - 2Phase :采用混合方法,当两个补丁产生不同数量的通过和失败测试时,选择失败最少/通过最多的补丁;否则,根据失败测试的断言距离总和来确定哪个补丁更优。
- GenProgNS :用解决方案的新颖性取代基于通过测试数量的适应度评估。使用布尔向量表示每个测试用例的结果,通过汉明距离计算新颖性,优先探索大搜索空间。
- Checkpoints :跟踪测试用例中变量的值,并与原始测试执行中的值进行比较,根据比较结果分配适应度。对于特定变体上的失败测试用例,如果它保持了原本通过的测试中变量的值,并改变了原本失败的测试中变量的值,则认为该变体更合适。
2.3 实验设计
本次实验旨在回答以下问题:与仅考虑布尔测试用例结果的传统适应度评估相比,使用细化适应度函数的搜索式APR效果如何?
为了回答这个问题,选择了ARJA和ARJA - e进行比较。ARJA使用传统的基于失败测试用例数量的适应度函数,ARJA - e使用“平滑”的非布尔适应度函数。
实验使用了Defects4J v2.0中的151个非弃用且之前未评估的故障。以下是Defects4J v1.0.1和v2.0中各项目的故障数量详情:
| Project | # Bugs (v1.0.1) | # Bugs (v2.0) | # Dep. | Total |
| ---- | ---- | ---- | ---- | ---- |
| Chart | 26 | 0 | 0 | 26 |
| Cli | 0 | 39 | 1 | 39 |
| Closure | 131 | 43 | 2 | 174 |
| Codec | 0 | 18 | 0 | 18 |
| Collections | 0 | 4 | 24 | 4 |
| Compress | 0 | 47 | 0 | 47 |
| Csv | 0 | 16 | 0 | 16 |
| Gson | 0 | 18 | 0 | 18 |
| JacksonDatabind | 0 | 112 | 0 | 112 |
| JacksonXml | 0 | 6 | 0 | 6 |
| Jsoup | 0 | 93 | 0 | 93 |
| JxPath | 0 | 22 | 0 | 22 |
| Lang | 64 | 0 | 1 | 64 |
| Math | 106 | 0 | 0 | 106 |
| Mockito | 0 | 38 | 0 | 38 |
| Time | 26 | 0 | 1 | 26 |
| JacksonCore | 0 | 26 | 0 | 26 |
| Total | 391 | 444 | 29 | 835 |
然而,实验过程中遇到了一些技术挑战:
-
ARJA的未记录要求
:ARJA分为核心模块和外部模块,外部模块的默认位置硬编码在源代码中,在不同工作目录执行会导致失败。虽然可以通过提供外部模块路径作为参数来解决,但这一点未记录。而且,当找不到外部项目时,ARJA既不失败/崩溃也不输出错误信息,会正常执行但无生成补丁。
-
Defects4J资源文件/脚本问题
:Defects4J提供的用于检出、编译和导出项目元数据的脚本有时会因各种原因失败。例如,使用Java 8结合Defects4J提供的Maven和Ant编译脚本编译Compress项目时,由于不支持Java 1.4源文件的编译而失败。
2.4 实验结果
实验结果显示,ARJA能够为6.6%(10/151)的故障找到补丁,ARJA - e能够为8%(12/151)的故障找到补丁。与使用Defects4J v1.0.1的先前工作相比,ARJA在v1.0.1中能为26.3%(59/224)的故障找到合适的补丁,ARJA - e能为47.3%(106/224)的故障找到补丁。这表明使用细化适应度函数的优势较小,并且可能存在工具对Defects4J先前版本数据集过拟合的问题。
综上所述,在Defects4J中可以通过系统的搜索策略找到大量多故障程序,而在搜索式自动化程序修复中,细化适应度函数的效果在新数据集上并不显著,可能需要进一步改进工具以避免过拟合问题。
基于Defects4J的多故障程序搜索与适应度函数优化研究
3. 多故障程序搜索结果的深入分析
3.1 多故障程序的分布情况
从多故障主题的搜索结果来看,大部分故障版本都包含多个故障。在统计的326个故障程序中,有95.4%(311/326)的程序存在多个故障。这一数据直观地反映出软件系统中多故障现象的普遍性。
为了更清晰地展示多故障程序的分布,我们可以将不同数量故障的程序数量进行统计,如下表所示:
| 故障数量 | 故障版本数量 |
| ---- | ---- |
| 2 - 9 | 163 |
| 10 - 19 | 126 |
| 20及以上 | 22 |
从这个表格中可以推测出,虽然大部分多故障程序的故障数量集中在2 - 9个,但仍有相当一部分程序存在10个及以上的故障,这对于软件的稳定性和维护性提出了巨大的挑战。
3.2 故障寿命的影响因素
故障寿命的差异范围从0天到超过三年,如Lang - 41的寿命达到了1187天。这种巨大的差异表明故障寿命受到多种因素的影响。
- 代码复杂度 :复杂的代码结构可能导致故障难以被发现和修复,从而延长故障的寿命。例如,在一些大型项目中,模块之间的耦合度高,一个故障可能隐藏在多个模块的交互中,增加了排查和修复的难度。
- 测试覆盖度 :如果测试用例不能覆盖到所有的代码路径,一些故障可能会长期存在而不被发现。例如,某些边界条件的测试缺失,可能使得在特定输入下才会出现的故障一直潜伏。
- 开发团队的关注度 :对于一些不太关键的功能模块,开发团队可能投入的精力较少,导致故障修复的时间延长。
以下是故障寿命影响因素的mermaid流程图:
graph TD;
A[故障寿命] --> B[代码复杂度];
A --> C[测试覆盖度];
A --> D[开发团队关注度];
B --> E[高耦合度];
B --> F[复杂逻辑];
C --> G[边界条件测试缺失];
C --> H[代码路径覆盖不全];
D --> I[关键功能优先];
D --> J[资源分配不均];
4. 适应度函数优化的进一步探讨
4.1 细化适应度函数的优势与局限
从实验结果来看,ARJA - e使用的细化适应度函数在新数据集上的优势并不明显。虽然它在理论上能够更精确地评估候选补丁的质量,但在实际应用中存在一定的局限性。
- 优势 :细化适应度函数考虑了测试用例的输出信息,而不仅仅是通过或失败的结果。这使得它能够更细致地评估补丁对程序的影响,对于一些复杂的故障可能更有优势。例如,在处理涉及数值计算的故障时,能够根据输出的数值差异来评估补丁的优劣。
- 局限 :计算细化适应度函数的复杂度较高,需要更多的计算资源和时间。而且,在某些情况下,测试用例的输出信息可能并不足以准确判断补丁的质量,导致评估结果不准确。
4.2 避免过拟合的策略
实验结果显示工具可能存在对Defects4J先前版本数据集过拟合的问题。为了避免过拟合,可以采取以下策略:
- 增加数据集的多样性 :使用更多不同来源、不同类型的数据集进行训练和测试,减少对单一数据集的依赖。例如,可以结合开源项目和实际生产环境中的故障数据。
- 正则化方法 :在适应度函数中引入正则化项,限制模型的复杂度,避免模型过于拟合训练数据。例如,在ARJA - e的适应度函数中,可以添加对补丁大小的惩罚项,防止生成过于复杂的补丁。
- 交叉验证 :采用交叉验证的方法,将数据集划分为多个子集,轮流使用不同的子集进行训练和测试,以评估模型的泛化能力。
以下是避免过拟合策略的列表:
1. 增加数据集的多样性
- 收集不同来源的故障数据
- 涵盖不同类型的软件项目
2. 正则化方法
- 在适应度函数中添加惩罚项
- 限制补丁的复杂度
3. 交叉验证
- 划分数据集为多个子集
- 轮流进行训练和测试
5. 总结与展望
5.1 总结
通过对Defects4J中多故障程序的搜索和适应度函数优化的研究,我们得到了以下结论:
- 在Defects4J中,大部分故障版本都包含多个故障,这表明软件系统中的多故障现象普遍存在。
- 细化适应度函数在新数据集上的优势较小,可能存在工具对先前版本数据集过拟合的问题。
5.2 展望
未来的研究可以从以下几个方面展开:
-
改进搜索策略
:进一步优化多故障程序的搜索策略,提高搜索效率和准确性。例如,可以结合机器学习算法,对故障的特征进行分析,预测故障可能存在的位置。
-
优化适应度函数
:继续探索更有效的细化适应度函数,平衡计算复杂度和评估准确性。例如,可以引入深度学习模型,自动学习测试用例输出信息与补丁质量之间的关系。
-
跨语言和跨平台的研究
:将研究扩展到其他编程语言和平台,提高研究成果的通用性。例如,研究在Python、C++等语言中的多故障程序搜索和适应度函数优化。
综上所述,软件故障的检测和修复是一个复杂而长期的研究课题,需要不断地探索和创新,以提高软件的质量和可靠性。
超级会员免费看
32

被折叠的 条评论
为什么被折叠?



