预期
将标准的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)
时,您可能会遇到问题,例如,在尝试验证函数已使用mockito
或mockk
调用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")
);
}