JUnit 5 参数化测试 - 使用 Kotlin 在 @MethodSource 中“无法调用非静态方法”

Ada*_*itz 6 junit android kotlin android-testing junit5

预计

将标准 JUnit 5 测试转换为参数化测试,以便使用Kotlin 中的@ParamterizedTest@MethodSource注释迭代测试用例流。

观察

@MethodSource无法访问数据流。这似乎是此注释的一个问题,因为@ValueSource(strings = ["SF", "NYC"])按预期迭代静态定义的选项。

错误:

PreconditionViolationException:无法在空目标上调用非静态方法 {someMethodName}。

实施

参数化测试设置为传入数据类流,类似于 Phillip Hauer 在参数化测试的数据类中概述的设置。

代码

build.gradle (:SomeProject)

dependencies {
    ...    
    classpath("de.mannodermaus.gradle.plugins:android-junit5:$junit5_version")
}
Run Code Online (Sandbox Code Playgroud)

build.gradle (:someModule)

apply plugin: "de.mannodermaus.android-junit5"
android {
    ...
    compileOptions.targetCompatibility = JavaVersion.VERSION_1_8
    kotlinOptions.jvmTarget = "1.8"
}
dependencies {
    testImplementation "org.junit.jupiter:junit-jupiter-api:5.6.2"
    testRuntimeOnly "org.junit.jupiter:junit-jupiter-engine:5.6.2"
    testImplementation "org.junit.jupiter:junit-jupiter-params:5.6.2"
}
Run Code Online (Sandbox Code Playgroud)

SomeTest.kt

class SomeTest {
    private val testDispatcher = TestCoroutineDispatcher()

    private fun someDataStates() = Stream.of(
        // Kotlin data class
        TestState("123"),
        TestState("345")
    )

    @ParameterizedTest
    @MethodSource("someDataStates")
    fun someTest(testState: TestState) = testDispatcher.runBlockingTest {
        // Test state here.
        ...
    }
}
Run Code Online (Sandbox Code Playgroud)

搭建环境

  • 安卓工作室 4.0
  • Build #AI-193.6911.18.40.6514223,建于 2020 年 5 月 20 日
  • 运行时版本:1.8.0_242-release-1644-b3-6222593 x86_64
  • VM:JetBrains sro 的 OpenJDK 64 位服务器 VM
  • macOS 10.15.5
  • GC:ParNew、ConcurrentMarkSweep
  • 内存:1979M
  • 核心数:16
  • 注册表:ide.new.welcome.screen.force=true
  • 非捆绑插件:cn.wjdghd.unique.plugin.id、com.android.tool.sizereduction.plugin、com.developerphil.adbidea、com.thoughtworks.gauge、mobi.hsz.idea.gitignore

尝试的解决方案

1. 将测试用例数据状态重构为顶级函数。

测试用例.kt

dependencies {
    ...    
    classpath("de.mannodermaus.gradle.plugins:android-junit5:$junit5_version")
}
Run Code Online (Sandbox Code Playgroud)

SomeTest.kt

apply plugin: "de.mannodermaus.android-junit5"
android {
    ...
    compileOptions.targetCompatibility = JavaVersion.VERSION_1_8
    kotlinOptions.jvmTarget = "1.8"
}
dependencies {
    testImplementation "org.junit.jupiter:junit-jupiter-api:5.6.2"
    testRuntimeOnly "org.junit.jupiter:junit-jupiter-engine:5.6.2"
    testImplementation "org.junit.jupiter:junit-jupiter-params:5.6.2"
}
Run Code Online (Sandbox Code Playgroud)

2. 将测试用例数据状态重构为 typeList而不是 的顶级函数Stream

class SomeTest {
    private val testDispatcher = TestCoroutineDispatcher()

    private fun someDataStates() = Stream.of(
        // Kotlin data class
        TestState("123"),
        TestState("345")
    )

    @ParameterizedTest
    @MethodSource("someDataStates")
    fun someTest(testState: TestState) = testDispatcher.runBlockingTest {
        // Test state here.
        ...
    }
}
Run Code Online (Sandbox Code Playgroud)

完整的错误日志

org.junit.platform.commons.PreconditionViolationException:无法在空目标上调用非静态方法 [private final {someMethodName}。

在 org.junit.platform.commons.util.Preconditions.condition(Preconditions.java:296) 在 org.junit.platform.commons.util.ReflectionUtils.invokeMethod(ReflectionUtils.java:682) 在 org.junit.jupiter.params .provider.MethodArgumentsProvider.lambda$provideArguments$1(MethodArgumentsProvider.java:46) 在 java.util.stream.ReferencePipeline$3$1.accept(ReferencePipeline.java:193) 在 java.util.stream.ReferencePipeline$3$1.accept(ReferencePipeline. java:193) 在 java.util.Spliterators$ArraySpliterator.forEachRemaining(Spliterators.java:948) 在 java.util.stream.AbstractPipeline.copyInto(AbstractPipeline.java:482) 在 java.util.stream.AbstractPipeline.wrapAndCopyInto(AbstractPipeline) .java:472) 在 java.util.stream.ForEachOps$ForEachOp.evaluateSequential(ForEachOps.java:150) 在 java.util.stream。ForEachOps$ForEachOp$OfRef.evaluateSequential(ForEachOps.java:173) 在 java.util.stream.AbstractPipeline.evaluate(AbstractPipeline.java:234) 在 java.util.stream.ReferencePipeline.forEach(ReferencePipeline.java:485) 在 java .util.stream.ReferencePipeline$7$1.accept(ReferencePipeline.java:272) 在 java.util.stream.ReferencePipeline$3$1.accept(ReferencePipeline.java:193) 在 java.util.stream.ReferencePipeline$3$1.accept(ReferencePipeline) .java:193) 在 java.util.stream.ReferencePipeline$3$1.accept(ReferencePipeline.java:193) 在 java.util.ArrayList$ArrayListSpliterator.forEachRemaining(ArrayList.java:1382) 在 java.util.stream.AbstractPipeline。 copyInto(AbstractPipeline.java:482) 在 java.util.stream.AbstractPipeline.wrapAndCopyInto(AbstractPipeline.java:472) 在 java.util.stream.ForEachOps$ForEachOp。在 java.util.stream.ForEachOps$ForEachOp$OfRef.evaluateSequential(ForEachOps.java:173) 在 java.util.stream.AbstractPipeline.evaluate(AbstractPipeline.java:234) 在 java.util .stream.ReferencePipeline.forEach(ReferencePipeline.java:485) at java.util.stream.ReferencePipeline$7$1.accept(ReferencePipeline.java:272) at java.util.ArrayList$ArrayListSpliterator.forEachRemaining(ArrayList.java:1382) at java.util.stream.AbstractPipeline.copyInto(AbstractPipeline.java:482) at java.util.stream.AbstractPipeline.wrapAndCopyInto(AbstractPipeline.java:472) at java.util.stream.ForEachOps$ForEachOp.evaluateSequential(ForEachOps.java: 150) 在 java.util.stream.ForEachOps$ForEachOp$OfRef.evaluateSequential(ForEachOps.java:173) 在 java.util.stream.AbstractPipeline。在 org.junit.jupiter.engine.descriptor.TestTemplateTestDescriptor.execute(TestTemplateTestDescriptor.java:106) 在 org.junit 的 java.util.stream.ReferencePipeline.forEach(ReferencePipeline.java:485) 在评估(AbstractPipeline.java:234)在 org.junit .jupiter.engine.descriptor.TestTemplateTestDescriptor.execute(TestTemplateTestDescriptor.java:41) 在 org.junit.platform.engine.support.hierarchical.NodeTestTask.lambda$executeRecursively$5(NodeTestTask.java:135) 在 org.junit.platform。 engine.support.hierarchical.ThrowableCollector.execute(ThrowableCollector.java:73) 在 org.junit.platform.engine.support.hierarchical.NodeTestTask.lambda$executeRecursively$7(NodeTestTask.java:125) 在 org.junit.platform.engine .support.hierarchical.Node.around(Node.java:135) 在 org.junit.platform.engine.support.hierarchical.NodeTestTask。lambda$executeRecursively$8(NodeTestTask.java:123) 在 org.junit.platform.engine.support.hierarchical.ThrowableCollector.execute(ThrowableCollector.java:73) 在 org.junit.platform.engine.support.hierarchical.NodeTestTask.executeRecursively (NodeTestTask.java:122) at org.junit.platform.engine.support.hierarchical.NodeTestTask.execute(NodeTestTask.java:80) at java.util.ArrayList.forEach(ArrayList.java:1257) at org.junit。 platform.engine.support.hierarchical.SameThreadHierarchicalTestExecutorService.invokeAll(SameThreadHierarchicalTestExecutorService.java:38) at org.junit.platform.engine.support.hierarchical.NodeTestTask.lambda$executeRecursively$5(NodeTestTask.java:139) at org.junit. .engine.support.hierarchical.ThrowableCollector.execute(ThrowableCollector.java:73) 在 org.junit.platform.engine。support.hierarchical.NodeTestTask.lambda$executeRecursively$7(NodeTestTask.java:125) at org.junit.platform.engine.support.hierarchical.Node.around(Node.java:135) at org.junit.platform.engine.support .hierarchical.NodeTestTask.lambda$executeRecursively$8(NodeTestTask.java:123) 在 org.junit.platform.engine.support.hierarchical.ThrowableCollector.execute(ThrowableCollector.java:73) 在 org.junit.platform.engine.support。在 org.junit.platform.engine.support.hierarchical.NodeTestTask.execute(NodeTestTask.java:80) 在 java.util.ArrayList.forEach(ArrayList.java:1257) 上的hierarchy.NodeTestTask.executeRecursively(NodeTestTask.java:122)在 org.junit.platform.engine.support.hierarchical.SameThreadHierarchicalTestExecutorService.invokeAll(SameThreadHierarchicalTestExecutorService.java:38) 在 org.junit.platform。engine.support.hierarchical.NodeTestTask.lambda$executeRecursively$5(NodeTestTask.java:139) 在 org.junit.platform.engine.support.hierarchical.ThrowableCollector.execute(ThrowableCollector.java:73) 在 org.junit.platform.engine .support.hierarchical.NodeTestTask.lambda$executeRecursively$7(NodeTestTask.java:125) 在 org.junit.platform.engine.support.hierarchical.Node.around(Node.java:135) 在 org.junit.platform.engine。 support.hierarchical.NodeTestTask.lambda$executeRecursively$8(NodeTestTask.java:123) at org.junit.platform.engine.support.hierarchical.ThrowableCollector.execute(ThrowableCollector.java:73) at org.junit.platform.engine.support .hierarchical.NodeTestTask.executeRecursively(NodeTestTask.java:122) 在 org.junit.platform.engine.support.hierarchical.NodeTestTask.execute(NodeTestTask.java:80) 在 org.junit.platform.engine.support.hierarchical.SameThreadHierarchicalTestExecutorService.submit(SameThreadHierarchicalTestExecutorService.java:32) at org.junit.platform.engine.support.hierarchical.HierarchicalTestExecutor.execute(HierarchicalTestExecutor.java:57) at org.platform.org. engine.support.hierarchical.HierarchicalTestEngine.execute(HierarchicalTestEngine.java:51) at org.junit.platform.launcher.core.DefaultLauncher.execute(DefaultLauncher.java:248) at org.junit.platform.launcher.core.DefaultLauncher。 org.junit.platform.launcher.core.DefaultLauncher.withInterceptedStreams(DefaultLauncher.java:226) 上的 lambda$execute$5(DefaultLauncher.java:211) org.junit.platform.launcher.core.DefaultLauncher.execute(DefaultLauncher.java) :199) 在 org.junit.platform.launcher.core.DefaultLauncher.execute(DefaultLauncher.java:132) 在 com.intellij.junit5.JUnit5IdeaTestRunner.startRunnerWithArgs(JUnit5IdeaTestRunner.java:69) 在 com.intellij.rt.junit.IdeaTestRunner$Repeater.startRunnerWithArgs(IdeaTestRunner.java:33) 在 com.intellij.rt.junit .JUnitStarter.prepareStreamsAndStart(JUnitStarter.java:230) at com.intellij.rt.junit.JUnitStarter.main(JUnitStarter.java:58) 被抑制:org.junit.platform.commons.PreconditionViolationException:配置错误:您必须至少配置在 org.junit.platform.commons.util.Preconditions.condition(Preconditions.java:281) 在 org.junit.jupiter.params.ParameterizedTestExtension.lambda$provideTestTemplateInvocationContexts$6(ParameterizedTestExtension.java:90 ) 在 java.util.stream.AbstractPipeline.close(AbstractPipeline.java:323) 在 java.util.stream.ReferencePipeline$7$1.accept(ReferencePipeline.java:279) ... 49 更多

进程以退出代码 255 结束

Neo*_*Neo 11

我想您错过了告诉 JUnit 实例化您的测试类一次的信息,如下所示:

import org.junit.jupiter.api.TestInstance
import org.junit.jupiter.api.TestInstance.Lifecycle.PER_CLASS

@TestInstance(PER_CLASS) // <--- This one will do the trick
class SomeTest {

    private val testDispatcher = TestCoroutineDispatcher()

    @ParameterizedTest
    @MethodSource("someDataStates")
    fun someTest(testState: TestState) = testDispatcher.runBlockingTest {
        // Test state here.
        ...
    }

    private fun someDataStates() = listOf(
        TestState("123"),
        TestState("345")
    )
}
Run Code Online (Sandbox Code Playgroud)


cam*_*amb 6

使用 时@TestInstance(PER_CLASS),您可能会遇到麻烦,例如当尝试验证某个函数是否已使用mockito或调用 X 次时mockk。您必须对所有呼叫求和。

在这种情况下,请使用@JvmStatic

class SomeTest {

    companion object{
        @JvmStatic
        private fun someDataStates() = listOf(
            TestState("123"),
            TestState("345")
        )
    }
    
    private val testDispatcher = TestCoroutineDispatcher()

    @ParameterizedTest
    @MethodSource("someDataStates")
    fun someTest(testState: TestState) = testDispatcher.runBlockingTest {
        // Test state here.
        ...
    }
}
Run Code Online (Sandbox Code Playgroud)

编辑 27/08/2021:自最新版本的 kotlin(使用 1.5.21 测试)以来,请勿将 @JvmStatic 方法设置为,private否则它将再次以PreconditionViolationException