想象你是一家制造复杂机器的工厂的质量检验员。你的工作是编写一份检查清单(即“测试”),以确保机器的每个部件都能正常运行。
在软件世界中,这些机器是Java 程序,而这些检查清单就是单元测试。
旧方法:“假部件”问题
传统上,当检验员测试机器的某个特定部件(例如齿轮)时,他们通常不会使用与其连接的真实发动机或真实燃油泵。相反,他们会使用模拟对象(mocks)——这些部件的虚假、橡胶复制品。
- 类比:想象通过连接一个燃油泵的纸板模型来测试汽车发动机。你可以检查发动机是否转动,但你永远无法知道真实的燃油泵是否堵塞或损坏,因为你从未真正使用过它。
- 问题:这种方法快速且简单,但留下了“浅层”的覆盖范围。它遗漏了真实部件相互作用时才会出现的错误。
新挑战:“真实部件”的噩梦
这篇论文的作者希望停止使用纸板模型。他们希望测试真实的燃油泵和发动机(真实的依赖项)。
- 问题:这极其困难。如果你尝试连接一个真实的燃油泵,你需要确切知道如何连接它、按什么顺序打开开关以及使用何种燃料。
- AI 的挣扎:研究人员使用了一个超级智能的 AI(大型语言模型,LLM)来编写这些测试。但 AI 不断犯下两类错误:
- “不知道”:AI 不知道真实工厂实际上是如何连接这些部件的。它进行猜测,创建了真实代码中不存在的虚假连接。
- “不遵循”:即使你告诉 AI 规则(例如,“在启动发动机之前先打开开关”),它有时也会忽略这些规则,按错误的顺序执行,导致机器爆炸(崩溃)。
解决方案:认识"MocklessTester"
团队构建了一个名为MocklessTester的新系统。把它想象成一位拥有超级笔记的主检验员。
MocklessTester 不再仅仅要求 AI“编写测试”,而是为 AI 提供两种特殊工具来修正其错误:
1. “真实案例”工具(上下文增强生成)
为了解决**“不知道”**的问题,系统会扫描整个工厂的历史记录。
- 类比:AI 不再猜测如何连接燃油泵,而是查看工厂的日志,看看其他工人过去是如何成功连接该泵的。它复制这些真实且有效的模式。
- 结果:AI 不再编造虚假连接,而是开始使用代码中发现的真实连接。
2. “严格规则手册”工具(约束强制修复)
为了解决**“不遵循”**的问题,系统扮演一名严格的安全检验员,分两个阶段检查工作。
- 阶段 1(草稿):AI 编写测试。
- 阶段 2(审计):在测试被接受之前,系统会根据三条严格规则对其进行审查:
- 符号检查:“你是否发明了一个不存在的部件?”(如果是,请用真实的部件替换它)。
- 协议检查:“你是否在打开开关之前就启动了发动机?”(如果是,强制 AI 修正顺序)。
- 记忆检查:“你是否之前尝试过同样的修复并失败了?”(如果是,请尝试不同的方法)。
- 转折:如果 AI 未能通过审计,它必须撰写一份理由说明,解释为什么它的新修复符合规则。这迫使 AI 在再次行动前仔细思考。
结果:更好的测试,多花一点时间
研究人员在两组软件项目上测试了这个新系统:
- Defects4J:一个标准的旧 Java 项目集合。
- Deps4J:一个全新的现代复杂项目集合,AI 从未见过(以确保 AI 不是通过死记硬背旧答案来“作弊”)。
发现:
- 更深层的覆盖:与之前的最佳方法相比,MocklessTester 发现了多 20% 的错误,并覆盖了多 25% 的代码行。关键在于,它实际上测试了机器中真实连接的部件,而不仅仅是孤立的齿轮。
- 代价:完成这项工作需要更多的时间和“脑力”(计算令牌)。AI 必须尝试更多次才能正确完成。
- 结论:多花的时间是值得的。测试质量大大提高,捕捉到了“假部件”测试所遗漏的现实世界问题。
一句话总结
这篇论文表明,通过向 AI 提供代码使用方式的真实示例,并强制其遵守严格规则,给予其第二次机会来解释其工作,我们终于可以在不依赖虚假、橡皮图章式的“模拟”部件的情况下,自动化复杂软件的测试。这之间的区别,就像是用纸板发动机测试汽车,与用真实发动机测试汽车的区别。
技术摘要:MocklessTester
问题陈述
尽管大语言模型(LLM)在自动化测试生成方面已展现出潜力,但现有的 Java 测试生成方法主要依赖 mocking 框架(如 Mockito)将被测方法(MUT)与其依赖项隔离。这种依赖引入了两个根本性局限:
- 浅层覆盖:Mock 测试在隔离状态下锻炼 MUT,导致依赖类的实际行为未被测试。
- Mock 脆弱性:测试与假设的 API 行为紧密耦合,即使真实行为保持兼容,一旦依赖项发生演变,测试也会失效。
无 Mock 测试(Mockless testing)通过实例化和执行真实依赖项提供了解决方案,但面临重大挑战。生成有效的无 Mock 测试需要严格遵守语言约束、正确的对象实例化以及有效的 API 调用序列。当前的基于 LLM 的方法在此处表现不佳,主要源于幻觉的两个根本原因:
- 不知晓(Not Knowing):LLM 缺乏足够的项目特定上下文(例如有效的构造函数、工厂方法或使用模式),无法生成正确的代码。
- 不遵循(Not Following):即使提供了约束,LLM 仍未能遵守它们(例如违反符号有效性或 API 调用协议),这通常是由于缺乏执行机制所致。
方法论:MocklessTester
作者提出了 MocklessTester,这是一种针对 Java 的无 Mock 单元测试生成方法,采用迭代式的规划–生成–验证–修复工作流。该系统围绕两大核心策略构建,以解决上述根本原因:上下文增强生成和约束强制修复。
1. 准备阶段
在测试生成开始之前,MocklessTester 分析目标仓库以构建三个项目特定的工件:
- 代码属性图(CPG):使用 Joern 构建,该图捕获依赖项使用情况,通过程序切片检索真实的对象构建和 API 调用示例。
- 类索引(ClassIndex):记录所有项目可见的类、方法、构造函数和导入项。它在修复过程中作为验证符号和解析类型的基准事实。
- 马尔可夫类型状态模型(Markov Typestate Model):一个概率模型,捕获有状态 API 的有效方法调用顺序。它从源代码证据中初始化,并根据测试结果动态更新,以检测非法调用序列。
2. 迭代多智能体循环
生成过程涉及五个智能体:初始化器(Initializer)、规划器(Planner)、生成器(Generator)、验证器(Validator)和修复器(Fixer)。
- 上下文增强生成:为缓解“不知晓”问题,生成器不仅依赖被测代码(CUT)的源代码。相反,程序切片器从项目的生产代码和测试代码中挖掘依赖项的真实实例化和使用模式。这些模式被注入到提示词中,以指导 LLM 创建合理的对象实例化。
- 约束强制修复:为缓解“不遵循”问题,修复器对失败的测试采用两阶段修复机制:
- 阶段 I(修复器 I):基于执行反馈(编译/运行时错误)生成初始修复。
- 阶段 II(修复器 II):如果初始修复违反约束,则触发第二次修复。LLM 必须生成满足三个层级约束的修复,并提供结构化理由,解释该修复如何解决故障并遵守规则:
- 符号级:确保所有类、方法和导入项存在于类索引中。
- 协议级:确保 API 调用序列遵守马尔可夫类型状态模型(例如,在使用前初始化状态)。
- 迭代级:利用经验记忆(Experience Memory),其中存储了成功的修复模式(“黄金测试”)、修复配方和反模式,以避免重复已知故障。
主要贡献
- 上下文增强提示:作者确定依赖项使用模式是关键上下文。他们提出了一种策略,通过从目标项目中挖掘的真实依赖项实例化示例来增强 LLM,显著减少了因知识不足导致的幻觉。
- 约束强制修复机制:一种新颖的两阶段修复过程,强制执行符号、协议和迭代级约束。关键在于,第二阶段要求模型为其修正生成结构化理由,直接针对“不遵循”类幻觉。
- 首个面向 Java 的无 Mock LLM 方法:MocklessTester 被呈现为首个显式针对外部依赖项的 Java 基于 LLM 的方法,推动自动化测试从 Mock 单元测试向真实执行迈进。
- 全面的实证评估:该研究引入了**依赖行覆盖率(DepLC)**作为新指标,用于衡量除被测类(CUT)之外实际执行的项目代码范围。
实验结果
该方法在 Defects4J(14 个项目,130 个类)和新基准 Deps4J(5 个近期多模块项目,30 个类)上进行了评估,并与最先进基线 PANTA 进行了对比。
有效性:
- 在 Defects4J 上,MocklessTester 将平均行覆盖率提高了 19.99%(从 68.83% 提升至 88.82%),分支覆盖率提高了 24.90%(从 58.84% 提升至 83.74%)。变异分数提高了 13.67%。
- 在 Deps4J 上,行覆盖率提高了 22.69%,分支覆盖率提高了 15.78%,同时保持了相当的变异分数。
- 依赖覆盖率:与基线相比,MocklessTester 覆盖了显著更多的真实依赖代码,在 Defects4J 上增加了 378 行 DepLC,在 Deps4J 上增加了 55 行。
- 测试效率:尽管每个项目平均生成的测试数量较少,但 MocklessTester 实现了更高的覆盖率,表明每个测试的质量更高。
效率:
- 由于执行更多迭代以达到更高覆盖率,MocklessTester 产生了更高的总 Token 和时间成本。
- 然而,成本仍然实用:在 Defects4J 上,每个方法的平均耗时为 108.97 秒,Token 数为 26.59k。
- 值得注意的是,MocklessTester 每次迭代的速度显著更快(时间减少约 58%),表明即使迭代次数更多,其迭代的时间效率也更高。
消融研究:
- 移除**使用检索(Usage Retrieval)**导致性能下降最大,证实了上下文的重要性。
- 移除协议级约束产生了第二大影响,突显了在没有明确执行机制的情况下管理有状态对象的难度。
- 符号级和迭代级约束也提供了显著但略小的改进。
意义与主张
论文声称,MocklessTester 通过生成能够锻炼真实项目依赖项的测试,成功解决了基于 Mock 测试的局限性。通过通过上下文增强系统性地解决“不知晓”问题,并通过约束强制解决“不遵循”问题,该方法在行覆盖率、分支覆盖率和变异分数方面,在现有和新基准上均取得了最先进成果。
作者强调,尽管该方法比基线方法需要更高的计算资源,但这种权衡产生的测试不仅在覆盖率上更全面,而且在应对依赖项演变时也更加稳健。DepLC 的引入为评估测试套件提供了新维度,超越了 CUT 以衡量更广泛系统的实际执行情况。该工作表明,未来的改进可能涉及更丰富的记忆机制以及针对以解析器为中心的组件的语法感知输入生成。
每周获取最佳 computer science 论文。
受到斯坦福、剑桥和法国科学院研究人员的信赖。
请查收邮箱确认订阅。
出了点问题,再试一次?
无垃圾邮件,随时退订。