跳到主要内容

JEP 281:HotSpot C++ 单元测试框架

QWen Max 中英对照

概述

启用并鼓励为 HotSpot 开发 C++ 单元测试。

目标

  • 支持编写和执行针对方法、类和子系统的单元测试

  • 支持单元测试,其中仅测试一个单元,不运行其他内容

  • 支持需要虚拟机 (VM) 初始化的测试

  • 支持快速测试,其执行时间在毫秒级别

  • 支持正向和反向测试

  • 允许测试隔离

  • 支持与产品源代码共存的测试

  • 支持与当前基础设施的集成

  • 为每个测试生成单独的结果

  • 能够轻松运行单个测试(从命令行)

  • 能够为测试失败提供最小的复现步骤

  • 提供 IDE 支持

  • 允许框架演进,包括对框架进行快速修复

  • 支持测试选择和测试分组,其粒度类似于 jtreg

  • 允许测试任何编译目标,无论是产品版本还是调试版本

  • 允许测试平台相关代码

  • 提供最少的文档:一份如何操作的 Wiki 和存储库中的示例

  • 允许通过修改测试源代码或其他文件(如排除列表)来排除某些测试的执行

  • 支持所有内部测试的转换

  • 支持 Oracle 支持的 JDK 9 构建平台

非目标

  • 替换 Java 测试。C++ 中的单元测试是对不同用例进行测试的补充。

动机

在一个经过充分测试的代码库中,进行更改会更加容易。测试套件通过验证没有意外的破坏来支持进行更改的工程师。

如今,HotSpot 有许多测试,但直接类型的测试并不多,而且编写这样的测试并不容易。

引入 C++ 的测试框架是迈向更好测试套件的第一步。C++ 的测试框架支持使用与 JVM 相同的语言编写测试,然后内部结构直接暴露给测试代码,相比于使用 jtreg 从 Java 进行功能测试,这提供了另一层次的可能性,可以更轻松地编写小型、精确的测试。

为现有功能开发单元测试的可能性将使得单独测试 C++ 的新特性代码成为可能,并且更容易为一些较为晦涩的问题编写回归测试。

描述

Google Test(GTest)框架是与我们的目标最为契合的 C++ 单元测试框架,它是一个 xUnit 测试框架,在社区中拥有很高的关注度。GTest 框架:

  • 由其他开发者开发并提供支持
  • 提供与 Eclipse IDE 的集成
  • 是经过实战验证的、完整的 API
  • 拥有功能丰富的执行模型
  • 拥有现有的文档和示例
  • 支持 JUnit 风格的测试结果,并与 Hudson 和 Jenkins 集成

要允许使用 GTest 为 HotSpot 编写测试,需要完成若干任务,同时还需要一些额外的任务来对其进行增强。在 GTest 的当前状态下:

  • 使用了 HotSpot 未使用且在其上被禁用的 C++ 构造,例如异常、模板和 STL
  • Solaris/Oracle Solaris Studio 不是受支持的操作系统/编译器

GTest 诚然是一种第三方工具,因此它为现有的构建和测试过程引入了另一个依赖项。GTest 也相当庞大(71K 行代码),并且将来可能会出现不兼容的变更。为了避免测试框架本身的变更导致问题的风险,我们需要控制使用哪个版本的 GTest,并能够将其作为构建的一部分进行指定(尽管应该可以覆盖)。拥有一个依赖管理系统来自动下载并安装正确版本的 GTest 将会很有益处。

HotSpot 测试目录布局

新的测试需要在源代码树中有一个存放的位置。测试的根目录应该靠近产品源代码本身,但不在其中,这与现有的测试目录结构类似。为了清晰起见,这些测试不应与现有的 jtreg 测试混在一起;相反,它们应该被分成两个目录。我们建议将当前的 jdk9/hotspot/test 目录拆分为两个子目录:

  • jdk9/hotspot/test/java
  • jdk9/hotspot/test/native

翻译为中文:

  • jdk9/hotspot/test/java
  • jdk9/hotspot/test/native

(注:这些是路径名称,通常不需要翻译,保留原样即可。)

现有的 jtreg 测试将移至 java 目录下(包括 JNI 代码和 shell 脚本)。TEST.ROOT 文件仍将保留在顶层。

构建目标和二进制文件

产品二进制文件绝不能以任何可见的方式受到测试代码的影响。例如,不应有额外的符号被导出,产品包也不应包含任何测试。编译后的测试将被放入单独的测试包中,每个配置一个测试包。这些测试将链接到从非精简版 JVM 库中导出的符号,该库是与常规库相同的对象文件创建的。

调用测试

使用 make 从命令行运行测试必须简单易行。为了使测试结果与其他测试的结果兼容,调用时可能会使用 jtreg 测试包装器运行,该包装器会依次调用 GTest。GTest 本身可以生成 JUnit 风格的结果,这与 Hudson/Jenkins 以及类似工具能够很好地集成。

替代方案

替代方案 1:HUTT。之前创建了一个名为“热点单元测试工具”(HotSpot Unit Test Tool,HUTT)的原型框架,它是一个 xUnit 框架。该框架比 GTest(2000 行代码)要小得多,并且不是外部依赖项。这是一个可行但成本更高的解决方案。此外,它缺乏 IDE 支持。

替代方案 2:继续使用 Java 实现测试。可以使用 Whitebox API 访问 JVM 内部结构。但与添加 Whitebox API 相比,操作起来更加繁琐,执行速度也较慢。它适用于某些内省操作,但远非适用于所有测试。Java 测试编写和执行的成本更高,因为为了获得针对特定功能的高质量测试,测试变得非常复杂,并且往往难以保证确定性。

第三种选择:继续使用内部测试。这个解决方案无法实现许多既定目标。

风险与假设

风险:GTest 可能会朝着使其不适合作为 HotSpot 单元测试框架的方向发展。该风险估计较低。

缓解计划:分叉 GTest 框架,或者使用 HUTT。

风险:反向移植 GTest 修复将证明是非常昂贵的。

缓解计划:分叉 GTest 框架,或者使用 HUTT。