对遗留应用程序进行现代化改造和改造是一项具有挑战性的活动,涉及多项任务。其中一项关键任务是验证现代化应用程序是否保留了旧应用程序的功能。不幸的是,这可能很乏味且难以执行。遗留应用程序通常没有自动化测试用例,或者,如果可用,测试覆盖率可能不足,无论是一般情况下,还是专门用于涵盖与现代化相关的更改。维护不善的测试套件还可能包含许多过时的测试(随着应用程序的发展而积累)。因此,在大多数现代化项目中,验证主要是手动完成的——这是一个耗时且可能无法充分测试应用程序的过程。在一些报告的案例研究中,测试约占现代化项目所花费时间的 70% 到 80% [1]。 Tackle-test 是一种旨在应对这一挑战的自动化测试工具。

Tackle-test概述

Tackle-test 的核心是 Java 应用程序单元测试用例的自动生成器。它可以生成带有断言的测试,这使得该工具在现代化项目中特别有用,其中应用程序转换通常是保留功能的——因此,可以通过观察遗留应用程序版本的运行时状态来创建有用的测试断言。这可以使旧版和现代化应用程序版本之间的差异测试更加有效;没有断言的测试用例将仅检测现代化版本在旧版本成功执行的测试输入上崩溃的那些差异。 Tackle-test 生成的断言在每个代码语句之后捕获创建的对象值,如下一节所示。

更多关于 Java

  • 什么是企业Java编程?

  • Red Hat 构建的 OpenJDK

  • Java 备忘单

  • 免费在线课程:使用微服务架构开发云原生应用程序

  • Java新鲜文章

Tackle-test 使用一种新颖的测试生成技术,该技术将组合测试设计 (CTD)(也称为组合测试或组合交互测试 [2])应用于方法接口,目的是对具有“复杂接口”的方法进行严格测试,”其中接口复杂性的特征在于可以调用方法的参数类型组合的空间。 CTD 是一种众所周知的、有效的和高效的测试设计技术。它通常需要以 CTD 模型的形式手动定义测试空间,包括一组参数、它们各自的值以及对值组合的约束。测试空间中的有效测试被定义为为满足约束的每个参数分配一个值。 CTD 算法自动构建有效测试集的子集,以覆盖每个 t 参数的所有合法值组合,其中 t 通常是用户输入。

尽管 CTD 通常以黑盒方式应用于程序输入,并且 CTD 模型是手动创建的,但 Tackle-test 会自动为每个被测方法构建基于参数类型的白盒 CTD 模型。然后它生成一个由模型的覆盖目标组成的测试计划,并综合测试序列以覆盖测试计划的行。可以在不同的、用户可配置的交互级别生成测试计划,其中更高的级别导致生成更多的测试用例和更彻底的测试,但代价是增加了测试生成时间。

Tackle-test 还利用一些现有的和常用的测试生成策略来最大化代码覆盖率。具体来说,这些策略包括反馈驱动的随机测试生成(通过Randoop开源工具)和进化和基于约束的测试生成(通过EvoSuite开源工具)。这些工具计算代码元素中的覆盖率目标,例如方法、语句和分支。

滑车测试组件

图片来源:

图 1:Tackle-test 的高级组件。

图 1 展示了 Tackle-test 主要组件的高级视图。它由一个基于 Java 的核心测试生成器(生成 CTD 驱动的测试)和一个基于 Python 的命令行界面 (CLI) 组成,后者是用户交互的主要机制。

工具入门

Tackle-test 在 Konveyor 组织 (https://github.com/konveyor/tackle-test-generator-cli) 下作为开源发布。首先,克隆 repo,然后按照 repo 自述文件中提供的安装和运行工具的说明进行操作。有两种安装选项:使用 docker/docker-compose 或本地安装。

CLI 提供了两个主要命令:用于生成 JUnit 测试用例的generate和用于执行它们的execute。要验证您的安装是否成功完成,请使用位于 test/data 文件夹中的示例irs应用程序来运行这两个命令。

generate命令附带一个指定测试生成策略(ctd-amplifiedrandoopevosuite)的子命令,并创建 JUnit 测试用例。默认情况下,差异断言被添加到生成的测试用例中。让我们使用 CTD 引导策略在irs样本上运行 generate 命令。

$ tkltest --config-file ./test/data/irs/tkltest_config.toml --verbose 生成 ctd-amplified

[tkltest|18:00:11.171] 加载配置文件 ./test/data/irs/tkltest_config.toml

[tkltest|18:00:11.175] 使用 CTD 计算覆盖目标

* CTD 交互级别:1

* 班级总数:5

* 针对 5 个类

* 为5个目标类的20个目标方法创建了总共20个测试组合

[tkltest|18:00:12.816] 使用 CTD 计算测试计划耗时 1.64 秒

[tkltest|18:00:12.816] 使用 CombinedTestGenerator 生成基本块测试序列

[tkltest|18:00:12.816] 测试生成器输出将写入 irs_CombinedTestGenerator_output.log

[tkltest|18:01:02.693] 使用 CombinedTestGenerator 生成基本块测试序列耗时 49.88 秒

[tkltest|18:01:02.693] 扩展序列以达到覆盖目标并生成 junit 测试

* u003du003du003d 总 CTD 测试计划覆盖率:90.00% (18/20)

* 在所有序列中总共添加了 64 个差异断言

* 为生成 CTD 放大测试 (JSON) 编写了摘要文件

* 将 5 个测试类文件写入“irs-ctd-amplified-tests/monolithic”,共有 18 种测试方法

* 编写 CTD 测试计划覆盖率报告 (JSON)

[tkltest|18:01:06.694] JUnit 测试保存在 ./irs-ctd-amplified-tests

[tkltest|18:01:06.695] 扩展测试序列和编写 junit 测试耗时 4.0 秒

[tkltest|18:01:06.700] CTD 覆盖率报告保存在 ./irs-tkltest-reports/ctd report/ctdsummary.html

[tkltest|18:01:06.743] 生成的 Ant 构建文件 ./irs-ctd-amplified-tests/build.xml

[tkltest|18:01:06.743] 生成的 Maven 构建文件 ./irs-ctd-amplified-tests/pom.xml

irs样本上生成测试需要几分钟时间。默认情况下,该工具在每个类的初始测试序列生成上花费 10 秒。但是,由于额外的步骤,整体运行时间可能会更长,如下节所述。请注意,每个类选项的时间限制是可配置的,对于大型应用程序,测试生成可能需要几个小时。因此,在对所有应用程序类执行测试生成之前,最好先从几个类的有限范围开始了解该工具。

当测试生成完成时,测试用例被写入一个名为irs-ctd-amplified-tests的指定目录作为工具的输出,以及用于编译和执行它们的 Maven 和 Ant 脚本。测试用例位于名为monolith的子目录中。为每个应用程序类创建一个单独的测试文件。每个此类文件都包含多种测试方法,用于测试具有不同参数类型组合的类的公共方法,如 CTD 测试计划所指定。将创建一个 CTD 覆盖率报告,该报告总结了可以在名为irs-tkltest-reports的目录中为其生成单元测试的测试计划部分。在上面的输出中,我们可以看到 Tackle-test 为 20 个测试计划行中的 18 个创建了测试用例,从而实现了 90% 的测试计划覆盖率。

放大测试

现在让我们看一下为irs.IRS类生成的测试方法之一。

@测试

公共 void test1() 抛出 Throwable {

irs.IRS iRS0 u003d 新 irs.IRS();

java.util.ArrayList<irs.Salary>salaryList1 u003d new java.util.ArrayList<irs.Salary>();

irs.Salary Salary5 u003d new irs.Salary(0, 0, (double)100);

assertEquals(0, ((irs.Salary)salary5).getEmployerId());

assertEquals(0, ((irs.Salary)salary5).getEmployeeId());

assertEquals(100.0, (double) ((irs.Salary)salary5).getSalary(), 1.0E-4);

boolean boolean6 u003d 工资列表1.add(salary5);

assertEquals(true, boolean6);

iRS0.setSalaryList((java.util.List<irs.Salary>)salaryList1);

}

此测试方法旨在测试 IRS 的setSalaryList方法,该方法接收irs.Salary对象列表作为其输入。我们可以看到测试用例的语句之后调用了assertEquals方法,将生成的对象的值与该测试生成期间记录的值进行比较。当测试再次执行时,例如,在应用程序的现代化版本上,如果任何值与记录的值不同,则会发生断言失败,这可能表明损坏的代码没有保留旧应用程序的功能。

接下来,我们将使用 CLIexecute命令编译并运行生成的测试用例。我们注意到这些是标准的 JUnit 测试用例,可以在 IDE 中运行或使用任何 JUnit 测试运行器;它们也可以集成到 CI 管道中。使用 CLI 执行时,会生成 JUnit 报告以及可选的代码覆盖率报告(使用JaCoCo创建)。

$ tkltest --config-file ./test/data/irs/tkltest_config.toml --verbose 执行

[tkltest|18:12:46.446] 加载配置文件 ./test/data/irs/tkltest_config.toml

[tkltest|18:12:46.457] 总测试类:5

[tkltest|18:12:46.457] 在 ./irs-ctd-amplified-tests 中编译和运行测试

构建文件:./irs-ctd-amplified-tests/build.xml

删除类:

编译类_monolithic:

[javac] 编译5个源文件

执行测试_monolithic:

[mkdir] 创建目录:./irs-tkltest-reports/junit-reports/monolithic

[mkdir] 创建目录:./irs-tkltest-reports/junit-reports/monolithic/raw

[mkdir] 创建目录:./irs-tkltest-reports/junit-reports/monolithic/html

[jacoco:coverage] 使用覆盖增强junit

...

构建成功

总时间:2秒

[tkltest|18:12:49.772] JUnit 报告保存在 ./irs-tkltest-reports/junit-reports

[tkltest|18:12:49.773] Jacoco 代码覆盖率报告保存在 ./irs-tkltest-reports/jacoco-reports

Ant 脚本默认执行单元测试,但用户可以将工具配置为使用 Maven。 Gradle 也将很快得到支持。

查看位于irs-tkltest-reports的 JUnit 报告,我们可以看到所有 JUnit 测试方法都通过了。这是意料之中的,因为我们在生成它们的相同版本的应用程序上执行它们。

junit 报告

从同样位于irs-tkltest-reports的 JaCoCo 代码覆盖率报告中,我们可以看到 CTD 引导的测试生成在 irs 样本上实现了整体 71% 的语句覆盖率和 94% 的分支覆盖率。我们还可以深入到类和方法级别以查看它们的覆盖率。缺少的覆盖率是测试生成器无法为其生成通过序列的测试计划行的结果。增加每个类的测试生成时间限制可以提高覆盖率。

雅可可

CTD引导测试生成

图 2 说明了 CTD 引导测试生成的测试生成流程,在 Tackle-test 的核心测试生成引擎中实现。测试生成流程的输入是(1)应用程序类,(2)应用程序的库依赖项,以及(3)可选的用于测试生成的应用程序类集的规范(如果未指定,所有应用程序类是目标)。此规范通过TOML配置文件提供。流程的输出包括:(1) JUnit 测试用例(带或不带断言),(2) Maven 和 Ant 构建文件,以及 (3) 包含测试生成和 CTD 测试计划覆盖摘要的 JSON 文件。

ctd-guided test generation

图片来源:

图 2:CTD 引导的测试生成过程。

该流程从生成 CTD 测试计划开始。这涉及为目标类的每个公共方法创建一个 CTD 模型。每个方法的 CTD 模型为方法的每个形式参数捕获所有可能的具体类型,包括可以添加到集合/映射/数组参数类型的元素。 Tackle-test 结合轻量级静态分析来推断每种方法的每个参数的可行混凝土类型。

接下来,在给定(用户可配置)交互级别从模型自动生成 CTD 测试计划。测试计划中的每一行都描述了应该调用该方法的具体参数类型的特定组合。默认情况下,交互级别设置为 1,这会导致单向测试:每种可能的具体参数类型至少出现在测试计划的一行中。将交互级别设置为两个,也就是成对测试,将产生一个测试计划,其中至少包括其中一行中每对方法参数的每一对具体类型。

CTD 测试计划提供了一组需要综合测试序列的覆盖目标。 Tackle-test 分两步进行。第一步,它使用 Randoop 和/或 EvoSuite(用户可以配置使用哪些工具)来创建基本测试序列。分析基本测试序列以在方法和类级别生成序列池,测试生成引擎从中采样序列,以将每个测试计划行的覆盖序列放在一起。如果成功创建了覆盖序列,引擎会执行它以确保序列在不会导致应用程序崩溃的意义上是有效的。在此执行期间,还记录所创建对象的运行时状态,以供以后用于断言生成。失败的序列被丢弃。如果用户指定了断言选项,引擎会将断言添加到传递序列。最后,引擎将按类分组的序列导出到 JUnit 类文件。该引擎还创建 Antbuild.xml和 Mavenpom.xml文件,如果需要,可以使用它们来运行生成的测试用例。

其他工具功能

Tackle-test 是高度可配置的,并提供了几个配置选项,用户可以使用这些选项来定制工具的行为:例如,为哪些类生成测试,使用哪些工具生成测试,在测试生成上花费多少时间,是否向测试用例添加断言,使用什么交互级别来生成 CTD 测试计划,为扩展测试序列执行多少次,等等。

不同测试生成策略的有效性

Tackle-test 已经在几个开源 Java 应用程序上进行了评估,目前也被应用于企业级 Java 应用程序。

指令覆盖结果

图片来源:

图 3:从SF110 基准获取的两个小型开源 Java 应用程序使用不同策略和交互级别生成的测试用例实现的指令覆盖率。

图 3 展示了在两个小型开源 Java 应用程序上使用不同测试策略生成的测试所实现的语句覆盖率数据。这些应用程序取自SF110 基准,这是一个大型开源 Java 应用程序语料库,旨在促进自动化测试技术的实证研究。其中一个应用程序jni-inchi由 24 个类和 74 个方法组成;另一个是gaj,由 14 个类和 17 个方法组成。箱线图显示,单独针对 CTD 测试计划行可以实现良好的语句覆盖率,并且与从 Randoop 和 EvoSuite 生成的测试用例中抽取的与 CTD 引导的测试套件相同大小的测试套件相比,CTD-引导式测试套件实现了更高的语句覆盖率,使其更高效。

目前正在使用来自 SF110 基准测试的更多应用程序和一些专有的企业 Java 应用程序对 Tackle-test 进行大规模评估。

如果你喜欢看视频演示,你可以在这里观看。

我们鼓励您试用该工具并提供反馈,以帮助我们通过提交拉取请求来改进它。我们还邀请您通过为项目做出贡献来帮助改进该工具。

与 Konveyor 社区一起迁移到 Kubernetes

Tackle-test 是 Konveyor 社区的一部分。该社区正在帮助其他人通过构建工具、识别模式并提供关于打破单体、采用容器和拥抱 Kubernetes 的建议来实现应用程序的现代化并将其迁移到混合云。

该社区包括将虚拟机迁移到 KubeVirt、Cloud Foundry 或 Docker 容器到 Kubernetes 或 Kubernetes 集群之间的命名空间的开源工具。这些是我们解决的一些用例。

有关这些工具的更新以及从业者展示他们如何迁移到 Kubernetes 的聚会邀请,加入社区。

参考文献

[1] COBOL 到 Java 和报纸仍然得到交付,https://arxiv.org/pdf/1808.03724.pdf,2018。

[2] D. R. Kuhn、R. N. Kacker 和 Y. Lei。组合测试简介。查普曼和霍尔/CRC,2013 年。

Logo

K8S/Kubernetes社区为您提供最前沿的新闻资讯和知识内容

更多推荐