不再手动测试了吗?评估和改进ChatGPT的单元测试生成能力

互联不一般哥 2024-04-22 21:06:37

No More Manual Tests? Evaluating and Improving ChatGPT for Unit Test Generation

Zhiqiang Yuan, Yiling Lou , Mingwei Liu , Shiji Ding , Kaixin Wang, Yixuan Chen, Xin Peng Jiangxi Normal University, China, Fudan University, China, Tongji University, China, Xiamen University, China

引用

Yuan, Zhiqiang, Yiling Lou, Mingwei Liu, Shiji Ding, Kaixin Wang, Yixuan Chen and Xin Peng. “No More Manual Tests? Evaluating and Improving ChatGPT for Unit Test Generation.”ArXiv abs/2305.04207 (2023): n. pag.

论文:https://arxiv.org/abs/2305.04207

仓库:https://github.com/jstzwj/ChatTester?tab=readme-ov-file

摘要

本文提出了CHATTESTER,一种新颖的基于ChatGPT的单元测试生成方法,它利用ChatGPT本身来提高其生成的测试的质量。CHATTESTER包含一个初始测试生成器和一个迭代测试提炼器,我们的评估通过生成34.3%以上的可编译测试用例和18.7%以上有正确断言测试用例证明了CHATTESTER比默认的ChatGPT更有效。

1 引言

作为软件开发过程中的主要阶段,单元测试在检测和诊断初始阶段的错误并防止其在开发周期中进一步传播方面起着至关重要的作用。因此,编写高质量的单元测试对于确保软件质量至关重要。

传统的单元测试生成技术利用基于搜索的,基于约束的,或者随机策略来生成一套单元测试,主要目标是最大化被测软件的覆盖率。尽管实现了合理的覆盖率,但这些自动生成的测试在可读性和意义方面与手工编写的测试有很大差距,因此开发人员大多不愿意在实践中直接采用它们。

为了解决这些问题,最近的工作利用高级深度学习(DL)技术,尤其是大型语言模型(LLM)来生成单元测试。这些技术主要通过将给定的焦点方法翻译成相应的测试前缀和测试断言来将单元测试的生成公式化为神经机器翻译问题。这些模型能够生成更类似人类且更有意义的测试代码,显示了LLMs在单元测试生成方面的巨大潜力。

ChatGPT聊天机器人,不同于LLM在现有的基于学习的测试生成技术。ChatGPT进一步整合了RLHF(加强从人类反馈中学习)和更大的模型规模从而在各种领域中表现出更好的泛化能力和与人类意图的更高一致性。然而,ChatGPT在生成单元测试方面的效率如何仍不清楚。

在这项工作中,我们进行了第一次实证研究来评估ChatGPT的单元测试生成能力。我们首先构建一个包含1000个Java焦点方法的数据集,每个方法都带有一个完整的可执行项目环境。此外,基于之前测试生成工作中的常见实践和广泛认可的ChatGPT相关经验,我们设计了一个基本提示词,包括关于任务的自然语言描述和焦点方法的代码上下文和其他相关上下文。然后,我们使用基本提示查询ChatGPT API,并分析返回测试的质量。

基于我们的研究结果,我们有以下主要发现。从坏的方面来看,我们发现ChatGPT生成的测试中只有一部分(24.8%)能够通过执行,其余的测试都存在不同的正确性问题。从好的方面来看,我们发现ChatGPT生成的通过测试实际上类似于手动编写的测试,实现了可比较的覆盖率、可读性,有时甚至比开发人员手动编写的测试用例更好。总的来说,如果生成的测试中的正确性问题能够得到进一步解决,那么基于ChatGPT的测试生成将非常有前途。

受上述发现的启发,我们进一步提出了一种新的基于ChatGPT的单元测试生成方法CHATTESTER,它利用ChatGPT本身来提高其生成测试的正确性。CHATTESTER包括一个初始测试生成器和一个迭代测试提炼器。初始测试生成器通过以下方式将测试生成任务分解为两个子任务:1.首先利用ChatGPT通过意图提示理解焦点方法,2.利用ChatGPT通过提示词的帮助生成焦点方法和生成意图。然后,迭代测试精炼器迭代地修复初始测试生成器生成的测试中的编译错误,在此过程中遵循验证和修复范例,基于编译错误信息和额外的代码上下文提示ChatGPT。

我们的结果表明,CHATTESTER大大提高了ChatGPT生成测试的正确性,在可编译率和执行通过率方面分别提高了34.3%和18.7%。此外,我们的结果进一步证实了CHATTESTER中两个组件的贡献,即初始测试生成器能够生成更多具有正确断言的测试,而迭代测试精炼器能够迭代地修复编译错误。

综上所述,本文做出了以下贡献:

第一项通过定量分析和用户研究广泛调查ChatGPT生成的测试的正确性、充分性、可读性和可用性的研究;指出了基于ChatGPT的单元测试生成的局限性和前景的发现和实际意义;第一次提出CHATTESTER技术,包括一个新颖的初始测试生成器和迭代测试精炼器,它利用ChatGPT本身来提高其生成的测试的正确性;通过大量减少ChatGPT生成的测试中的编译错误和错误断言,证明CHATTESTER的有效性。

2 技术介绍

2.1 基准测试

单元测试生成的现有基准仅包含有限的代码上下文(例如,仅包含焦点方法),而不是完整的可执行项目,并且很难使用现有数据集直接编译和执行生成的测试。因此,为了全面评估ChatGPT生成的测试的质量,我们构造了一个新的不仅是焦点方法的基准,也是完整和可执行项目的基准。具体来说,我们如下构建基准。

T1:项目收集

我们在流行的基准代码搜索网络中使用了4685个Java项目作为初始项目列表。对于每个项目,我们从GitHub中克隆它并收集其相关信息(例如,其创建时间和上次提交时间)。为了保持高质量的项目,我们然后根据以下标准筛选4685个Java项目:

1.项目处于持续维护中;

2.该项目至少有100颗星;

3.该项目是用Maven框架构建的(便于测试执行),可以在我们的本地环境中成功编译。这样,我们获得了185个Java项目。

T2:数据对收集

我们从185个Java项目中提取数据对。这里的每个数据对指的是焦点方法信息及其对应的测试方法对。具体来说,除了焦点方法本身之外,焦点方法信息还包括焦点类声明、所有字段和所有方法签名(即类构造函数和实例方法)。对于每个Java项目,我们在以下步骤中提取数据对:

1.我们首先找到项目中的所有测试类。如果一个类至少包含一个用@Test注释的方法,我们将这个类视为一个测试类,并收集这个测试类中的所有测试方法。

2.然后我们根据文件路径和类名匹配为每个测试方法找到相应的焦点方法。例如,对于位于路径“src/test/ java/FooTest.java”中的测试方法“testFunction()”,我们将位于路径“src/main/ java/Foo.java”中的方法“Function()”视为其焦点方法。对于同一个类中有多个同名焦点方法的情况,我们进一步根据参数的数量和类型对它们进行过滤,以找到唯一匹配的方法。根据如此严格的映射标准,我们从185个Java项目中提取了1748个数据对。表1展示我们的基准测试的统计分布。

表1:基准测试统计

2.2 基本提示词设计

为了避免使用可能导致低估ChatGPT能力的过于简单的提示,或者避免使用实践中不常见的过于复杂的提示,我们通过仔细遵循现有单元测试生成工作中的常见实践和使用ChatGPT的经验来设计我们的基本提示。具体来说,我们的基本提示包括两个部分:1.向ChatGPT解释任务的自然语言描述部分(即NL部分),2.代码上下文部分(即CC部分),包含焦点方法和其他相关代码上下文。

T1:代码上下文部分

我们将以下代码上下文包含在CC部分中:

1.完整的焦点方法,包括签名和正文;

2.焦点类的名称(即焦点方法所属的类);

3.焦点类中的域;

4.焦点类中定义的所有方法的签名。

T2:自然语言部分。我们在NL部分包括以下内容:

1.角色扮演说明(即“您是编写Java测试方法的专业人员。”)来激发ChatGPT的测试生成能力,这是一种常见的提示词优化策略。

2.任务描述指令(即“请使用Junit版本根据给定信息为焦点方法编写测试方法”),用来解释该任务。

在使用基本提示词进行查询后,ChatGPT返回一个测试,该过程如图1所示。

图1:基本提示词工作过程

2.3 CHATTESTER方法

我们提出了一种新的基于ChatGPT的单元测试生成方法CHATTESTER,它提高了ChatGPT自身生成测试的正确性。CHATTESTER包含两个组件,即初始测试生成器和迭代测试精炼器。图2显示了CHATTESTER的工作流程。

图2:CHATTESTER的工作流程

2.3.1 初始测试生成器

初始测试生成器将测试生成分解为两个步骤:1.首先利用ChatGPT通过意图提示理解焦点方法,2.利用ChatGPT通过生成的意图为焦点方法生成测试。我们要求ChatGPT返回被测焦点方法的预期功能的自然语言描述。特别是,代码上下文部分类似于2.2中的基本提示词,包括类声明、构造函数签名、相关字段和焦点方法本身;而自然语言指令是要求ChatGPT推断焦点方法的意图。然后,生成的提示词进一步包括生成的意图,并要求ChatGPT为焦点方法生成单元测试。图3展示了一个比较基本提示和初始测试生成器如何为相同的给定焦点方法“setCharAt()”生成测试的示例。

图3:基本提示词与初始测试生成器的提示词

2.3.2 迭代测试精炼器

迭代测试优化器迭代地修复初始测试生成器生成的测试中的编译错误。每次迭代连续利用两个步骤:1.通过在验证环境中编译生成的测试来验证它;2.基于编译期间的错误消息和与编译错误相关的额外代码上下文来构建提示。然后在ChatGPT中查询新的测试以获得精炼的测试。重复这样的过程,直到生成的测试可以成功编译或者达到最大迭代次数。请注意,目前我们只关注修复编译错误而不是执行错误,因为在实践中很难识别测试执行失败是由错误的测试代码还是错误的焦点方法引起的。每个步骤如图4所示。

图4:迭代测试精炼器的提示词

验证器。为了便于编译生成的测试,我们直接在焦点类的同一个目录中创建一个测试文件。特别是,生成的测试方法被封装在一个带有相关导入语句的测试类中,用Java编译器编译测试文件。然后,控制器根据编译状态决定下一步:

成功编译:如果没有编译错误,控制器将终止迭代优化过程并返回最终测试;有效细化:如果编译错误的数量少于上一次迭代中的数量,则当前细化被视为有效细化。无效细化:如果编译错误的数量大于或等于上一次迭代中的错误数量,则当前细化被视为无效细化。如果无效细化的累积数量大于最大值(例如,在我们的实验中为3),则控制器将终止细化;或者继续迭代提示词构造器。

迭代提示词构造器。迭代提示词构造器建立在1.分析关于编译错误的错误消息的EM解析器和2.提取与编译错误相关的附加代码上下文的代码分析器之上。EM解析器通过解析错误消息收集三种类型的信息:

错误类型:错误的高级描述,通常是错误消息的第一句话。例如,“找不到简单类”和“找不到符号方法”错误位置:触发编译错误的测试代码的行号。有了这样的位置信息,提示构造器就能够在错误行周围插入相关信息,Buggy元素:buggy中的对象或变量位置。

考虑到ChatGPT的输入长度有限,在CHATTESTER中,我们建议以迭代方式添加必要的额外代码上下文。

3 实验评估

3.1 实验设置

研究问题。在本文中,我们研究以下研究问题:

RQ1(正确性):ChatGPT生成的单元测试的正确性如何?不正确测试中的常见错误有哪些?

RQ2(充分性):ChatGPT生成的单元测试的充分性如何?

RQ3(可读性):ChatGPT生成的单元测试的可读性如何?

RQ4(可用性):开发人员如何使用ChatGPT生成的测试?对于那些由ChatGPT生成的正确测试,开发人员是否愿意直接采用它们。

RQ5:CHATTESTER方法的效果怎么样?

对比方法。为了评估CHATTESTER的整体有效性以及CHATTESTER中每个组件(即初始测试生成器和迭代测试精炼器)的单独贡献,我们研究了三种方法:

ChatGPT:带有基本提示的默认ChatGPT。CHATTESTER-:没有迭代测试精炼器的CHATTESTER变体,用初始测试生成器增强了默认的ChatGPT。CHATTESTER:完整的 CHATTESTER,带有初始测试生成器和迭代测试提炼器。

为了减轻ChatGPT中的随机性,我们将所有实验重复三次并给出平均结果。

实验步骤。图5显示了我们实验程序的概述。对于2.1中构建的基准中的每个数据对,我们使用2.2中设计的基本提示词来查询ChatGPT,并将ChatGPT生成的测试作为输出。为了自动化我们的实验,我们使用官方ChatGPT API的默认设置。在这项工作中,我们主要关注gpt-3.5-turbo模型。然后,我们将生成的测试放在其焦点类的同一目录中,并尝试编译和执行它以供进一步分析。然后,我们分别解释每个RQ中的详细步骤。

RQ1:正确性。遵循现有的基于学习的测试生成工作,我们用三个度量来衡量生成的测试的正确性,包括1.语法正确性(测试是否可以通过语法检查器);2.编译正确性(测试是否可以成功编译),以及3.执行正确性(测试是否可以通过执行)。在这里我们利用AST解析器作为语法检查器。具体来说,我们自动提取编译和执行期间抛出的错误消息,包括不同的编译和执行错误类型。

RQ2:充足性。我们包括三个指标来评估ChatGPT生成的测试的充分性:1.焦点方法上测试的语句覆盖率;2.重点方法测试的分支覆盖率;3.测试用例中断言的数量。我们利用Jacoco来收集覆盖率。

RQ3和RQ4:可读性和可用性用户研究。我们调查了ChatGPT生成的测试的可读性和可用性。这里,我们只关注ChatGPT生成的248个通过测试,因为在实践中向开发人员推荐有编译错误或执行错误的测试意义不大。我们邀请了五位具有丰富Java开发经验的人员参与评估指标。给定ChatGPT生成的测试(表示为X)和项目中手动编写的测试(表示为Y),我们向每个参与者询问以下两个问题。参与者不会被告知哪个测试是由ChatGPT生成的还是手动编写的。

问题 1(可读性):“请将X和Y的可读性从1分到4分进行评分。”

问题 2(可用性):“你更喜欢在项目中直接使用哪个测试(X或Y)?

图5:实证研究的工作流程

RQ5:CHATTESTER的有效性。为了评估CHATTESTER的有效性,我们进一步构建了一个额外的评估数据集,在单独的数据集上进行评估可以消除潜在的过拟合问题。在本节中,我们从剩余的748个数据对中再抽取100个数据对作为我们的评估数据集,以评估CHATTESTER的有效性。

RQ1 正确性

表2展示了由ChatGPT和其他技术生成的1000个测试的正确性。总的来说,我们可以观察到ChatGPT生成的很大一部分测试都存在正确性问题,即42.1%的生成测试成功编译,而只有24.8%的生成测试成功执行且没有任何执行错误。

表2:生成测试的正确性

对比基线AthenaTest,ChatGPT在语法正确性、编译正确性和可执行正确性方面比AthenaTest有了实质性的改进。对比传统的基于搜索的基线Evosuite,我们可以在其生成的测试中观察到更高的可编译率和通过率,因为Evosuite在搜索过程中删除了无效的测试代码,并完全基于动态执行生成断言,而基于学习的技术(即ChatGPT和AthenaTest)直接一个一个词地生成测试,而无需任何后期生成验证或过滤。

失败用例分析。我们进一步分析常见错误ChatGPT生成的失败测试中的类型(编译或执行失败)。表3显示了编译错误用例的详细情况,而表4显示了执行失败用例的详细情况。

编译失败。在表3中“频率”一栏显示了579个不可编译测试中每个编译错误的数量。请注意,一次测试中可能有多个编译错误,因此总和大于579。由于篇幅限制,我们只列出观察到的超过10次的常见编译错误,1.最常见的编译错误是由无法解析的符号引起的,2.另一大类编译错误与类型错误有关,3.ChatGPT还经常生成无效访问私有变量或方法的测试代码(即访问错误)。4.一些生成的测试由于无效抽象类或使用不支持的运算符而遇到编译错误。

表3:编译错误用例分析

执行失败。如表所示,大多数执行失败(85.5%)是由断言错误引起的,即ChatGPT生成的断言认为被测程序违反规范。我们发现所有这些错误都是由ChatGPT生成的不正确断言引起的。这意味着ChatGPT可能无法准确理解焦点方法,ChatGPT生成的断言的质量应该大大提高。此外,我们观察到剩余的执行错误与不同的运行时异常有关。例如,图6展示了ChatGPT生成的测试中的失败执行示例。测试在第3行执行时抛出了NullpointerExeception。发生该错误是因为创建的对象“url”访问不存在的外部资源“/test.jar”。它实际上显示了ChatGPT的一个固有限制,即在测试生成期间不知道外部资源。

表4:执行失败用例分析

图6:空指针异常实例

RQ2 充分性

表5呈现可能通过执行的已生成测试的语句和分支覆盖范围。我们进一步包括手动编写的测试的覆盖范围。如表中所示,我们可以观察到,与现有的基于学习和基于搜索的技术相比,ChatGPT生成的测试实现了最高的覆盖率,并且它还实现了与手动编写的测试相当的覆盖率。

表5:生成测试的覆盖率

图7展示由不同技术生成的每个测试中的断言数量的分布图。有趣的是,我们观察到ChatGPT生成的测试在每个测试的断言数量方面表现出与手工编写的测试最相似。特别是,Evosuite倾向于生成断言较少的测试,而基于学习的技术AthenaTest会生成断言数量异常多的测试。潜在的原因可能是RLHF有助于ChatGPT生成更像人类的测试代码。

图7:生成的测试中断言数量

RQ3 可读性

如图8所示,x轴代表每个具有丰富Java开发经验的人员(即从A到E),y轴代表不同分数的比率。总的来说,大多数ChatGPT生成的测试都被评估为具有相当好的可读性,并且与手动编写的测试相比,它们也被认为具有相当的可读性,有时甚至更好。

图8:可读性评分

RQ4 可用性

如图9所示,y轴代表每个具有丰富Java开发经验的人员,x轴显示倾向于手动编写测试、ChatGPT生成测试或不倾向于手动编写测试的响应数量。有趣的是,我们发现ChatGPT生成的测试非常有竞争力,有时甚至有相当一部分情况下参与者更喜欢ChatGPT生成的测试。根据参与者的反馈,我们发现人们通常综合多种因素,例如代码格式、注释、调用焦点方法的方式以及断言的合理性。

基于以上结果,我们进一步讨论了基于ChatGPT的单元测试生成的优势和局限性。局限性。正如我们的结果所示,很大一部分ChatGPT生成的测试在编译或执行时失败,这可能是ChatGPT这样的生成式语言模型的两个固有限制的结果。

为此有两个方向:1.为ChatGPT提供有关代码的深入知识;2.帮助ChatGPT更好地理解焦点方法的意图,从而分别减少其编译错误和断言错误。

图9:可用性评分

RQ5 CHATTESTER方法评估

表6展示了ChatGPT生成的测试和我们的方法的正确性。总的来说,与默认的ChatGPT相比,我们可以观察到CHATTESTER生成的测试在编译速度和通过率方面都有很大的提高。例如,额外的34.3%测试可以成功编译,而额外的18.7%测试可以通过执行。总之,所提出的方法有效地提高了ChatGPT生成的测试的正确性。此外,我们可以观察到变体CHATTESTER-的编译速度和通过率分别提高了11.7%和7.4%,优于默认的ChatGPT。特别是,我们发现在ChatGPT生成的错误断言测试中,12.5%的测试在chat ester-中被修复为正确的断言,这表明了初始测试生成器的有效性。此外,我们可以观察到从CHATTESTER-到CHATTESTER的进一步改进。总之,两个组件(即初始测试生成器和迭代测试提炼器)都对CHATTESTER的有效性做出了积极贡献。

表6:CHATTESTER的有效性

转述:叶恒杰

0 阅读:0

互联不一般哥

简介:感谢大家的关注