1. 摘要
单元测试已经被广泛认为是保障软件质量的有效技术。其也是无聊、耗时的任务。自动化单元测试用例生成,作为用于释放开发者且能够提高测试效率的最有前景的技术,目前实践中表现不能令人满意。作为补充,测试用例推荐得到了研究者的关注。测试用例推荐本质上是测试用例的重用,也就是两个相似的测试目标可以重用彼此的测试用例。已有的方法从单一的角度度量测试目标相似性会使得他们的性能很容易受到字面文本修改的影响。此外,已有方法推荐的测试用例缺少必要的依赖,这会使得推荐的测试用例难以理解。
在本文中,我们提出一种新的测试用例推荐方法,其从多个方面准确地度量测试目标相似性,包括方法签名、注释和代码。这些方面都可以从一定程度上反映出测试目标在功能上的相似性。特别地,在代码方面,我们将控制流上的相似性考虑在内以弥补在仅度量字面文本上相似性上发现的不足。此外,我们通过挖掘相对完整的测试依赖使推荐的测试用例是依赖意识的,这不仅有助于提高其可理解性,而且有助于降低调整它以适用于新测试目标需要付出的努力。作为概念上的应用,我们实现了一个测试用例推荐器命名为 TBooster。我们构建了一个包含超过 13,000 依赖意识的测试用例的候选测试用例集。基于该集合,我们实施了全面的实验以评估 TBooster,评估结果显示 TBooster 可以有效地推荐相关的测试用例并优于现有的最新技术。
2. 相关概念
有关测试用例(Test Case)的严格定义是不存在的,我们试图在测试用例推荐应用场景下精细化该术语。
测试目标(Test Target)。毫无疑问测试用例必须有明确的测试目标。在单元测试中,一个待测单元是软件最小的可测部分。通常,最小的待测单元是一个方法。生产代码中的所有待测的方法都是潜在的测试目标。如图 3b 显示了一个测试目标的示例,即方法 attach()。对于每一测试目标 mi,我们可以从三个方面推测其实现的功能:(1)mi 的注释。注释是自然语言描述,用于帮助开发者理解程序以及降低额外的花费在阅读和索引源代码上的时间。(2)mi 的签名。签名由方法名及其参数类型组成。编译器通过签名区别同一个类中的重载方法。方法名通常由动词和名字组成,并且抽象地显示方法可能执行的动作。参数类型指定了方法的输入行为的状态空间。(3)mi 的代码。代码块包含大量的隐式地表示功能的信息。我们主要考虑字面文本和控制流。
测试方法(Test Method)。测试方法是测试用例的核心部分,主要负责完成测试任务的执行。其通常通过标记注解‘@Test’来声明。例如如图 3a 显示,方法 attachMultiRequests() 是一个测试方法,用于测试测试目标 attach()。
测试依赖(Test Dependency)。测试依赖是指测试方法执行测试期间所依赖的上下文代码元素,例如变量和方法调用。对变量的依赖被称为变量依赖。对方法调用的依赖被视为方法依赖。在变量依赖中考虑了四种变量:局部、全局、外部以及来自第三方库中的变量。在方法依赖中考虑了三种方法:内部、外部以及来自第三方库中的方法。例如,在图 3a 中显示了一些变量(例如,URI_1)和方法调用(例如,mockImageViewTarget())。
图 3:测试方法和待测方法示例
在这,我们简单解释下为什么分析测试依赖很重要。首先,完整的测试依赖是后续测试目标识别的前置条件。例如,在不知变量 action2 的数据类型的情况下,自动化地并正确地将图 3 第 16 行的方法 attach(action2) 识别为一个测试目标并非是容易的任务。其次,完整的测试依赖将会使推荐的测试用例更加易于理解。例如,如果我们仅推荐测试方法 attachMultiRequests() (第 11 至 19 行)而不包含上下文测试依赖(例如,第 4 至 8 行)给开发者,开发者可能会困惑变量 picasso 是什么?
单元测试粒度(Unit Test Granularity)。单元测试粒度是指一个测试方法测试的测试目标数量。每个测试方法应当仅测试一个测试目标而不应该将多个不相关的测试组合在单个测试方法中。直接将不规范的测试方法推荐给开发者是不合适且用户友好的。例如,通过分析测试依赖,我们可以发现图 3a 中的测试方法 attachMultiRequests() 测试了两个测试目标,分别是类 BitmapHunnter 中的构造函数 BitmapHunnter() 和方法 attach()。在这种情况下,测试方法 attachMultiRequests() 的单元测试粒度为 2。为了提高推荐的测试用例的的可理解性,我们应当根据具体的测试目标将不规范的测试方法切割成多个规范化的测试方法。我们将测试方法的切片过程称为单元测试粒度规范化。例如,图 4 中显示的两个测试方法是测试方法 attachMultiRequests() 的切片结果。
测试用例(Test Case)。综上,在本文中,测试用例由规范化的测试方法及对应的测试依赖组成,其中‘规范的’是指测试方法中有且仅有一个明确的测试目标。
图 4:单元测试粒度规范化示例