JUnit 5 参数化测试 - 与 Kotlin @MethodSource 'Cannot invoke non-static method'



预期

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

观察

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

错误:

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

实现

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

代码

build.gradle(:SomeProject)

dependencies {
...    
classpath("de.mannodermaus.gradle.plugins:android-junit5:$junit5_version")
}

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"
}

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.
...
}
}

构建环境

  • Android Studio 4.0
  • 构建号AI-193.6911.18.40.6514223,构建于2020年5月20日
  • 运行时版本:1.8.0_242-release-1644-b3-6222593 x86_64
  • 虚拟机:JetBrains s.r.o的OpenJDK 64位服务器虚拟机
  • 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.adbida、com.thingworks.gauge、mobi.hsz.idea.gitignore

尝试的解决方案

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

测试用例.kt

fun someDataStates() = Stream.of(
TestState("123"),
TestState("345")
)

SomeTest.kt

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

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

fun someDataStates() = listOf(
TestState("123"),
TestState("345")
)

完整错误日志

org.unit.platform.commons.PreventionViolationException:无法在null目标上调用非静态方法[private-final{someMethodName}。

网址:org.junit.platform.commons.util.Prequisitions.condition(Preconditions.java:296)网址:org.junit.platform.commons.util.ReflectionUtils.invokeMethod(ReflectionUtils.java:682)位于org.junit.jupiter.params.provider.MethodArgumentsProviderLambda$provideArguments$1(MethodArguments provider.java:46)位于java.util.stream.ReferencePipeline$3$1.accept(ReferencePiperine.java:193)位于java.util.stream.ReferencePipeline$3$1.accept(ReferencePiperine.java:193)位于java.util.Splitterators$ArraySpliterator.forEachRemaining(Spliteators.java:948)位于java.util.stream.AbstractPipeline.copyTonto(AbstractPipeline.java:482)位于java.util.stream.AbstractPipeline.wrapAndCopyInto(AbstractPipeline.java:472)位于java.util.stream.ForEachOps$ForEachOp.evalateSequential(ForEachOperation.java:150)位于java.util.stream.ForEachOps$ForEachOp$OfRef.evalateSequential(ForEachOperation.java:173)位于java.util.stream.AbstractPipeline.evaluate(AbstractPipeline.java:234)位于java.util.stream.ReferencePipeline.forEach(ReferencePiperine.java:485)位于java.util.stream.ReferencePipeline$7$1.accept(ReferencePiperine.java:272)位于java.util.stream.ReferencePipeline$3$1.accept(ReferencePiperine.java:193)位于java.util.stream.ReferencePipeline$3$1.accept(ReferencePiperine.java:193)位于java.util.stream.ReferencePipeline$3$1.accept(ReferencePiperine.java:193)位于java.util.ArrayList$ArrayListSpliterator.forEachRemaining(ArrayList.java:1382)位于java.util.stream.AbstractPipeline.copyTonto(AbstractPipeline.java:482)位于java.util.stream.AbstractPipeline.wrapAndCopyInto(AbstractPipeline.java:472)位于java.util.stream.ForEachOps$ForEachOp.evalateSequential(ForEachOperation.java:150)位于java.util.stream.ForEachOps$ForEachOp$OfRef.evalateSequential(ForEachOperation.java:173)位于java.util.stream.AbstractPipeline.evaluate(AbstractPipeline.java:234)位于java.util.stream.ReferencePipeline.forEach(ReferencePiperine.java:485)位于java.util.stream.ReferencePipeline$7$1.accept(ReferencePiperine.java:272)位于java.util.ArrayList$ArrayListSpliterator.forEachRemaining(ArrayList.java:1382)位于java.util.stream.AbstractPipeline.copyTonto(AbstractPipeline.java:482)位于java.util.stream.AbstractPipeline.wrapAndCopyInto(AbstractPipeline.java:472)位于java.util.stream.ForEachOps$ForEachOp.evalateSequential(ForEachOperation.java:150)位于java.util.stream.ForEachOps$ForEachOp$OfRef.evalateSequential(ForEachOperation.java:173)位于java.util.stream.AbstractPipeline.evaluate(AbstractPipeline.java:234)位于java.util.stream.ReferencePipeline.forEach(ReferencePiperine.java:485)位于org.junit.jupiter.engine.descriptor.TestTemplateTestDescriptor.exexecute(TestTemplateTestDescriptor.java:106)位于org.junit.jupiter.engine.descriptor.TestTemplateTestDescriptor.execute(TestTemplateTestDescriptor.java:41)位于org.junit.platform.engine.support.hhierarchy.NodeTestTaskLambda$executeSecurivey$5(NodeTestTask.java:135)网址:org.junit.platform.engine.support.hhierarchy.ThrowableCollectioner.execute(ThrowableCollector.java:73)位于org.junit.platform.engine.support.histrared.NodeTestTaskLambda$executeSecurive$7(NodeTestTask.java:125)网址:org.junit.platform.engine.support.histrared.Node.around(Node.java:135)位于org.junit.platform.engine.support.histrared.NodeTestTaskLambda$executeSecurive$8(NodeTestTask.java:123)网址:org.junit.platform.engine.support.hhierarchy.ThrowableCollectioner.execute(ThrowableCollector.java:73)位于org.junit.platform.engine.support.histrared.NodeTestTask.executeSecurively(NodeTestTask.java:122)网址:org.junit.platform.engine.support.histrared.NodeTestTask.execute(NodeTestTask.java:80)位于java.util.ArrayList.forEach(ArrayList.java:1257)位于org.junit.platform.engine.support.hhierarchical.SameThreadHierarchicalTestExecutiorService.invokeAll(SameThreadHierarchialTestExecutiorServices.java:38)位于org.junit.platform.engine.support.histrared.NodeTestTaskLambda$executeSecurivey$5(NodeTestTask.java:139)网址:org.junit.platform.engine.support.hhierarchy.ThrowableCollectioner.execute(ThrowableCollector.java:73)位于org.junit.platform.engine.support.histrared.NodeTestTaskLambda$executeSecurive$7(NodeTestTask.java:125)网址:org.junit.platform.engine.support.histrared.Node.around(Node.java:135)位于org.junit.platform.engine.support.histrared.NodeTestTaskLambda$executeSecurive$8(NodeTestTask.java:123)网址:org.junit.platform.engine.support.hhierarchy.ThrowableCollectioner.execute(ThrowableCollector.java:73)位于org.junit.platform.engine.support.histrared.NodeTestTask.executeSecurively(NodeTestTask.java:122)网址:org.junit.platform.engine.support.histrared.NodeTestTask.execute(NodeTestTask.java:80)位于java.util.ArrayList.forEach(ArrayList.java:1257)位于org.junit.platform.engine.support.hhierarchical.SameThreadHierarchicalTestExecutiorService.invokeAll(SameThreadHierarchialTestExecutiorServices.java:38)位于org.junit.platform.engine.support.histrared.NodeTestTaskLambda$executeSecurivey$5(NodeTestTask.java:139)网址:org.junit.platform.engine.support.hhierarchy.ThrowableCollectioner.execute(ThrowableCollector.java:73)位于org.junit.platform.engine.support.histrared.NodeTestTaskLambda$executeSecurive$7(NodeTestTask.java:125)网址:org.junit.platform.engine.support.histrared.Node.around(Node.java:135)位于org.junit.platform.engine.support.histrared.NodeTestTaskLambda$executeSecurive$8(NodeTestTask.java:123)网址:org.junit.platform.engine.support.hhierarchy.ThrowableCollectioner.execute(ThrowableCollector.java:73)位于org.junit.platform.engine.support.histrared.NodeTestTask.executeSecurively(NodeTestTask.java:122)网址:org.junit.platform.engine.support.histrared.NodeTestTask.execute(NodeTestTask.java:80)位于org.junit.platform.engine.support.hhierarchical.SameThreadHierarchicalTestExecutiorService.submit(SameThreadHIerarchicaltestExecutiorService.java:32)位于org.junit.platform.engine.support.hhierarchicalTestExecution.execute(HierarchicalTestExecution.java:57)位于org.junit.platform.engine.support.hhierarchicalTestEngine.exexecute(HierarchicalTestEngine.java:51)位于org.junit.platform.selauncher.core.DefaultLauncher.execute(DefaultLauncher.java:248)位于org.junit.platform.selauncher.core.DefaultLauncher.lambda$execute$5(DefaultLauncher.java:211)位于org.junit.platform.selauncher.core.DefaultLauncher.withInterceptedStreams(DefaultLauncher.java:226)位于org.junit.platform.selauncher.core.DefaultLauncher.execute(DefaultLauncher.java:199)位于org.junit.platform.selauncher.core.DefaultLauncher.execute(DefaultLauncher.java:132)网址:com.intellij.junit5.JUnit5IdeaTestRunner.startRunnerWithArgs网址:com.intellij.rt.junit.IideaTestRunner$Repeater.startRunnerWithArgs(IdeaTestRunner.java:33)网址:com.intellij.rt.junit.JUnitStarter.prepareStreamsAndStart(JUnitStarter.java:230)网址:com.intellij.rt.junit.JUnitStarter.main(JUnitStarter.java:58)已抑制:org.unit.platform.commons.PreventionViolationException:配置错误:您必须为此@ParameterizedTest配置至少一组参数网址:org.junit.platform.commons.util.Prequisitions.condition(Preconditions.java:281)位于org.junit.jupiter.params.ParameterizedTestExtensionLambda$provideTestTemplateInvocationContexts$6(ParameterizedTestExtensions.java:90)位于java.util.stream.AbstractPipeline.close(AbstractPipeline.java:323)位于java.util.stream.ReferencePipeline$7$1.accept(ReferencePiperine.java:279)…还有49个

进程结束,退出代码为255

我想您错过了告诉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")
)
}

使用@TestInstance(PER_CLASS)时,您可能会遇到问题,例如,在尝试验证函数已使用mockitomockk调用X次时。你必须把所有的电话都加起来。

在这种情况下,使用@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.
...
}
}

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

如果由于某种原因无法使用@TestInstance(PER_CLASS)注释,则可以在测试方法中使用@TestFactory注释而不是@ParameterizedTest注释。

@TestFactory
Stream<DynamicTest> someTest() {
return someDataStates().map(testState -> DynamicTest.dynamicTest(testState.getName(), () -> someTest0(testState)));
}
private void someTest0(TestState testState) {
// Test state here.
}
private Stream<TestState> someDataStates() {
return Stream.of(
new TestState("123"),
new TestState("345")
);
}

相关内容

最新更新