使用预训练大模型和变异测试进行有效的测试用例生成

互联不一般哥 2024-04-23 09:56:33

Effective Test Generation Using Pre-trained Large Language Models and Mutation Testing

Arghavan Moradi Dakhel , Amin Nikanjam , Vahid Majdinasab ,

Foutse Khomh and Michel C. Desmarais

引用

Dakhel A M, Nikanjam A, Majdinasab V, et al. Effective test generation using pre-trained large language models and mutation testing[J]. arXiv preprint arXiv:2308.16557, 2023.

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

摘要

最近,研究人员利用代码的大语言模型(LLMs)生成单元测试。虽然通常评估生成测试的代码覆盖率,但文献已经承认覆盖率与测试在错误检测效率方面的相关性较弱。为了克服这一限制,在本文中,我们介绍了MuTAP(使用增强prompt的变异测试用例生成),通过利用变异测试来提高LLMs生成的测试用例在揭示缺陷方面的有效性。我们通过用幸存变异体增强prompt来实现我们的目标,因为这些变异体突出了测试用例在检测缺陷方面的局限性。在没有测试程序(PUTs)自然语言描述的情况下,MuTAP能够生成有效的测试用例。我们在MuTAP中使用不同的LLMs,并在不同的基准测试上评估它们的性能。

1 引言

单元测试是测试自动化金字塔的基础,用于检查函数或组件在隔离环境中是否按预期工作。自动生成单元测试是软件工程中的重要课题,旨在减少开发人员的测试工作量,避免软件产品中的错误。现有的自动生成单元测试工具存在一些缺陷,如生成没有断言或过于一般的断言的测试,或者生成断言无法有效评估程序预期行为的测试。

近年来,研究人员开始探索利用基于机器学习的代码合成技术生成更好的单元测试,尤其是利用具有transformer架构的大型语言模型(LLMs),如Codex。然而,现有研究虽然有所进展,但没有尝试提高生成测试的bug检测能力。文献指出,虽然测试覆盖率是评估测试质量的有用指标,但它与测试在检测缺陷方面的效率关联较弱。

变异测试(MT)是一种白盒测试技术,用于评估测试用例揭示缺陷的能力。MT已被广泛研究并成功应用于软件工程中,通过向程序注入基于真实故障的人工更改,生成被称为变异体的PUT的变异版本。生存的变异体突出了测试用例的弱点,目标是使测试用例能够检测所有变异体。变异体不仅有助于评估测试用例的有效性,还可用作设计更有效测试用例的手段。

本文介绍了利用MT增强和评估LLMs生成Python程序的测试用例的有效性的研究。我们的方法旨在优化测试用例以用于bug检测,而不是代码覆盖率。我们的技术MuTAP利用LLM作为主要组件,通过在初始prompt中包含PUT和生成测试用例的指令,使用zero-shot和few-shot学习来生成测试用例。MuTAP评估生成的测试用例的语法,并在检测到语法问题后重新提示LLM以纠正错误。接着,MuTAP评估生成的测试用例的预期行为,通过比较测试用例在某些测试输入上的输出与使用相同测试输入时PUT的预期返回值,从而纠正测试用例中的意外行为。

随后,MuTAP应用MT来检查测试用例在杀死PUT的变异体方面的有效性。生存的变异体突出了生成测试用例的限制,MuTAP通过在初始prompt中增加初始测试用例和生存的变异体来为具有生存变异体的PUT生成新的测试用例。MuTAP在停止增加初始prompt的过程时,要么最终的测试用例能够有效检测所有变异体,要么没有剩余的生存变异体可用于增强初始prompt。

MuTAP的LLMC使用两种类型的LLMs:Codex(用于代码相关任务)和llama-2-chat(优化用于对话用例,足够灵活以适应一系列任务,包括编程)。我们在164个PUT的合成缺陷和从Python错误修复基准测试中收集的1710个有缺陷程序上评估MuTAP。结果表明,我们的方法生成了具有平均变异得分(MS,被杀死的变异体总数与变异体总数的比率)为93.57%的有效测试用例,优于Pynguin(最先进的全自动测试生成工具)和传统的LLM基础的zero-shot/few-shot学习技术。此外,我们的方法检测到比其他可比方法多达468个(28%)人类编写的有缺陷代码片段。值得注意的是,它识别出79个(17%)人类编写的有缺陷代码片段,其他技术均无法检测到。

综上所述,本文的贡献包括:

我们提出了利用MT生成LLMs测试用例的第一项研究。我们提出了一种基于prompt的学习技术,通过将prompt与PUT的初始测试用例和生存的变异体相结合来提高测试用例的有效性。我们评估了生成的测试在检测真实和合成有缺陷PUT的bug方面的效果。我们将所提出的技术MuTAP公开在线提供,供其他研究人员/实践者复制或建立在我们工作基础上的工作。

2 技术介绍

整体流程如图1所示。

图 1:使用LLMs生成和评估测试的方法

图 2:MuTAP在PUT上的不同步骤。2是由初始prompt 1为PUT生成的一组测试用例,4是在初始prompt中增加存活的突变体SM0后获得的一组测试用例。3'显示了在更新为PUT0的另一个存活的突变体SM1后的突变体组件。

2.1 初始Prompt

大型语言模型(LLMs)能够执行它们已经训练过的任务。对LLMs进行微调以执行新任务是计算密集的。此外,像Codex这样表现出色的LLMs是闭源的,因此不可能对它们进行微调以执行新任务。

基于Prompt的学习是一种有效的技术,用于使LLMs适应新任务。Prompt是自然语言和/或编程语言上下文的组合,作为LLMs的输入。有研究表明,在prompt中放入自然语言指令(zero-shot learning)或多个示例(few-shot learning)可以增强LLMs执行新任务的能力。

MuTAP利用zero-shot learning和few-shot learning构建初始prompt,并分别在它们上调用LLMC。这一步骤在算法2中展示。

在初始prompt中没有待测程序(PUT)的自然语言描述,因为这样的描述可能并不总是可用,MuTAP依赖于LLMC合成代码上下文的能力。MuTAP调用初始prompt,zero-shot学习或few-shot学习,在LLMC上,然后将推断的输出传递到下一步(算法1中的第2行)。

2.2 优化

我们描述了在MuTAP中优化生成的测试用例的过程,其中包括修复语法错误和修复预期行为。具体细节如算法3所示。

2.2.1 语法修复器

由LLMC生成的测试用例可能存在语法错误(缺少括号、未完成的行等)。由于MuTAP需要执行测试函数以进行对MT的调查和prompt增强,具有语法错误的样本变得低效。然而,有时LLMC的输出稍作更改即可修复语法错误并将其转换为可执行的测试用例。

MuTAP利用其LLMC的能力来修复语法错误。为此,LLMC在新prompt上被调用以修复其自身输出中的语法错误(算法3中的Procedure SyntaxFixer)。语法修复prompt由两部分组成。第一部分是自然语言指令,“修复以下代码片段中的语法错误”,第二部分是LLMC在初始prompt上生成的测试函数(算法3中的第7-8行)。如果即使重新提示LLMC后语法错误仍然存在,MuTAP将使用Python解析器来识别错误的行。然后保留问题行之前的行,确保它们不包含语法错误(算法3中的第13行)。

2.2.2 预期行为修复

基于初始prompt,LLMC生成不同的测试用例,通过调用PUT对某些输入进行序列化,并将PUT的返回输出与预期输出或基本事实进行比较,例如,{assert add (2,2) == 4}。然而,LLMC可能生成断言错误返回值的测试用例。这意味着对于某些测试用例,LLMC未生成PUT的预期返回输出。初始prompt中缺乏关于PUT的自然语言描述可能导致生成未能准确反映方法预期行为的测试用例。

断言具有错误返回值可能在变异体上失败,不是因为检测到错误,而是因为断言的意外行为。这些失败会导致对测试用例的有效性产生困惑。因此,MuTAP的这一步旨在修复测试用例中断言神谕的预期行为(算法3中的Procedure IntendedBehaviorFixer)。

对于测试中的每个断言,MuTAP在测试输入上运行PUT,并将PUT的返回输出与断言输出进行比较。如果PUT的返回输出与神谕中的断言输出相同,则MuTAP将其视为具有正确预期行为的断言神谕。否则,它通过将断言输出替换为PUT的预期输出来修复这些断言(算法3中的第22-27行)。MuTAP省略了那些在PUT上失败的输入类型的断言,例如,如果PUT期望一个整数列表,但测试输入是一个字符串。这一步的最终结果被称为初始单元测试(IUT),它是经过精炼后由LLMC生成的一组测试用例,如图1的2中所示。

2.3 变异测试(MT)

变异测试(MT)评估测试用例的质量和有效性。通过向PUT注入人为错误来构建变异体,以模拟缺陷。如果测试用例在一个变异体上失败,我们将其视为被杀死的变异体,否则,它幸存下来,这意味着单元测试中的测试用例无法检测到它。幸存变异体的存在突出了测试用例的不足之处,表明需要添加新的测试用例或改进现有测试用例。变异分数(MS)通过计算被杀死的变异体占PUT所有变异体的比率来表示测试用例的有效性。

算法4呈现了这一步骤的详细信息。MuTAP使用MutPy为每个PUT生成不同的变异体并计算MS(算法4中的第3-7行)。在每个变异体上执行测试用例涉及执行一些初步设置。为此,MuTAP使用Python内置的“setuptools.find_packages”来定位和安装所需的包,如"math"、"numPy"、"pandas"、"pytest"等。此外,MuTAP实现了负责创建临时目录的设置函数,在对变异体上的测试用例执行过程中使用这些目录。在对变异体执行测试用例并计算MS之后,MuTAP通过删除临时目录来正确拆除设置。

如算法1中的第5-9行所示,如果一个PUT的MS达到100%,MuTAP将测试用例传递给oracle最小化步骤,否则,它收集幸存变异体的列表,并将这些变异体转移到prompt增强步骤。

2.4 Prompt增强

算法5显示了这一步骤的详细信息。如果在上一步中有任何存活的突变体,MuTAP会通过添加四个新组件(算法5中的第3行)来扩充初始prompt,zero-shot或few-shot。第一个组件是IUT,即LLMC在优化后生成的初始单元测试。第二个组件是一条用自然语言命名的指令INS3,通过“测试函数test()无法检测以下代码中的错误”来澄清IUT的缺点。第三个组件是PUT的存活突变体之一,命名为SM。最后一个组件,INS4是一条自然语言和编程语言的指令:自然语言上下文通过要求“提供一个新的测试用例来检测以前代码中的错误”来澄清任务,而编程语言上下文仅作为引导LLMC生成输出的prompt。示例如图1的3所示。

MuTAP重新提示LLMC,并在生成的输出上重复优化步骤(算法5中的第4-5行)。然后,将新生成的测试用例附加到IUT,我们称之为增强单元测试(AUT)。将AUT传递给MT步骤(算法5中的第7行)。MuTAP递归地重复prompt增强,直到最终的测试用例杀死所有的突变体(MS = 100%),或者没有存活的突变体未在增强过程中使用(算法5中的第8行)。在图1中更新突变体组件的示例,3通过用SM0替换为SM1而更改为3'。4'表示在下一个存活的突变体上迭代该过程后由LLMC生成的测试用例。

2.5 Oracle最小化

由LLMC生成的测试用例通常包含冗余断言。此外,增强过程可能会向最终单元测试添加更多冗余断言。将所有这些(带有冗余)呈现为最终输出可能会使开发人员感到困惑。在最终步骤中,类似于以前生成基于突变的测试的工具,MuTAP通过利用贪婪技术来最小化断言的数量,消除不改善MS的冗余断言。该步骤在算法6中呈现。MuTAP通过跟踪每个断言杀死的突变体数量开始,然后选择包含杀死最大数量突变体的断言的测试用例。然后通过添加包含检测到最多突变体的下一个断言的测试用例来重复此过程(算法6中的第4-10行)。如果添加此新断言会增加MS,MuTAP将保留测试用例及其断言。否则,该测试用例将被丢弃为冗余。

3 实验评估

3.1 实验设置

研究问题:

RQ1:与自动生成测试用例工具生成的测试用例相比,MuTAP生成的测试用例效果如何?

RQ2:MuTAP的不同部分的表现如何?

RQ3:对于每种突变类型,MuTAP的性能如何?

评估数据集。我们使用两个不同的基准数据集进行实验。HumanEval是用于评估生成代码的LLMs的基准,包含164个易到中等难度的人类编写的编程问题。Refactory是用于Python错误修复的基准,包含1710个有错误的学生提交,每个任务有一个正确的参考解决方案。这些数据集能够让我们评估MuTAP生成的测试用例,并与Pynguin和基线进行比较。

Baseline。我们提出了两个baseline来评估MuTAP方法。Before-refining基线是在LLMC上初始prompt的输出,没有修复语法错误或修复预期行为。After-refining基线是在LLMC上初始prompt的输出,应用了优化和oracle最小化步骤。

RQ1 MuTAP与其他生成测试工具比较结果

我们的研究旨在通过MT来提高测试用例的有效性,我们将MuTAP与Pynguin以及我们的基线进行比较,从MS杀死的突变体数量和具有100% MS的PUT数量方面进行比较。值得一提的是,我们仅考虑具有正确测试用例的PUT来计算每种方法的平均MS。因此,我们报告了杀死的突变体总数和具有100% MS的PUT总数,以进行公平比较。

对于HumanEval基准数据集,上表显示了获得的结果。在进行语法修复和预期行为修复之前,使用zero-shot prompt时,Codex和llama-2-chat生成的测试用例对于164个PUT中的73和68个是不正确的。然而,它们成功杀死了295和318个(共1260个)突变体。

在Codex的输出受初始prompt影响较大,相对于llama-2-chat。将初始prompt切换为few-shot会将没有测试用例的PUT数量减少到39个,同时使用Codex作为LLMC时,杀死的突变体数量增加到508个。另一方面,使用llama-2-chat时,没有测试用例的PUT数量减少到60个,杀死的突变体数量从318增加到325。这种性能差异可能归因于llama-2-chat更适合对话prompt,使用一对演示输入和输出的prompt,缺乏自然语言环境,不会显著提高模型的性能。

相比之下,作为最先进的自动生成测试工具,Pynguin在优化前的输出中击败了两个LLM的输出,杀死了649个突变体,并未为31个任务生成测试用例。

在应用语法修复和预期行为修复的后处理步骤后,MuTAP在杀死更多突变体方面优于Pynguin。值得注意的是,在使用zero-shot和few-shot prompt后,llama-2-chat能够为所有PUT生成正确的测试用例。然而,在杀死突变体方面,其有效性分别为84.04%和85.16%。

MuTAP增强了由Codex和llama-2-chat生成的测试用例的有效性,实现了89.13%和91.98%的MS(zero-shot prompt),以及92.02%和93.57%的MS(few-shot prompt)。特别是,在使用llama-2-chat作为其LLMC时,MuTAP使用few-shot prompt能够杀死1260个中的1179个突变体,并为70%的PUT生成MS=100%的测试用例,相比之下,Pynguin只能杀死649个突变体,并有28.22%的PUT具有MS=100%。

上表显示了来自Refactory基准数据集的真实有错误程序的结果,这证实了我们在HumanEval上的发现。MuTAP在实际有错误代码上的表现优于Pynguin和优化后的方法,使用few-shot学习时,llama-2-chat作为其LLMC,辨别出468个比Pynguin更多的有错误代码(MS为94.91% vs. 67.54%),比优化后的方法多出111个有错误代码(MS为94.91% vs. 82.51%)。此外,MuTAP发现了79个有错误代码,Pynguin或llama-2-chat的测试用例在优化后的过程中未能检测到。当使用Codex时,MuTAP检测到73个有错误代码,这些错误代码被Pynguin和Codex在优化后的阶段漏掉。此外,MuTAP在应用贪婪优化后生成了更有效的测试用例,平均为2.6个。

结果 1:MuTAP使用llama-2-chat和Codex相比Pynguin表现更好,能够更好地杀死突变体并检测有错误代码。通过优化和prompt增强的后处理步骤,这些测试用例在检测缺陷方面的有效性得到提高。MuTAP生成的测试用例比Pynguin和传统的zero-shot和few-shot学习在LLM上生成的测试用例更有效。MuTAP的测试用例数量在最小化后并不比其他方法的输出多很多。此外,具有对话设置的LLM在增强prompt上表现更好。总之,通过使用幸存的突变体和后处理优化,LLM生成的测试用例的有效性可以得到提高。

RQ2 MuTAP的不同部分的表现

语法修复:平均而言,使用Codex时,zero-shot和few-shot prompt的测试用例中存在语法错误的百分比分别为38.98%和26.48%。当使用llama-2-chat时,zero-shot和few-shot prompt的语法错误百分比分别为33.85%和26.32%。

在考虑语法错误时,有三个因素有助于减少LLM输出中的语法错误。第一个因素是初始prompt的类型。few-shot learning导致两个LLM的输出中的语法错误更少。具体来说,当使用Codex时,经过优化后,语法错误的百分比从44.79%降至29.03%,对于MuTAP,从33.17%降至23.93%。使用llama-2-chat作为LLMC时,经过优化后,语法错误的百分比从38.03%降至26.99%,对于MuTAP,从29.66%降至25.64%。

第二个影响较大的因素,也是主要因素,是语法修复组件。当使用Codex时,MuTAP中的语法修复组件平均通过利用LLMC修复14.5%的语法错误,并通过省略导致错误的行修复81.37%的语法错误。另一方面,当将llama-2-chat作为MuTAP的LLMC时,语法修复组件平均通过重新提示LLMC解决32.31%的语法错误,通过省略有问题的行解决60.73%的错误。

改善测试用例中语法错误的最后一个因素是MuTAP中的prompt增强过程。通过用IUT增强prompt,使用zero-shot技术时,Codex的输出中的语法错误的百分比从44.79%降至33.17%。类似地,使用llama-2-chat和zero-shot prompt时,语法错误的百分比从38.03%降至29.66%。通过用IUT增强prompt,提供测试用例的示例,类似于few-shot学习prompt中的演示示例,有效减少了LLM输出中的语法错误。

在Refactory基准数据集上的发现显示,使用Codex和few-shot learning时,MuTAP仅在一个PUT(共5个)中生成带有语法错误的测试用例。此外,没有一个语法错误可以通过重新提示LLMC来修复。另一方面,在两种初始prompt类型下,使用llama-2-chat时,语法错误减少到零。

预期行为修复:在修复预期行为方面,有两个不同的因素有助于降低断言预言中的错误率。当使用Codex作为LLMC时,预期行为修复步骤在优化后和MuTAP中平均修复了83.98%和89.86%的不正确行为。当使用llama-2-chat时,这一步骤分别修复了优化后和MuTAP中的84.35%和95.96%的意外行为。

除了预期行为修复步骤外,MuTAP中的prompt增强步骤显著减少了测试用例中意外行为的发生。例如,使用zero-shot prompt时,Codex中意外行为的断言,如错误的返回值,从63.63%降至19.38%。类似地,使用llama-2-chat和few-shot prompt时,意外行为的断言从63.25%降至10.75%。这种改善的原因可以归因于MuTAP中使用IUT(初始单元测试)来增强初始prompt。这些IUT已经代表了PUT的预期行为,从而帮助LLM提出具有较少意外行为(即更少错误的返回值)的测试用例。此外,在Refactory基准数据集上,MuTAP修复了所有具有不正确行为的断言。

与语法错误不同,prompt类型对断言中的意外行为帮助不大。预期行为修复步骤和prompt增强过程的结合提高了测试用例的有效性,确保它们符合PUT的预期行为。

幸存的突变体表示:我们还研究了在prompt增强过程中幸存的突变体顺序对MS的影响。图3展示了在所有PUT的5次运行中,使用随机顺序的幸存突变体增强prompt的效果。为了进行比较,我们随机选择每个具有MS<100%的PUT中的一个幸存突变体,并将其用于增强初始prompt。然后计算所有PUT的平均MS。随后,我们随机选择第二个幸存突变体,对剩余具有MS<100%的PUT(如果有的话)重复增强过程,并再次计算所有PUT的平均MS。我们继续重复这个过程,直到没有更多具有MS<100%的PUT,或者没有更多未在增强过程中使用的幸存突变体。

图 3:利用不同随机顺序的幸存突变体对不同LLM的MS产生的影响。每个数据点代表在五次不同运行中所有PUT的平均MS,其中幸存突变体被随机选择用于prompt增强过程。

如图3所示,每个数据点代表所有PUT的5次随机选择幸存突变体的平均MS。值得注意的是,仅使用一半的幸存突变体就可以达到超过90%的MS,而在不同LLM中重复增强步骤后,MS的改善会停滞。例如,当使用Codex作为LLMC时,在zero-shot学习中,即使平均有27个幸存突变体(共226个)未在增强prompt步骤中使用,MS的改善也停止了。类似地,在few-shot学习中,这个数字为24个(共106个)。

结果 2:我们针对RQ2的结果表明,由LLM生成的测试用例,无论初始prompt类型如何,都需要后处理,例如语法修正或预期行为修复,以确保其正常运行并有效地检测错误。另外,增强prompt时幸存的突变体顺序对MS的提高并没有显著影响。

RQ3 性能

在这个研究中,我们评估了MuTAP在不同突变类型上的性能。我们在下表中报告了在HumanEval基准测试中每种方法对幸存突变体的总数和被杀死的幸存突变体的数量。报告每种方法对每种突变类型的性能有助于进行比较。每种方法中每种类型的突变总数都不同,因为所有方法中有问题的PUT数量并不相同。每种类型/方法的MS表示被杀死的幸存突变体占该类型总数的比例。一些突变类型更常见(这些类型中有更多样本),例如AOR、COI和ROR。每种类型中的突变数量取决于PUT。例如,在HumanEval中,有一些带有异常处理的PUT。因此,在EHD中的突变较少。

结果 3:总的来说,与Pynguin和LLMs优化后的输出相比,MuTAP在所有突变类型中表现更好或类似。以ASR为例,MuTAP在所有方法中在这种突变类型上表现最佳。例如,Pynguin生成的测试用例在这个类别中识别了45个突变体,而使用llama-2-chat和few-shot生成的MuTAP测试用例在这个类别中识别了79个突变体(共84个)。

对于一种突变类型BCR,在我们的基准测试中是一种罕见类型,MuTAP和优化后的zero-shot和few-shot prompt,以及使用Codex,表现相同。然而,当使用llama-2-chat时,MuTAP通过消灭更多这种类型的突变体表现优异。对于我们数据集中另一种罕见的突变类型EHD,值得注意的是,尽管Codex使用了两种初始prompt类型和增强过程,却未能生成用于检测此类别中存在的两个突变体的测试用例。相反,MuTAP使用few-shot prompt和llama-2-chat成功消灭了这一类别中的所有突变体。

转述:宗兆威

0 阅读:0

互联不一般哥

简介:感谢大家的关注