使用Transformer和焦点上下文生成单元测试用例

互联不一般哥 2024-04-20 21:06:33

Unit Test Case Generation with Transformers

and Focal Context

Michele Tufano, Dawn Drain, Alexey Svyatkovskiy, Shao Kun Deng, Neel Sundaresan

引用

Tufano M, Drain D, Svyatkovskiy A, et al. Unit test case generation with transformers and focal context[J]. arXiv preprint arXiv:2009.05617, 2020.

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

摘要

ATHENATEST是一种创新的单元测试用例生成方法,它通过分析真实世界的焦点方法和开发者编写的测试用例来学习生成更符合开发者习惯的测试用例。该方法结合了自然语言处理和源代码分析技术,通过两阶段训练——无监督的去噪预训练和有监督的微调——来优化测试用例的质量,实现了在验证损失上的显著提升。相较于其他工具,ATHENATEST在测试覆盖率和易读性方面展现出优势,专业开发者的反馈也表明其生成的测试用例更受欢迎,有望显著提升软件开发过程中的测试效率和代码质量。主要分为引言、方法、实验设计及结果这三部分。

1 引言

在软件开发生命周期中,软件测试是至关重要、具有挑战性且成本高昂的阶段。为了在不牺牲软件质量和正确性的前提下加快交付速度,科技公司通常依赖于持续集成和持续交付,这允许软件快速且可靠地部署到生产环境。自动化测试作为这一流程的基础环节,为开发者提供了快速迭代和集成新功能的信心,同时避免了回归错误。

单元测试构成了测试金字塔的基础层次,位于集成测试和端到端测试之下。这一视觉隐喻旨在指导我们在每个测试层面上应投入的努力程度。因此,单元测试层应拥有最多的测试用例,在这一层中,软件的单个单元(例如,一个单独的方法)被隔离测试,以确保它们按预期行为运作。

JUnit等单元测试框架提供了一个环境和API,便于编写和执行可重复的测试用例。JUnit提供断言方法,支持开发者检查软件程序中的条件、输出或状态,评估其预期行为。在JUnit的基础上,还构建了如Cactus和TestNG等其他框架。还有一些框架可以与JUnit集成,以支持不同的场景或测试方法,例如Mockito,它允许通过替换功能性的虚拟实现来模拟对象,从而将测试重点放在被测试的方法上。

在这些框架的基础上,研究人员提出了多种旨在自动化生成单元测试用例的技术。EvoSuite、Randoop和Agitar是这类技术中最受欢迎和广泛使用的示例。EvoSuite依赖于基于遗传算法的进化方法来生成单元测试用例,目标是代码覆盖率标准,如分支和行覆盖率。具体来说,它引入突变体(即被测试方法或类的修改版本),并迭代生成断言语句以消除这些突变体。在这个过程中,EvoSuite尽量减少断言的数量,同时尽量检测出更多的突变体。

Randoop是一个不同的自动化测试生成工具,它依赖于反馈驱动的随机测试,这是一种使用执行跟踪来指导方法序列选择的技术,然后将这些序列与用户指定的合同(即用户指定的程序逻辑)进行对比检查。

在自动化测试领域,尽管存在多种工具和技术,但它们仍面临一些批评和挑战。主要问题之一是这些工具生成的测试用例可读性和理解性差,因为它们看起来显然是机器生成的代码。此外,这些自动化工具还有其他局限性,包括代码质量不令人满意、故障检测能力不足,以及无法充分满足工业开发者的软件测试需求。这些问题的根源在于这些方法主要关注代码覆盖率作为唯一目标,忽视了对开发者可能相关的其他因素。

近年来,深度学习技术在从现实世界例子中学习方面展现出巨大潜力,并已被应用于多项软件工程任务,例如代码自动完成、自动补丁生成、注释生成等。特别是,像OpenAI的GPT-3这样的变换模型最近取得了显著进展,在真实文本生成和问答任务中展示了令人印象深刻的结果。

本文介绍了一种新方法,旨在学习开发者编写的测试用例,以生成正确且易于阅读的测试用例。该方法依赖于一个大型的序列到序列变换模型,该模型在英文和Java源代码上进行了预训练,然后在生成单元测试用例的任务上进行了微调。为此,我们挖掘了数千个真实世界的测试用例,并将它们映射到相应的焦点方法,然后使用这个并行语料库进行训练和评估。

总结来说,我们的贡献包括:

ATHENATEST:一种基于序列到序列变换模型的自动化测试用例生成方法。该方法能够为Defects4j项目生成数千个语法正确、可编译和通过的测试用例,调用了各种测试API。与EvoSuite相比,生成的测试用例具有可比的测试覆盖率,并且在可读性、理解性和测试效果方面受到专业开发者的青睐。这些测试用例似乎是:(i) 真实的——类似于开发者编写的测试用例;(ii) 准确的——正确断言焦点方法的预期行为;(iii) 人类可读的——代码可读且易于理解,具有良好的变量和方法命名。METHODS2TEST:最大的公开可用的并行测试用例语料库,将测试用例映射到相应的焦点方法。该数据集列出了从91K个开源Java项目中提取的780K个映射测试用例。

2 方法

图1展示了实验流程的概览。首先,我们从GitHub获取了一组Java开源项目的数据集,然后,我们挖掘测试用例并将它们映射到相应的焦点方法(第2.1节)。接下来,我们采用一个变换模型(第2.2节),该模型已经在英文和源代码语料库上进行了预训练(第2.3节)。我们选择围绕焦点方法的最佳上下文(第2.4节),并针对生成单元测试用例的任务进行微调(第2.5节)。

图1 整体实验方法设计

2.1 数据收集阶段

本研究的目标是从一系列Java项目中挖掘测试用例及其对应的焦点方法。研究团队从GitHub上选取了91,000个样本,这些样本均为声明开源许可、在过去五年内有更新且非fork的公共Java仓库。

解析过程:使用tree-sitter解析器对每个项目进行解析,自动收集类和方法及其元数据,包括名称、签名、体、注解和变量等信息。这些解析后的代码用于识别测试用例和对应的焦点方法,并为焦点方法提供上下文信息。

测试类和焦点类的识别:通过特定的注解(@Test)来标记测试类,即包含至少一个测试方法的类。对于每个测试类,研究团队采用路径匹配和名称匹配的启发式方法来识别对应的焦点类,即被测试的类。

焦点方法的识别:对于测试类中的每个测试方法,研究团队采用名称匹配和唯一方法调用的启发式方法来识别焦点类中的对应焦点方法。如果测试方法仅调用了焦点类中的一个方法,那么这个方法很可能就是被测试的方法。

映射测试用例:数据收集阶段的结果是一组映射的测试用例,每个测试用例都映射到对应的焦点方法。研究团队设计了基于最佳测试实践的启发式方法,以高置信度获得正确的映射。这使得模型能够在遵循最佳实践的测试用例上进行训练,同时可能排除那些自动生成的测试用例。最终,研究团队收集了887,646对映射的测试用例,排除重复项后,剩下780,944对唯一的映射测试用例。这些数据被分为训练集(约80%,624,022对)、验证集(约10%,78,534对)和测试集(约10%,78,388对),在分割过程中特别注意防止数据泄露。

研究团队公开了METHODS2TEST数据集,用于训练模型,以生成给定焦点方法的测试用例。这一数据集的发布将有助于推动自动化测试领域的研究和实践。表1详细报告了数据集分割的结果,包括涉及的代码库数量和映射测试用例的数量。这些映射的测试用例集合是模型训练的基础,目的是教会模型如何根据给定的焦点方法生成相应的测试用例。

表1 METHODS2TEST数据集

2.2 BART Transformer模型概述

ATHENA系统采用了基于BART Transformer模型的架构。BART是一种去噪自编码器,它沿用了标准的序列到序列的Transformer架构,并以GeLU激活函数替代了传统的ReLU。这种选择的原因在于BART模型在微调下游的测试用例生成任务时表现出色,它提供了一套更为先进的噪声转换技术,包括标记遮蔽、标记删除、填充和语句置换等。BART模型通过破坏文档内容并优化解码器输出与原始输入序列之间的交叉熵损失来进行预训练。在ATHENA系统中,我们使用了具有12个编码器层和12个解码器层的BART大型模型架构。

模型的训练采用了混合精度方法,并使用了Adam随机优化算法,其优化器参数设置为学习率为,和分别为0.9和0.98。此外,我们还采用了基于更新步数的逆平方根学习率调度策略,基础学习率设为0.0001,热身期为5000个更新步,以及每4个更新步进行一次局部梯度累积。

2.3 预训练

在ATHENA系统中,我们采用了两个阶段的预训练方法:英语预训练和代码预训练。这两个阶段旨在让模型学习自然语言的语义和统计特性,以及源代码的语法和特性。

英语预训练:英语预训练阶段,我们以半监督的方式对大量英文文本进行预训练,目的是让模型掌握自然语言的结构和用法。我们使用了来自书籍、维基百科和新闻文章的160GB英文文本,进行了40个时代的训练。BART模型在这个过程中以无监督的方式进行,目标是重建原始文本,通过遮蔽30%的标记并根据Poisson分布进行句子置换来破坏文本。

代码预训练:代码预训练阶段,我们专注于让模型学习Java语言编写的源代码的语法和特性。我们通过爬取GitHub上所有至少有50星的公共非分叉Java仓库来收集代码语料库,并通过哈希函数在文件级别进行去重。

经过对许可证的筛选和基于非ASCII字符比例等启发式规则的过滤,我们得到了来自26,000个仓库的25GB训练数据。与英语预训练类似,源代码文件通过独立删除20%的标记并旋转一半的文档来进行破坏。此阶段的训练持续了10个时代。

模型预训练变体:经过这两个预训练阶段,我们得到了四种不同预训练程度的模型变体:

BART_Scratch:未经任何语料库预训练,直接在测试用例生成任务上进行微调的模型。BART_English:先在英文语料上预训练,然后针对测试用例生成任务进行微调的模型。BART_Code:先在源代码语料上预训练,然后针对测试用例生成任务进行微调的模型。BART_English+Code:先在英文语料上预训练,再在源代码语料上进一步预训练,最后针对测试用例生成任务进行微调的模型。

2.4 焦点上下文

在本节中,我们将探讨如何为模型构建输入,以包含模型生成正确且有用的测试用例所需的必要信息。焦点方法(即被测试的方法)是提供给模型的核心信息。然而,额外的上下文信息可以为模型提供重要的线索,帮助模型更好地理解焦点方法的性质及其上下文,从而提高生成可编译且能正确测试焦点方法的测试用例的可能性。

我们构建了不同版本的代码输入表示——具有不同程度焦点上下文——目的是通过实证评估这些代码表示。我们从核心信息(即焦点方法)开始,逐步添加类名、构造函数、其他方法签名和字段等上下文信息。图2提供了我们在Calculator类中为焦点方法添加的不同上下文级别的概览。左侧对应文本表示,右侧则用焦点上下文ID表示上下文。

图2 焦点上下文

fm:此表示仅包含焦点方法的源代码。这是生成给定方法准确测试用例最重要的信息。

fm+fc:此表示增加了焦点类名,这可以为模型提供有意义的语义信息。

fm+fc+c:此表示增加了焦点类构造函数方法的签名。这一增强的想法是测试用例可能需要实例化焦点类的对象以正确测试焦点方法。

fm+fc+c+m:此表示增加了焦点类中其他公共方法的签名。包含这些的动机是测试用例可能需要调用类中的其他辅助方法(例如getter、setter)来设置或拆除测试环境。

fm+fc+c+m+f:此表示增加了焦点类的公共字段。动机是测试用例可能需要检查公共字段的状态以正确测试一个焦点方法。

在构建这些表示时,我们面临两个相互对立的目标:(i) 尽可能多地包含令牌,因为它们具有强大的表达能力;(ii) 保持简洁的表示,以适应GPU内存。包含大量焦点上下文令牌的表示可以让模型关注输入的不同部分,并利用这些信息生成正确且有意义的测试用例。另一方面,不相关的令牌可能对学习过程产生噪声影响,这可能导致性能下降,同时浪费可用于更有信息量令牌的GPU内存。

与模型预训练类似,我们针对每个焦点上下文级别训练不同的模型变体。具体来说,我们获得了五个不同的模型,我们根据相应的焦点上下文ID来称呼它们。这些设计决策的重要性在于,文本表示如果超过1024个令牌(即我们模型中的最大序列长度)可能会被截断。这种包含顺序是由作者根据他们对焦点类内测试用例生成的有意义线索的理解和直觉所定义的。我们将在我们的实证研究中评估这些设计决策。

2.5 微调

在微调阶段,我们的目标是让模型能够为给定的方法生成单元测试用例。我们将这一任务视为一个翻译任务,其中源代码是焦点方法(即我们希望测试的方法),目标代码是软件工程师原本编写的相应测试用例。

微调训练使用收集到的映射测试用例(参见第2.1节),其中映射测试用例可以看作是一对,包含测试用例和对应的焦点方法。微调过程是一个翻译任务,其训练目标是学习条件概率的映射。需要注意的是,我们所指的是焦点方法及其可用的焦点上下文,这取决于模型变体的不同。

在训练过程中,我们使用交叉熵损失函数和Adam优化器,并监控验证集上的损失以进行早停。出于优化的考虑,我们在编码器和解码器之间使用共享的词汇嵌入,因为我们的输入和输出语言是相同的(即Java源代码)。

3 实验设计及结果

3.1 研究问题

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

RQ1: 预训练模型是否对单元测试用例生成任务的性能有影响?RQ2: 焦点上下文如何影响单元测试用例生成任务的训练?RQ3: 生成的测试用例的质量如何?RQ4: ATHENATEST能否为Defects4j项目生成测试用例?RQ5: 我们的方法与EvoSuite和GPT-3相比如何?RQ6: 开发者是否更喜欢ATHENATEST的测试用例而不是EvoSuite的?

Sub-RQ6.1: 哪个测试用例更易读且易于理解?

Sub-RQ6.2: 哪个测试用例更适当地测试了方法?

Sub-RQ6.3: 你更倾向于在你的项目中拥有哪个测试用例?

RQ1 预训练模型对单元测试用例生成性能的影响

实验设计:为了评估预训练模型对单元测试用例生成任务性能的影响,我们对四种不同的模型变体进行了微调:随机初始化的模型(BART_Scratch)、仅在英语语料上预训练的模型(BART_English)、仅在源代码上预训练的模型(BART_Code),以及在英语和源代码上都进行了预训练的模型(BART_English+Code)。这些模型在生成测试用例的任务上进行了独立的微调,直到在验证集上没有显著的改进为止,最多进行50,000步的训练。在所有模型变体上,我们使用相同的最小焦点上下文(仅包含焦点方法)进行训练,以观察预训练效果。

图3 预训练模型 - 验证损失

实验结果:从验证集的交叉熵损失来看,未进行预训练的模型(BART_Scratch)与进行了英语、源代码或两者预训练的模型之间存在显著差距。具体来说,与仅英语预训练的模型相比,源代码预训练和两者预训练的模型在初始损失、最佳损失和收敛速度上都有显著改善。这些结果表明,英语和源代码的预训练对于我们的下游任务是有益的。特别是,BART_English+Code模型在验证损失上取得了最佳表现,因此被选为后续研究问题的起始模型。这表明预训练模型在理解自然语言的语义和统计特性以及源代码的语法和特性方面起到了积极作用,从而提高了生成测试用例的性能。

RQ2 焦点上下文对单元测试用例生成训练的影响

实验设计:本研究旨在评估不同级别的焦点上下文对模型在单元测试用例生成任务上性能的影响。我们从仅包含焦点方法的最小焦点上下文开始,逐步增加额外的上下文信息,如类名、构造函数、其他方法签名和字段。我们训练了五个不同的模型变体,每个变体都使用不同级别的焦点上下文。这些模型变体都是从在英语和源代码上预训练的BART_English+Code模型开始进行微调的。

图4 焦点上下文模型 - 验证损失

图5 焦点上下文 – 成分分析

实验结果:通过对不同焦点上下文模型变体的训练损失进行分析,我们发现增加焦点上下文信息对于模型性能有积极影响。特别是,当加入类名(fm+fc)时,模型显示出更低的初始损失和最佳损失,以及更快的收敛速度。这表明类名作为一个强语义线索,可以帮助模型更好地理解焦点方法的性质和上下文。随着更多上下文信息的加入,如构造函数签名(fm+fc+c)、其他公共方法签名(fm+fc+c+m)和公共字段(fm+fc+c+m+f),模型性能持续改善,但改善幅度逐渐减小。最终,具有最大可用焦点上下文的模型(fm+fc+c+m+f)在验证损失上表现最佳。这些结果证实了焦点上下文信息,除了焦点方法本身之外,还为模型提供了有用的信息,有助于生成正确和有用的测试用例。因此,我们选择了具有最大焦点上下文的模型作为ATHENATEST的目标模型。

RQ3 生成的测试用例的质量

实验设计:

为了深入了解ATHENATEST生成的测试用例的质量,我们对模型预测的测试用例进行了多项评估。首先,我们验证了生成的测试用例的语法正确性,确保它们能够代表符合Java语言规范的源代码方法。其次,我们检查了测试用例是否使用了适当的测试API,这是评估其作为单元测试用例的基本属性。这些属性包括:测试用例是否声明了@Test注解、是否调用了正确的焦点方法,以及是否使用了如JUnit断言API和Mockito框架API等测试框架API来检查焦点方法的正确行为。

图6 测试API分布

图7 测试 API 的故障分布

图8 生成的测试用例示例

实验结果:

ATHENATEST生成的测试用例在大多数情况下(84%)是语法正确的。通过简单的后处理方法,如删除截断的语句并添加闭合括号,我们可以将语法正确率提高到95%。此外,几乎所有生成的测试用例(99.99%)都正确声明了@Test注解,并且94.9%的测试用例正确调用了焦点方法。在测试API的使用方面,生成的测试用例涵盖了多种不同的测试框架API,其分布与原始测试用例相似。这些结果表明,ATHENATEST能够生成符合单元测试标准、调用多种测试API的语法正确的测试用例,且在经过简单的修正后,能够达到非常高的正确性水平。此外,我们还提供了一些定性的测试用例生成示例,以展示模型在生成现实和可读性强的测试用例方面的能力。

RQ4 ATHENATEST能否为Defects4j项目生成测试用例

实验设计:

本研究旨在评估ATHENATEST在为Defects4j项目生成单元测试用例方面的性能。我们选择了Defects4j中的五个流行且常用的开源软件项目,针对每个项目的特定修订版本,使用ATHENATEST生成了30个候选测试用例。这些测试用例经过编译、执行,并收集了测试覆盖率信息。我们对每个候选测试用例进行了分类,包括语法错误、构建错误、执行失败、通过测试和正确测试等类别。

表3 Defects4j结果 - 由ATHENA TEST生成的测试用例根据语法正确性、可编译性、测试执行和覆盖范围分为五类。总体而言,生成的测试中有16.21%是正确的(即编译、通过并覆盖正确的焦点方法),ATHENA TEST能够正确测试43.75%的焦点方法。

实验结果:

ATHENATEST为5,278个焦点方法生成了约158k个测试用例。其中,9.49%的测试用例存在语法错误,42.41%的测试用例虽然语法正确但无法构建,26.71%的测试用例构建成功但执行失败,21.35%的测试用例构建并执行成功。在通过测试的用例中,我们进一步分析了覆盖率信息,发现约16.21%的生成测试用例覆盖了正确的焦点方法,即约25K个测试用例被认为是正确的。此外,ATHENATEST能够为43.75%的焦点方法生成至少一个正确的测试用例。这些结果表明ATHENATEST能够在不同的项目中为大量不同的焦点方法生成正确的测试用例。尽管正确测试用例的比例可能还有提升空间,但我们的实验结果证明了ATHENATEST在自动化测试用例生成方面的潜力。

RQ5 我们的方法与EvoSuite和GPT-3相比如何

实验设计:

为了评估ATHENATEST与其他现有方法的比较情况,我们选择了Defects4j项目中的Lang1-f版本作为测试平台,使用ATHENATEST、EvoSuite和GPT-3三种不同的方法为NumberUtils类中的所有公共方法生成单元测试用例。我们对这些生成的测试用例进行了编译和执行,并通过人工评估它们的正确性。此外,我们使用Defects4j提供的Cobertura覆盖率工具来计算每个测试用例的代码覆盖率。

表4 测试覆盖分析 - EvoSuite、GPT-3和ATHENA TEST生成的测试用例被执行,并根据所覆盖的代码行和条件进行分析。相对于EvoSuite,ATHENA TEST具有相当的覆盖范围。

图9 为createFloat生成的测试

图10 为isDigit生成的测试

实验结果:

在与EvoSuite和GPT-3的比较中,ATHENATEST显示出了竞争力。EvoSuite成功地为所有方法生成了测试用例,而GPT-3只正确测试了18个方法中的6个。ATHENATEST为所有方法生成了正确的测试用例,并且在大多数情况下实现了最佳的代码覆盖率。尽管GPT-3在某些情况下也表现出了生成正确测试用例的能力,但总体而言,ATHENATEST在生成具有适当测试覆盖率的正确测试用例方面表现更佳。此外,ATHENATEST生成的测试用例在可读性和与开发者编写的代码相似性方面也更胜一筹。这些结果表明,ATHENATEST在自动化单元测试用例生成方面是一个有效的工具,能够与成熟的工具如EvoSuite相媲美,并且在某些方面甚至超越了GPT-3这样的大型语言模型。

RQ6 开发者是否更喜欢ATHENATEST的测试用例而不是EvoSuite的?

实验设计:

为了了解开发者对ATHENATEST和EvoSuite生成的测试用例的偏好,我们设计了一个调查,向开发者展示了一个正在测试的焦点方法以及两个不同的测试用例:一个由ATHENATEST生成,另一个由EvoSuite生成。我们要求开发者根据个人偏好评估这些测试用例的可读性、适当的测试方法以及整体偏好。

图11 专业开发者的调查结果

实验结果:

调查显示,大多数开发者(61%)倾向于认为ATHENATEST生成的测试用例在可读性和理解性方面更优,而29%的开发者认为两者相当,只有10%的开发者更偏好EvoSuite的测试用例。在测试方法的适当性方面,70%的开发者选择了ATHENATEST的测试用例,认为它们更适当地测试了方法,而只有18%的开发者更偏好EvoSuite的测试用例。当被问及总体偏好时,82%的开发者选择了ATHENATEST的测试用例,只有18%的开发者选择了EvoSuite的测试用例。这些结果表明,开发者普遍更喜欢ATHENATEST生成的测试用例,因为它们在可读性、理解性和测试有效性方面更符合开发者的期望。

转述:田方源

0 阅读:0

互联不一般哥

简介:感谢大家的关注