"DataPoint field samples must be public" 在 Kotlin 中使用 JUnit 4 Theories 时出错

"DataPoint field samples must be public" error when using JUnit 4 Theories in Kotlin

概览

在 Kotlin class 中将 @DataPoint@DataPoints 应用于 属性 会导致 "DataPoint field samples must be public" 错误。

描述

JUnit4 中的理论使用 @DataPoint@DataPoints 注释来标记示例数据,这些数据被收集并传递给可以将它们作为参数的单独测试。当这些注解应用于 Kotlin 属性(也必须注解 @JvmStatic 并在伴随对象中声明)时,测试用例会失败并显示错误 "DataPoint field samples must be public"。默认情况下,Kotlin 属性应该是 public。此外,显式添加 "public" 修饰符没有任何影响。

示例代码

import org.junit.runner.RunWith
import org.junit.experimental.theories.Theories
import org.junit.experimental.theories.DataPoints
import org.junit.experimental.theories.Theory
import org.junit.Assume.*

import org.hamcrest.MatcherAssert.assertThat
import org.hamcrest.Matchers.*

@RunWith(Theories::class)
class SampleTheories {
    companion object {
        @JvmStatic
        @DataPoints
        public val samples = listOf(
            -8, -1, 0, 1, 2, 4, 8
        )
    }

    @Theory
    fun triangleInequality(a:Int, b:Int) {
        assumeThat(a, `is`(greaterThan(0)))
        assumeThat(b, `is`(greaterThan(0)))

        assertThat(a+b, `is`(greaterThan(a)))
        assertThat(a+b, `is`(greaterThan(b)))
    }
}

Gradle 依赖关系

dependencies {
    implementation fileTree(include: ['*.jar'], dir: 'libs')
    implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:1.2.51"
    // ...
    testImplementation 'junit:junit:4.12'
    testImplementation 'org.hamcrest:hamcrest-library:1.3'
    // ...
}

其他系统信息

这可能是因为 Kotlin 不知道注释应该放在字节码中的什么位置。这意味着实际代码将类似于:

private static List<Integer> samples = ....

@DataPoints
public static List<Integer> getSamples() {
    return this.samples;
}

@JvmStatic 替换为 @JvmField 应该会生成正确的代码。如果您使用的是 IntelliJ,您始终可以使用 Inspect Kotlin Bytecode 命令来检查正在生成的代码。

在幕后,Kotlin 中的 public 属性有一个私有支持字段和 public 访问器。在 Java 端(这是 JUnit4 运行的地方),Kotlin 属性本身并不存在。相反,必须显式使用字段和访问器方法。

出现错误是因为注释应用于(私有)字段,而不是 (public) 访问器。

有几个决议:

  1. @DataPoints 注释应用于 属性 的 getter:

    @JvmStatic
    val samples = listOf(
        -8, -1, 0, 1, 2, 4, 8
    ) @DataPoints get
    

    这只会计算一次表达式。

  2. 将数据点声明为方法,而不是 属性:

    @JvmStatic
    @DataPoints
    fun samples = listOf(
        -8, -1, 0, 1, 2, 4, 8
    )
    

    由于数据点源是一个函数,每次检索它们时(可能不是很多次;我不熟悉 Theories 的内部结构),都会重新计算表达式。