【问题标题】:TestNG tests execution against JDK 9 module causes InaccessibleObjectExceptionTestNG 测试针对 JDK 9 模块的执行导致 InaccessibleObjectException
【发布时间】:2018-01-22 05:31:24
【问题描述】:

我正在尝试将以下库转换为 Java 9 模块:https://github.com/sskorol/test-data-supplier

遵循本指南:https://guides.gradle.org/building-java-9-modules

经过一些操作和重构(无法管理 lombok 问题,所以暂时将其删除),我有以下 module-info.java

module io.github.sskorol {
    exports io.github.sskorol.core;
    exports io.github.sskorol.model;

    requires testng;
    requires vavr;
    requires streamex;
    requires joor;
    requires aspectjrt;
}

它甚至在测试跳过的情况下编译/构建。 但是,当我尝试运行 test 任务时,出现以下异常:

org.gradle.api.internal.tasks.testing.TestSuiteExecutionException: Could not complete execution for Gradle Test Executor 2.
    at org.gradle.api.internal.tasks.testing.SuiteTestClassProcessor.stop(SuiteTestClassProcessor.java:63)
    at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
    at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
    at java.base/jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
    at java.base/java.lang.reflect.Method.invoke(Method.java:564)
    at org.gradle.internal.dispatch.ReflectionDispatch.dispatch(ReflectionDispatch.java:35)
    at org.gradle.internal.dispatch.ReflectionDispatch.dispatch(ReflectionDispatch.java:24)
    at org.gradle.internal.dispatch.ContextClassLoaderDispatch.dispatch(ContextClassLoaderDispatch.java:32)
    at org.gradle.internal.dispatch.ProxyDispatchAdapter$DispatchingInvocationHandler.invoke(ProxyDispatchAdapter.java:93)
    at com.sun.proxy.$Proxy1.stop(Unknown Source)
    at org.gradle.api.internal.tasks.testing.worker.TestWorker.stop(TestWorker.java:120)
    at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
    at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
    at java.base/jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
    at java.base/java.lang.reflect.Method.invoke(Method.java:564)
    at org.gradle.internal.dispatch.ReflectionDispatch.dispatch(ReflectionDispatch.java:35)
    at org.gradle.internal.dispatch.ReflectionDispatch.dispatch(ReflectionDispatch.java:24)
    at org.gradle.internal.remote.internal.hub.MessageHubBackedObjectConnection$DispatchWrapper.dispatch(MessageHubBackedObjectConnection.java:146)
    at org.gradle.internal.remote.internal.hub.MessageHubBackedObjectConnection$DispatchWrapper.dispatch(MessageHubBackedObjectConnection.java:128)
    at org.gradle.internal.remote.internal.hub.MessageHub$Handler.run(MessageHub.java:404)
    at org.gradle.internal.concurrent.ExecutorPolicy$CatchAndRecordFailures.onExecute(ExecutorPolicy.java:63)
    at org.gradle.internal.concurrent.ManagedExecutorImpl$1.run(ManagedExecutorImpl.java:46)
    at java.base/java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1167)
    at java.base/java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:641)
    at org.gradle.internal.concurrent.ThreadFactoryImpl$ManagedThreadRunnable.run(ThreadFactoryImpl.java:55)
    at java.base/java.lang.Thread.run(Thread.java:844)
Caused by: org.testng.TestNGException: 
Cannot instantiate class io.github.sskorol.testcases.DataSupplierTests
    at testng@6.11/org.testng.internal.ObjectFactoryImpl.newInstance(ObjectFactoryImpl.java:31)
    at testng@6.11/org.testng.internal.ClassHelper.createInstance1(ClassHelper.java:410)
    at testng@6.11/org.testng.internal.ClassHelper.createInstance(ClassHelper.java:323)
    at testng@6.11/org.testng.internal.ClassImpl.getDefaultInstance(ClassImpl.java:126)
    at testng@6.11/org.testng.internal.ClassImpl.getInstances(ClassImpl.java:191)
    at testng@6.11/org.testng.TestClass.getInstances(TestClass.java:99)
    at testng@6.11/org.testng.TestClass.initTestClassesAndInstances(TestClass.java:85)
    at testng@6.11/org.testng.TestClass.init(TestClass.java:77)
    at testng@6.11/org.testng.TestClass.<init>(TestClass.java:42)
    at testng@6.11/org.testng.TestRunner.initMethods(TestRunner.java:423)
    at testng@6.11/org.testng.TestRunner.init(TestRunner.java:250)
    at testng@6.11/org.testng.TestRunner.init(TestRunner.java:220)
    at testng@6.11/org.testng.TestRunner.<init>(TestRunner.java:161)
    at testng@6.11/org.testng.SuiteRunner$DefaultTestRunnerFactory.newTestRunner(SuiteRunner.java:578)
    at testng@6.11/org.testng.SuiteRunner.init(SuiteRunner.java:185)
    at testng@6.11/org.testng.SuiteRunner.<init>(SuiteRunner.java:131)
    at testng@6.11/org.testng.TestNG.createSuiteRunner(TestNG.java:1383)
    at testng@6.11/org.testng.TestNG.createSuiteRunners(TestNG.java:1363)
    at testng@6.11/org.testng.TestNG.runSuitesLocally(TestNG.java:1217)
    at testng@6.11/org.testng.TestNG.runSuites(TestNG.java:1144)
    at testng@6.11/org.testng.TestNG.run(TestNG.java:1115)
    at org.gradle.api.internal.tasks.testing.testng.TestNGTestClassProcessor.runTests(TestNGTestClassProcessor.java:129)
    at org.gradle.api.internal.tasks.testing.testng.TestNGTestClassProcessor.stop(TestNGTestClassProcessor.java:88)
    at org.gradle.api.internal.tasks.testing.SuiteTestClassProcessor.stop(SuiteTestClassProcessor.java:61)
    ... 25 more
Caused by: java.lang.reflect.InaccessibleObjectException: Unable to make public io.github.sskorol.testcases.DataSupplierTests() accessible: module io.github.sskorol does not "exports io.github.sskorol.testcases" to module testng
    at java.base/java.lang.reflect.AccessibleObject.checkCanSetAccessible(AccessibleObject.java:337)
    at java.base/java.lang.reflect.AccessibleObject.checkCanSetAccessible(AccessibleObject.java:281)
    at java.base/java.lang.reflect.Constructor.checkCanSetAccessible(Constructor.java:192)
    at java.base/java.lang.reflect.Constructor.setAccessible(Constructor.java:185)
    at testng@6.11/org.testng.internal.ObjectFactoryImpl.newInstance(ObjectFactoryImpl.java:22)
    ... 48 more

这对我来说似乎有点混乱,因为 io.github.sskorol.testcasessrc/test/java 的一部分并且没有 module-信息 用于测试。所以我不能把这个包导出到TestNG。假设 ObjectFactoryImpl 中针对测试类的 TestNG 反射使用的根本原因。

有人知道如何解决吗?

环境:JDK 9(构建 9+181)、Gradle 4.1、TestNG 6.11

【问题讨论】:

    标签: java gradle testng java-9


    【解决方案1】:

    假设在 ObjectFactoryImpl 中针对测试类使用 TestNG 反射的根本原因。

    是的,这是两个原因之一。另一个是,显然,Gradle 将您的测试作为一个模块运行。正如您所指出的,您的测试没有模块描述符。 Gradle 可以使用--patch-module 将测试添加到包含生产代码的模块中。

    This question and answer 提供了大量背景信息和可能的修复。作为短期修复,我建议将opens io.github.sskorol.testcases 添加到您的生产代码的模块描述符中。从它的名字来看,我猜还没有这样的包,所以你要么必须重命名,要么添加一个虚拟类(我更喜欢前者)。

    我也会将此问题提交给 Gradle 邮件列表或错误跟踪器。除非我们忽略了某些事情(完全有可能),否则 Gradle 的行为是非常不幸的,因为它需要使生产代码的模块描述符适应测试代码的需求。

    【讨论】:

    • 谢谢,将检查这些选项。
    • Nicolai,我尝试添加 opens,它成功了。然而,所有的测试都失败了。最流行的异常与我的主代码中的反射使用有关。例如。 joor 无法访问 src/test/java 中的类,并要求导出相应的包,类似于 TestNG 的初始问题。所以我想这没有简单的解决方法,最好在 Gradle 跟踪器中提出问题。
    • @Nikolai,除了使用--patch-module,Gradle 还能做什么?这似乎是让测试访问生产代码的唯一方法。
    • 有人争辩说,单元测试不应该作为模块运行,而是使用类路径。 (我不太确定,但它有一些优势。)
    【解决方案2】:

    如果测试与被测模块在同一个包中,那么它们需要被编译(使用--patch-module),以便它们被编译为“好像”它们是模块的一部分。测试中对 TestNG 类型的引用意味着也需要 --add-reads io.github.sskorol=testng

    跑步是类似的。测试需要“好像”在模块io.github.sskorol 中一样运行。这意味着运行:

    --patch-module io.github.sskorol=<testclasses> \
    --add-reads io.github.sskorol=testng
    

    此外,您可能需要将带有测试的包导出或打开到 TestNG。这将归结为测试类和方法是否在导出的包中是公共的。为了避免扫描,最简单的方法是将所有包含测试的包打开到TestNG,例如

    --add-opens io.github.sskorol/io.github.sskorol.core=testng \
    --add-opens io.github.sskorol/io.github.sskorol.core.internal=testng
    

    (.internal只是模块中未导出包的填充)

    这一切可能看起来很复杂,但这是 Maven Surefire 插件和 Gradle 应该做的事情。

    【讨论】:

    • 我对@9​​87654327@ 感到惊讶。 TestNg 使用反射来查看 io.github.sskorol - 这不应该建立可读性吗?
    • 测试将使用 TestNG API(例如 org.testng.Assert.assertTrue),因此模块确实需要读取 testng。
    • 啊,我读错了条款。是的,这是有道理的。不仅如此,每个测试依赖项(模拟、断言、...库)都必须以这种方式添加。
    • @AlanBateman 谢谢,我刚刚尝试了这些技巧,现在它似乎正在工作,除了 1 部分,与 SPI 相关。与在 Java 9 中一样,我们不再需要 META-INF/services,而应该在 module-info.java 中使用 provides ... with ...,我想知道是否可以通过 jvmargs 指定此信息?由于测试没有module-info,但我仍然必须提供实现类,它位于测试包的 1 中。有什么标志吗?
    【解决方案3】:

    做了一些@AlanBateman 建议的技巧,他给了我一个有效的方向。

    最后,我想出了以下配置:

    模块信息.java

    module io.github.sskorol {
        exports io.github.sskorol.core;
        exports io.github.sskorol.model;
    
        opens io.github.sskorol.utils to joor;
    
        requires testng;
        requires vavr;
        requires streamex;
        requires joor;
    }
    

    build.gradle

    test {
        inputs.property("moduleName", moduleName)
        doFirst {
            jvmArgs = [
                    '--module-path', classpath.asPath,
                    '--add-modules', 'ALL-MODULE-PATH',
                    '--add-opens', 'io.github.sskorol/io.github.sskorol.testcases=testng',
                    '--add-opens', 'io.github.sskorol/io.github.sskorol.testcases=joor',
                    '--add-opens', 'io.github.sskorol/io.github.sskorol.datasuppliers=joor',
                    '--add-opens', 'io.github.sskorol/io.github.sskorol.datasuppliers=testng',
                    '--add-opens', 'java.base/java.util=streamex',
                    '--add-opens', 'java.base/java.util.stream=streamex',
                    '--patch-module', "$moduleName=" + files(sourceSets.test.java.outputDir).asPath
            ]
            classpath = files()
        }
    }
    

    testngjoor 都需要访问我的测试包。所以--add-opens 标志成功了。 streamex 模块也担心访问 java.base 包。

    请注意,与原始 Java 8 代码相比,我必须删除 lombokaspectj 依赖项,因为我无法完全解决迁移后出现的所有问题.

    我遇到的另一个问题与 SPI 测试有关。根据我读过的文档,在 Java 9 中,SPI 实现应该在module-info.java 中列出,而不是在META-INF/services 中。但这似乎不是一个解决方案,当实现类位于test 包之一时,test 又不是一个模块。只是想知道是否有一些 jvmflag 用于替换 provides ... with ... 语法,并执行与 --add-opens 相同的技巧。任何想法将不胜感激。

    完整的实现,修改为支持 Java 9,可以在 here 找到。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2020-06-04
      • 2011-02-09
      • 1970-01-01
      • 2019-02-01
      • 2018-05-28
      • 1970-01-01
      • 1970-01-01
      • 2018-11-15
      相关资源
      最近更新 更多