跳到主要内容

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

概括

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

目标

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

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

  • 支持需要VM初始化的测试

  • 支持快速测试,执行时间为毫秒级

  • 支持正向和负向测试

  • 允许测试隔离

  • 支持与产品源代码位于同一位置的测试

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

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

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

  • 能够为测试失败提供最少的重现器

  • 提供IDE支持

  • 允许框架不断发展,包括对框架进行快速修复

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

  • 允许测试任何编译目标、产品以及调试

  • 允许测试依赖于平台的代码

  • 提供最少的文档:操作方法 wiki 和存储库中的示例

  • 允许通过修改测试的源或其他文件(例如排除列表)来从执行中排除测试

  • 支持所有内部测试转换

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

非目标

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

动机

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

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

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

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

描述

Google 测试框架 (GTest) 是最符合我们目标的 C++ 单元测试框架,它是一个 xUnit 测试框架,在社区中具有很大的吸引力。 GTest框架:

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

需要几个任务来允许使用 GTest 为 HotSpot 编写测试,并且需要一些额外的任务来增强它。在GTest的当前状态下:

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

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

HotSpot 测试目录布局

新的测试需要在源代码树中占有一席之地。测试的根目录应放置在靠近产品源本身的位置,而不是在产品源本身中,就像现有的测试目录结构一样。为了清楚起见,测试不应与现有jtreg测试混合;相反,它们应该分为两个目录。我们建议将当前jdk9/hotspot/test目录拆分为两个子目录:

  • 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 (2K LOC) 小得多的框架,并且不是外部依赖项。这是一个可行但更昂贵的解决方案。它还缺乏 IDE 支持。

替代方案 2:继续使用 Java 实现测试。可以使用 Whitebox API 访问 JVM 内部。相比之下,添加Whitebox API 比较麻烦,而且执行速度很慢。它适合一些内省,但远非所有测试。 Java 测试的编写和执行成本更高,因为为了获得针对特定功能的高质量测试,测试变得非常复杂,并且通常很难保证确定性。

替代方案 3:继续使用内部测试。该解决方案无法满足许多既定目标。

风险和假设

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

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

风险:向后移植 GTest 修复将被证明成本非常高。

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