【问题标题】:Setting up multi-release JAR unit tests设置多版本 JAR 单元测试
【发布时间】:2021-12-31 10:29:43
【问题描述】:

我有一个使用大量反射的项目,还涉及“新”Java 功能,例如记录和密封类。我正在写这样的课程:

public class RecordHelper {
    public static boolean isRecord(Class<?> type) {
        return type.isRecord();
    }
}

当然,这只适用于 Java 16 及更高版本,所以我正在尝试设置一个多版本 JAR 文件,默认实现如下:

public class RecordHelper {
    public static boolean isRecord(Class<?> type) {
        return false;
    }
}

我已经能够使用 Maven 使用 this post on Baeldung 进行设置,而且效果很好。这很好,因为它不依赖于不同版本的单独配置文件,这使我的 pom 文件保持干净。

但现在我需要编写测试。

我希望能够在我想要支持的所有平台上运行测试套件,这意味着从 JDK 8 及更高版本开始的所有平台。 (我不介意在运行测试之前在不同的 JDK 上编译。)当然,在 JDK 16 及更高版本上,我还想测试与记录相关的东西(在 17 及更高版本上,密封类),这意味着我必须编译一些记录,这意味着我不可避免地会有一些在旧版 JDK 上无法运行的类文件。

在我看来,为测试提供类似多版本 JAR 文件之类的东西是有意义的,其中记录测试放置在 META-INF/versions 中的适当位置,但当然测试通常不打包在 JAR 中,所以这不起作用。

有没有办法让这个在单模块 Maven 项目中工作而不需要太多重复?

当然,随着 JDK 版本的上升,它必须“积累”测试类,例如在 JDK 8 上我只有“常规”测试类,在 JDK 16 上我有常规的和 java16 的,在 JDK 17 上我有常规的 java16java17 的。我还没有找到一种方法在 Maven 中以简洁的方式表达这种事情,但我不是 Maven 专家。

还是我看错了方向,是否最好制作一个多模块 Maven 项目,将主要代码放在一个模块中,将测试放在另一个模块中,然后为测试生成一个多模块 jar也?如果是这样,我将如何在不同的 JDK 上运行这个 jar 文件?

【问题讨论】:

  • 一个选项可能是有条件地运行 JUnit 测试?也参考 Baeldung :) junit-5-conditional-test-execution , junit-conditional-assume
  • @DirkDeyne 我考虑过,但是我为不同版本编译了类文件,所以如果我在 JDK 8 上运行测试,带有记录的类文件将导致 JVM 失败。 .
  • 您可以检查 github.com/khmarbaise/mrelease 也可能是选项...使用单个 JDK 构建(JDK 17)产生多版本 jar...可以在适当的模块中进行测试(相关到 JDK)。这可以使用工具链来增强,以真正使用适当的 JDK 而不是单个 JDK...不,它不是单个模块解决方案,因为我认为应该将这些东西分开。跨度>

标签: java maven


【解决方案1】:

要测试 MRJAR,必须将类打包为 jar,因此不要对 target/classes 使用surefire,而是在verify 阶段使用故障安全。 您必须至少运行两次,每个目标 Java 版本一次。 我会编写一个单元测试,它适用于所有 Java 版本,但可能会跳过某些测试。

import static org.junit.jupiter.api.Assertions.assertFalse;
import static org.junit.jupiter.api.Assertions.assertTrue;
import static org.junit.jupiter.api.Assumptions.assumeTrue;

import org.junit.jupiter.api.Test;

class RecordHelperTest
{

    @Test
    void isNotARecord()
    {
        assertFalse( RecordHelper.isRecord(Object.class));
    }

    @Test
    void isARecord() throws Exception
    {
        assumeTrue( Integer.parseInt( System.getProperty( "java.specification.version" ) ) >= 16 );
        
        Class c = Class.forName( "jdk.net.UnixDomainPrincipal" );
        assertTrue( RecordHelper.isRecord(c));
    }

}

如何运行它两次,这取决于你,例如使用不同的工具链配置故障安全插件两次,或者依赖 CI 服务器,使用两个 JDK 构建项目。

https://maven.apache.org/plugins/maven-compiler-plugin/multirelease.html 描述了几个选项及其优缺点,因为没有万能的解决方案。

【讨论】:

  • @EnabledOnJre@EnabledForJreRange 可能是更方便的解决方案,而不是通过系统属性使用假设。
【解决方案2】:

正如@khmarbaise 在问题下方的一个 cmets 中所建议的那样,我最终创建了一个多模块构建。他还提供an example

对于名为corem16m17 等的模块,我可以将大部分测试放在core 模块中,并将JDK 特定的测试放在相应的模块中。我什至可以测试特定于 JDK 的代码与 core 模块的其余部分的集成,因为显然在这个设置中,Maven 选择在 m17 中的类的实现而不是在 core 中的同一类的实现,从而模拟真实的多版本 jar 文件中发生的情况。我不能 100% 确定这是否是巧合编程,但它确实有效?

多模块方法的另一个优点是,如果我在特定于 JDK 的子模块中使用新的语言特性,我的 IDE 就能理解它。使用Baeldung article I cited in the question 中的方法,这行不通,我看到到处都是曲线。

为了在不同的 JDK 上测试构建,我使用在 JDK 版本上激活的配置文件,并选择适当的子模块,如下所示:

        <profile>
            <id>modules-jdk8</id>
            <activation>
                <jdk>[1.8,11)</jdk>
            </activation>
            <modules>
                <module>core</module>
            </modules>
        </profile>

        <profile>
            <id>modules-jdk16</id>
            <activation>
                <jdk>[16,17)</jdk>
            </activation>
            <modules>
                <module>core</module>
                <module>m16</module>
            </modules>
        </profile>

        <profile>
            <id>modules-jdk17</id>
            <activation>
                <jdk>[17,)</jdk>
            </activation>
            <modules>
                <module>core</module>
                <module>m16</module>
                <module>m17</module>
                <module>release</module>
            </modules>
        </profile>

(也许使用工具链的方法会更好。)

注意,还必须有一个release 模块,它依赖于所有其他模块,它使用 Maven Assembly 插件来实际创建 jar 文件,并将 JDK 特定的类放在适当的目录中罐子。

我已经为除release 模块之外的所有模块添加了&lt;maven.install&gt;true&lt;/maven.install&gt; 属性,因此只有实际的多版本jar 文件将安装到我的本地存储库中。这一点很重要,因为我也想发布到 Maven Central,我不想乱扔各种半成品 jar 文件。

【讨论】:

    【解决方案3】:

    TL;DR

    一个像@jqno 和@khmarbaise 这样的Maven 多模块解决方案应该比这个解决方案更受欢迎。 Maven Invoker Plugin 应仅在不能选择多模块设置时使用。

    详细解答

    Maven Invoker Plugin 可用于此目的。

    遵循插件建议的标准目录布局并假设版本 8、11 和 16 作为 JDK 目标,JDK 特定测试应在单独的目录中构建:

    • src/it/jdk-8 用于针对 JDK 8 的测试。
    • src/it/jdk-11 用于针对 JDK 11 的测试。
    • src/it/jdk-16 用于针对 JDK 16 的测试。

    每个组都应该有自己的 POM,其中 Java 版本在 maven-compiler-plugin 配置中指定,例如使用 maven.compiler.release 属性。

    每个测试组都可以在多个 Java 版本上运行,而无需复制测试类。例如,要在 Java 11 和 16 执行上运行 JDK 11 测试:

    • 应该使用maven-jar-plugin 在与JDK 11 相关的POM 中创建test JAR
    • 测试 JAR 应该安装在调用者使用的本地 Maven 存储库中(可以为测试特定组设置 invoker.goals = install
    • 应使用dependenciesToScan 参数将测试 JAR 声明为依赖项并添加到 JDK 16 相关 POM 的 Maven Surefire 插件配置中。

    优点

    • 单模块项目无需切换到仅用于测试执行的多模块设置。
    • 所有复杂性都锁定在src/it

    缺点

    • src/it 下的文件对 IDE 不友好,例如,src/it/jdk-*/pom.xml 文件未被检测为 Maven 项目,src/it/jdk-*/src/test 子目录未被检测为测试源根目录。

    https://github.com/scordio/invoker-plugin-example 提供完整示例。

    【讨论】:

      猜你喜欢
      • 2016-03-29
      • 2018-12-01
      • 1970-01-01
      • 1970-01-01
      • 2016-09-22
      • 2017-08-31
      • 2016-08-19
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多