使用生成式人工智能进行单元测试生成:自动生成工具的比较分析

互联不一般哥 2024-03-13 06:34:04

Unit Test Generation using Generative AI : A Comparative Performance Analysis of Autogeneration Tools

Shreya Bhatia, Tarushi Gandhi, Dhruv Kumar, Pankaj Jalote

Delhi, India

引用

Bhatia S, Gandhi T, Kumar D, et al. Unit test generation using generative ai: A comparative performance analysis of autogeneration tools[J]. arXiv preprint arXiv:2312.10622, 2023.

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

摘要

这项研究旨在实验性地调查LLMs(特别是ChatGPT)对Python程序生成单元测试脚本的有效性,以及生成的测试用例与现有单元测试生成器(Pynguin)生成的用例之间的比较。在实验中,我们考虑三种类型的代码单位:1)过程化脚本、2)基于函数模块化代码和3)基于类别代码。根据覆盖率、正确性和可读性等标准评估所生成的测试用例。

1 引言

单元测试是软件开发的一个重要部分,因为它有助于在开发过程中尽早捕获错误。手动创建和维护有效的单元测试是一项明显费时费力的任务。为了解决手动测试创建中固有的困难,研究人员提出了各种自动化单元测试生成过程的方法论。这个领域常见的方法包括基于搜索、基于约束或基于随机技术,所有这些方法都旨在生成一套主要目标是增强目标软件覆盖率的单元测试套件。然而,与手动创建的测试相比,通过这些技术产生的自动化测试可能不太易读和理解。这个缺点使得经验较少的测试人员很难学习,并且阻碍了这些策略特别是对初学者采用。因此,开发人员可能会犹豫是否将这些自动生成 的 测试直接纳入他们 的 工作流程中。为了解决这些担忧,最近的努力探索使用高级深度学习 (DL) 技术,特别是大型语言模型 (LLMs),进行单元测试生成。

大型语言模型(LLMs)的进步,以OpenAI的ChatGPT(GPT-3.5)为例,展示了在诸如问答、翻译和文本/代码生成等日常任务中增强的能力,与人类一样理解。与其他LLMs不同,ChatGPT结合了来自人类反馈的强化学习(RLHF)和更大规模的模型,提高了泛化能力并与人类意图对齐。ChatGPT广泛应用于日常活动中,在文本生成、语言翻译和自动客户支持等任务中至关重要。除了日常使用外,大型语言模型越来越多地应用于软件工程任务,包括代码生成和摘要。这些模型还可以促进单元测试用例的生成,并简化软件验证过程。

本文旨在探讨由大型语言模型生成的测试套件的优势和缺点。我们特别关注ChatGPT作为LLM的代表。此外,我们希望探索将现有单元测试生成器(如Pynguin)与基于大型语言模型(LLM)的方法集成以提高整体性能的潜力。

我们评估ChatGPT生成的单元测试质量与Pynguin相比。根据代码结构,我们将样本Python代码分类为3类:1)过程脚本,其中代码高度不结构化,缺乏类和方法的适当定义。2)基于函数的模块化代码是指具有清晰函数定义的独立操作且像独立代码单元一样运行的功能。3)基于类的模块化代码围绕类和对象组织而成,作为主要组织单位。

我们精心整理了一个数据集,包括60个Python项目,将它们分类为每个类别20个项目,并且每个项目都有完整的可执行环境。在我们的研究中,我们专注于从每个项目中选择一个指定的核心模块,代码行数在100-300行之间,根据诸如圈复杂度、函数计数和文件相互依赖等因素进行选择。然后我们通过将它们作为输入提示给ChatGPT来为所选模块生成单元测试,并与Pynguin生成的单元测试进行比较。我们旨在解决以下研究问题:

RQ1(比较性能):ChatGPT 在生成单元测试方面与 Pynguin 相比如何?RQ2(性能饱和和迭代改进):ChatGPT生成的测试用例的有效性如何随着多次提示迭代而改善/变化?RQ3(质量评估):ChatGPT 生成的断言有多正确,有多少百分比的断言与代码的预期功能相符?RQ4(结合工具以提高性能):ChatGPT和Pynguin的组合是否可以提升单元测试生成的整体性能,包括覆盖率和有效性?

2 技术介绍

图1:实证分析工作流程

2.1 基于代码结构的分类

三个划分的类别用于捕捉不同级别的代码组织:

第一类(过程脚本):包括以无结构、过程化脚本为特征的代码示例。它们具有线性执行流,涉及包含一系列步骤的程序,并且控制流依赖于块和作用域(if、while、for),提供有限封装。

类别2(基于功能的模块化代码):包含了具有结构化方法的代码示例,没有显式的类定义。它们具有清晰定义的独立函数,作为代码的独立单元。这种代码组织提供了有限封装性,并可能依赖全局变量,在函数内部没有数据隐藏或保护机制。

类别3(基于类的模块化代码):包含的代码示例具有良好组织结构,定义了类和方法作为主要单元。这种代码组织是从面向对象编程中采用的,提供了强大的封装性;数据被隐藏在类中,并通过方法访问。适用于具有复杂结构的较大项目或应用程序。收集到的大多数较大代码示例属于此类别。

2.2 数据收集

为了对单元测试生成工具进行全面评估,我们首先收集了一组Python代码样本的数据集,涵盖了各种项目。Pynguin存在一个限制,即它无法很好地处理使用原生代码(如Numpy)的Python程序。在我们的比较分析中,我们不得不使用没有依赖于这些库的Python项目。最初,针对每个类别,我们从开源Github存储库中选择了20个Python项目(即总共60个项目),确保多样性和平衡代表,并注意到Pynguin的限制。

每个项目包含多个文件,这些文件彼此之间存在依赖关系。为了进行测试生成,我们必须确定一个核心模块,将其作为 ChatGPT 的提示输入传递,以便该核心模块包含密集的代码逻辑并覆盖项目的主要功能。最初,我们将核心模块的选择限制在 0-100 行代码(LOC)的大小范围内(为了方便以后参考,我们将这些称为“小型代码示例”)。对这些数据进行分析显示,在从 ChatGPT 和 Pynguin 生成的测试中获得了类似性能表现,因此我们决定将分析扩展到更大规模的代码库,并且核心模块的大小范围从 100 到 300 LOC 不等。为了方便以后参考,我们将其称为“大型代码示例”。

基于 Lukasczyk 等人使用过的基准数据选取额外一组40个Python项目,并附带完整可执行项目环境,确保实验有稳固基础。值得注意的是,根据它们的代码结构和复杂程度对项目进行分类成不同类别:每个类别2和3各20个。我们没有找到足够数量符合类别1: 过程脚本 (只找到六个)100-300 行 大型项目, 因为大多数项目采用模块化编程方法来简化维护工作。

此外,通过我们的初步分析,我们发现 Pynguin 无法为类别1生成单元测试用例,因为它在过程脚本中难以识别单位。因此,我们将进一步比较 Pynguin 和 ChatGPT 在类别2和3之间的分析。然而,在接下来讨论由 ChatGPT 生成的测试时,我们也会考虑对类别1样本进行实验。总共有100个Python项目按代码行数分类:每个类别1(0-100行)、类别2(0-100 行)和类别3(0-100行)各20个,并且另外40个项目分布在类别2(100-300 行)和类别3(100-300行)。

使用ChatGPT启动单元测试用例生成过程的关键第一步涉及将单个代码模块作为提示的输入呈现。随后,我们着手确定每个项目中值得进行测试的关键核心模块,基于以下因素:

代码行数(LOC):为了简化我们的关注重点,我们将分析限制在0-300行代码的模块上,并根据每个项目内部的后续因素确定核心模块。复杂度指标:我们采用McCabe复杂度来评估文件中的复杂性和逻辑深度,通过优先选择具有最大圈复杂度函数的模块来完善我们的选择。功能计数:在剩余的竞争者中,我们考虑了每个文件中的函数计数作为一个额外标准,旨在识别具有较高数量函数且具有较高圈复杂度的文件,这表明其中包含复杂逻辑。依赖分析:我们选择过程的一个重要方面涉及审查文件之间的相互依赖关系。经常被其他文件导入或引用的文件出现为潜在候选项,对项目整体逻辑至关重要。

2.3 单元测试生成工具

我们探索了最近的工具Ticoder和Codamosa的潜力;然而,由于它们无法直接实验,我们选择了精通Python单元测试生成工具Pynguin来进一步与ChatGPT进行全面比较。我们聘请ChatGPT和Pynguin为每个已识别的核心模块生成单元测试用例。

2.4 ChatGPT的提示设计

我们的提示包括两个组成部分:i)Python程序,和ii)自然语言中描述任务目标的文本,如图2所示。在第i部分中,我们提供所选核心模块(100-300行代码)的全部代码。我们不会将单独的单元提供给ChatGPT,因为我们旨在评估ChatGPT在提供完整Python程序时识别单元的能力。在第ii部分中,我们向ChatGPT查询如下:“使用Pytest编写单元测试来覆盖所有边缘情况。”ChatGPT生成的测试用例随后会针对Pynguin生成的测试用例进行语句和分支覆盖率评估,通过这一过程我们还会发现ChatGPT和Pynguin错过的语句,即生成的测试用例无法覆盖,并查看它们是否重叠。接下来,我们为ChatGPT设计了一个新提示,该提示将获取这些被忽略语句的索引,并要求其再次生成单元测试以提高覆盖率。

接着,我们也被迭代提示ChatGPT不断改进覆盖范围的可能性所吸引。然后,我们在每次迭代时重复提示ChatGPT,并更新提示以包含新一组未命中语句的索引,直到观察到覆盖率没有进一步提高为止,如图3所示。

图2 基础提示词

图3 进阶提示词

3 实验评估

3.1 实验设置

研究问题。通过我们的研究,我们将按以下顺序解决研究问题:

RQ1(比较性能):我们测量了由ChatGPT和Pynguin生成的单元测试之间在不同代码结构和复杂性下的语句覆盖率和分支覆盖率差异。然后,我们调查了圈复杂度、代码结构以及ChatGPT实现的覆盖率之间是否存在任何相关性。

RQ2(性能饱和和迭代改进): 然后,我们迭代地提示Chatgpt改进样本的覆盖范围,给出错过语句的索引。

RQ3(质量评估):我们检查生成的单元测试用例是否存在编译错误。在正在编译的测试用例中,我们检查它们的断言是否正确地测试了代码的预期功能。

RQ4(结合Pynguin和ChatGPT提高性能):在遗漏语句的情况下,我们查看 ChatGPT 和 Pynguin 生成的单元测试中遗漏语句之间的重叠程度。我们使用这一点来得出结论,即两种工具结合使用的技术组合可能是改进性能的潜在解决方案。

评估指标。通过多方面的方法评估生成的单元测试的有效性,采用以下指标:

语句覆盖率:量化生成测试覆盖个别代码语句的程度。分支覆盖率:评估各种代码分支的覆盖范围,并衡量测试套件在探索不同执行路径方面的效果。正确性:检查生成断言是否有助于评估代码预期功能,同时也是正确的。

3.2 实验结果

3.2.1 小型代码示例(0-100行)

对于类别1(程序脚本),Pynguin未能生成任何单元测试用例。这可以归因于类别1样本的结构,其中缺乏结构,Pynguin无法识别出供测试的不同单元,因为它依赖于模块化代码的属性。注意到ChatGPT提供了将类别1程序重构为模块化单元的建议。它首先为所提供的代码示例生成了重构后的代码,然后根据修改后的代码生成了测试用例。对于类别2,在语句和分支覆盖率方面,Pynguin和ChatGPT达到的成就没有显著差异。这也可以从执行独立t检验后得到的p值(阈值=0.05)中看出来。语句覆盖率的p值为0.631,而分支覆盖率则是0.807。两个p值都高于阈值。对于类别3也是一样,在两种工具实现的语句和分支覆盖率之间没有显著差异,鉴定各自p值大于阈值:0.218 和 0.193 。在表格1中显示 ChatGPT和Pynguin 对 0-100行代码示例给出相似覆盖范围。

表1 ChatGPT&Pynguin为小代码样本获得的平均语句和分支覆盖率。

这两种工具都为类别2和类别3提供了相当的性能。

3.2.2 小型代码示例(0-100行)

A.类别覆盖分析

由于对于类别1,由于缺乏定义明确的单元,单元测试生成是不可行的,并且代码重构是一个广泛的领域,我们将覆盖率分析限制在具有100-300 LOC的大型代码样本的类别2和类别3。对于类别2,我们观察到ChatGPT和Pynguin实现的语句和分支覆盖率之间没有显著差异,各自的p值大于0.05(阈值);0.169和0.195。对于类别3,ChatGPT和Pynguin给出了类似的覆盖率,分别表示为语句和分支覆盖率的p值0.677和0.580。这些观察结果在表2和图4中详细显示。

回答RQ1:ChatGPT和Pynguin给出了所有3个类别的可比语句和分支覆盖率。此外,Mccabe复杂度是评估代码单元复杂性的指标。对于代码样本,我们为其分配最大Mccabe复杂度,这是代码中存在的所有单元的最大值。然而,根据这个复杂性度量绘制平均语句覆盖率似乎并没有突出显示每个工具实现的覆盖率与代码样本的最大Mccabe复杂度之间的任何趋势或相关性,如图5所示。

表2 ChatGPT&Pynguin对大型代码样本获得的平均语句和分支覆盖率。

图 4 代码样本(100-300 行)的语句覆盖率(左)和分支覆盖率(右)由ChatGPT(蓝色)和Pynguin(红色)获得。

B. 覆盖范围的迭代改进

到目前为止,覆盖率分析是基于从提示ChatGPT的第一次迭代中获得的结果进行的。作为对ChatGPT提示的一部分,我们提供了第一次迭代中遗漏的语句,并要求它进一步改善给定代码样本的覆盖范围。我们持续这个过程,直到连续迭代之间没有覆盖率改善为止。由于大多数样本在第4次迭代时收敛(如图6所示),我们最多需要反复提示ChatGPT 5次。回答RQ2:观察到类别2和类别3中语句覆盖率平均增加了27.95和15.25。对于类别1,在表3中可以看到尽管经过多次迭代,但在任何步骤上都没有改善覆盖范围。

表3 迭代改进。此表显示了在迭代1和5中实现的所有3个类别的平均声明覆盖率。还描述了5次迭代后状态覆盖率的平均改进以及达到覆盖率改进饱和点所需的迭代次数中位数。最佳覆盖率:所有迭代的最佳整体覆盖率平均值。

C. 正确率

为了评估ChatGPT生成的断言的正确性,我们以以下方式检查各种错误类别:i)测试用例是否编译通过,ii)在编译通过的测试用例中通过断言的百分比,iii)对于失败的断言遇到的错误性质。强调使用用于生成这些断言的源代码片段来自公开可供一般使用的经过充分测试以执行其预期功能的Python项目。这意味着ChatGPT生成的测试用例应有效捕获代码预期功能,并且如果它们正确地测试代码逻辑,则相应断言应该通过。然而,如果有任何断言失败,则表明这些断言未能测试代码预期功能,因此是不正确的。

回答RQ3:如表4所示,对于第2类别,平均约39%的生成断言是不正确的,而对于第3类别,平均28%的断言是不正确的。另外,我们还检查了ChatGPT为第1类别样本提供重构代码时断言的正确性,并发现58%生成的断言是不正确的。从第1类别到第3类别逐渐减少错误断言百分比可能意味着ChatGPT在具有明确定义结构程序中生成正确断言的能力更高,可能是由于代码中存在更连贯和有意义单元。

表4 不正确断言的百分比。此表给出了ChatGPT3类代码示例生成的断言正确的平均百分比。还指定了断言失败的3个原因的分布:i)断言-错误:当断言条件未得到满足时发生ii)try/除外块中的错误:当预计会引发异常但未引发时发生iii)运行时错误:在程序成功编译后运行时发生。

D. 重叠遗漏语句

我们研究了ChatGPT和Pynguin经常遗漏的重叠语句的数量。我们观察到,平均而言,在总共50个遗漏语句中,大约17个是两者共有的,这意味着重叠明显低于遗漏语句的总数。回答RQuar:如果我们把ChatGPT和Pynguin结合起来,一个可能的逻辑推论是,唯一遗漏的语句将是重叠的语句,其余的语句,以前被Chat-GPT和Pynguin单独遗漏的语句,现在将被覆盖。例如,根据我们的评估,这两个工具的组合将导致31个以上的语句被覆盖。这如表5和图7所示。这也符合CODAMOSA的发现,该发现提出SBST和LLM的组合可以带来更好的覆盖率。

表5 遗漏重叠语句的汇总统计

图7 每个代码示例的ChatGPT、Pynguin及其交集的遗漏语句

转述:顾思琦

0 阅读:0

互联不一般哥

简介:感谢大家的关注