使用 Gradle Kotlin DSL 进行集成测试
Posted
技术标签:
【中文标题】使用 Gradle Kotlin DSL 进行集成测试【英文标题】:Integration tests with Gradle Kotlin DSL 【发布时间】:2019-03-25 01:38:03 【问题描述】:我正在使用this blog post 为 Spring Boot 项目配置集成测试,但我一直坚持声明源集。我也找到了this post on ***,但我想我已经走得更远了。
我的项目结构是
project
|_ src
|_ main
| |_ kotlin
| |_ resources
|_ testIntegration
| |_ kotlin
| |_ resources
|_ test
| |_ kotlin
| |_ resources
|_ build.gradle.kts
|_ ... other files
还有build.gradle.kts
import org.jetbrains.kotlin.gradle.tasks.KotlinCompile
plugins
idea
kotlin("jvm")
id("org.springframework.boot") version "2.0.5.RELEASE"
id("org.jetbrains.kotlin.plugin.spring") version "1.2.71"
fun DependencyHandlerScope.springBoot(module: String) = this.compile("org.springframework.boot:spring-boot-$module:2.0.5.RELEASE")
fun DependencyHandlerScope.springBootStarter(module: String) = this.springBoot("starter-$module")
dependencies
springBoot("devtools")
springBootStarter("batch")
springBootStarter("... spring boot dependencies")
compile("... more dependencies")
testCompile("... more test dependencies")
val test by tasks.getting(Test::class)
useJUnitPlatform
kotlin
sourceSets
val integrationTest by creating
kotlin.srcDir("src/testIntegration/kotlin")
resources.srcDir("src/testIntegration/resources")
val integrationTestCompile by configurations.creating
extendsFrom(configurations["testCompile"])
val integrationTestRuntime by configurations.creating
extendsFrom(configurations["testRuntime"])
val testIntegration by tasks.creating(Test::class)
group = "verification"
testClassesDirs = kotlin.sourceSets["integrationTest"].kotlin
idea
module
testSourceDirs.addAll(kotlin.sourceSets["integrationTest"].kotlin.srcDirs)
testSourceDirs.addAll(kotlin.sourceSets["integrationTest"].resources.srcDirs)
我认为我的方向非常正确。至少它不再抛出异常:)
当我运行 testIntegration 任务时,我得到以下输出:
Testing started at 12:08 ...
12:08:49: Executing task 'testIntegration'...
> Task :project:compileKotlin UP-TO-DATE
> Task :project:compileJava NO-SOURCE
> Task :project:processResources UP-TO-DATE
> Task :project:classes UP-TO-DATE
> Task :project:compileTestKotlin UP-TO-DATE
> Task :project:compileTestJava NO-SOURCE
> Task :project:processTestResources UP-TO-DATE
> Task :project:testClasses UP-TO-DATE
> Task :project:testIntegration
BUILD SUCCESSFUL in 2s
5 actionable tasks: 1 executed, 4 up-to-date
12:08:51: Task execution finished 'testIntegration'.
此外,IntelliJ 不会将 testIntegration 目录识别为 Kotlin 包。
【问题讨论】:
【参考方案1】:多亏了 Kotlin Slack 频道上的一些帮助,我终于弄明白了。首先,我必须升级到 Gradle 版本 4.10.2。
有关更多信息,请查看 Gradle 的这两个页面:
https://docs.gradle.org/release-nightly/userguide/organizing_gradle_projects.html#sec:separate_test_type_source_files https://docs.gradle.org/release-nightly/userguide/organizing_gradle_projects.html#sec:separate_test_type_source_files然后我只需要为 integrationTests 创建 sourceSets
sourceSets
create("integrationTest")
kotlin.srcDir("src/integrationTest/kotlin")
resources.srcDir("src/integrationTest/resources")
compileClasspath += sourceSets["main"].output + configurations["testRuntimeClasspath"]
runtimeClasspath += output + compileClasspath + sourceSets["test"].runtimeClasspath
这适用于 Java,但由于我使用的是 Kotlin,因此我不得不添加一个额外的 withConvention
包装器
sourceSets
create("integrationTest")
withConvention(KotlinSourceSet::class)
kotlin.srcDir("src/integrationTest/kotlin")
resources.srcDir("src/integrationTest/resources")
compileClasspath += sourceSets["main"].output + configurations["testRuntimeClasspath"]
runtimeClasspath += output + compileClasspath + sourceSets["test"].runtimeClasspath
在文档中他们只输入了runtimeClasspath += output + compileClasspath
,但我添加了sourceSets["test"].runtimeClasspath
,因此我可以直接使用测试依赖项,而不是为integrationTest
任务声明新的依赖项。
一旦创建了 sourceSet,就需要声明一个新任务
task<Test>("integrationTest")
description = "Runs the integration tests"
group = "verification"
testClassesDirs = sourceSets["integrationTest"].output.classesDirs
classpath = sourceSets["integrationTest"].runtimeClasspath
mustRunAfter(tasks["test"])
在此之后测试仍然没有运行,但那是因为我使用的是 JUnit4。所以我只需要添加 useJUnitPlatform()
这就是最终代码
task<Test>("integrationTest")
description = "Runs the integration tests"
group = "verification"
testClassesDirs = sourceSets["integrationTest"].output.classesDirs
classpath = sourceSets["integrationTest"].runtimeClasspath
mustRunAfter(tasks["test"])
useJUnitPlatform()
【讨论】:
很高兴你知道了,因为它帮助我解决了我的问题。我发现很难理解何时使用“withConvention”......似乎总是在使用另一种语言如 Java 时(在我的情况下,Groovy 用于 Spock 测试)。当您想到类型系统时这是有道理的,但如果需要 withConvention(JavaXX) 而不是推断,我会发现它更一致。 这对我很有帮助,在我的情况下,我试图配置为运行 spock 和 kotlin,所以我必须做一些小改动,withConvention 的部分非常直观,最后我刚刚有了将其更改为 GroovySourceSet: withConvention(org.gradle.api.tasks.GroovySourceSet::class) groovy.srcDir("src/integration/groovy") 另外补充一点,如果你想在构建过程中运行测试,你必须在变量中设置 yoru 测试任务并将其添加到你的任务检查中: val integrationTest = task我不喜欢 withConvention 的使用以及如何设置 kotlin src 目录。所以在查看了 gradle 文档 here 和 here 之后,我想出了这个:
sourceSets
create("integrationTest")
kotlin
compileClasspath += main.get().output + configurations.testRuntimeClasspath
runtimeClasspath += output + compileClasspath
val integrationTest = task<Test>("integrationTest")
description = "Runs the integration tests"
group = "verification"
testClassesDirs = sourceSets["integrationTest"].output.classesDirs
classpath = sourceSets["integrationTest"].runtimeClasspath
mustRunAfter(tasks["test"])
tasks.check
dependsOn(integrationTest)
在使用kotlin
时,我更喜欢简洁的风格,并且在新的 integrationTestTask 中使用变量。
【讨论】:
【参考方案3】:从 Gradle 5.2.1 开始,请参阅 https://docs.gradle.org/current/userguide/java_testing.html#sec:configuring_java_integration_tests
sourceSets
create("intTest")
compileClasspath += sourceSets.main.get().output
runtimeClasspath += sourceSets.main.get().output
val intTestImplementation by configurations.getting
extendsFrom(configurations.testImplementation.get())
configurations["intTestRuntimeOnly"].extendsFrom(configurations.runtimeOnly.get())
dependencies
intTestImplementation("junit:junit:4.12")
val integrationTest = task<Test>("integrationTest")
description = "Runs integration tests."
group = "verification"
testClassesDirs = sourceSets["intTest"].output.classesDirs
classpath = sourceSets["intTest"].runtimeClasspath
shouldRunAfter("test")
tasks.check dependsOn(integrationTest)
【讨论】:
你所做的只是从 Gradle 的文档中复制一段代码:docs.gradle.org/current/userguide/…【参考方案4】:这里是git repo,你可以参考:enter link description here
import org.gradle.api.tasks.testing.logging.TestExceptionFormat
import org.gradle.api.tasks.testing.logging.TestLogEvent
plugins
application
kotlin("jvm") version "1.3.72"
id("com.diffplug.gradle.spotless") version "3.24.2"
id("org.jmailen.kotlinter") version "1.26.0"
checkstyle
version = "1.0.2"
group = "org.sample"
application
mainClass.set("org.sample.MainKt")
repositories
mavenCentral()
jcenter()
tasks.checkstyleMain group = "verification"
tasks.checkstyleTest group = "verification"
spotless
kotlin
ktlint()
kotlinGradle
target(fileTree(projectDir).apply
include("*.gradle.kts")
+ fileTree("src").apply
include("**/*.gradle.kts")
)
ktlint()
tasks.withType<Test>
useJUnitPlatform()
testLogging
lifecycle
events = mutableSetOf(TestLogEvent.FAILED, TestLogEvent.PASSED, TestLogEvent.SKIPPED)
exceptionFormat = TestExceptionFormat.FULL
showExceptions = true
showCauses = true
showStackTraces = true
showStandardStreams = true
info.events = lifecycle.events
info.exceptionFormat = lifecycle.exceptionFormat
val failedTests = mutableListOf<TestDescriptor>()
val skippedTests = mutableListOf<TestDescriptor>()
addTestListener(object : TestListener
override fun beforeSuite(suite: TestDescriptor)
override fun beforeTest(testDescriptor: TestDescriptor)
override fun afterTest(testDescriptor: TestDescriptor, result: TestResult)
when (result.resultType)
TestResult.ResultType.FAILURE -> failedTests.add(testDescriptor)
TestResult.ResultType.SKIPPED -> skippedTests.add(testDescriptor)
else -> Unit
override fun afterSuite(suite: TestDescriptor, result: TestResult)
if (suite.parent == null) // root suite
logger.lifecycle("----")
logger.lifecycle("Test result: $result.resultType")
logger.lifecycle(
"Test summary: $result.testCount tests, " +
"$result.successfulTestCount succeeded, " +
"$result.failedTestCount failed, " +
"$result.skippedTestCount skipped")
failedTests.takeIf it.isNotEmpty() ?.prefixedSummary("\tFailed Tests")
skippedTests.takeIf it.isNotEmpty() ?.prefixedSummary("\tSkipped Tests:")
private infix fun List<TestDescriptor>.prefixedSummary(subject: String)
logger.lifecycle(subject)
forEach test -> logger.lifecycle("\t\t$test.displayName()")
private fun TestDescriptor.displayName() = parent?.let "$it.name - $name" ?: "$name"
)
dependencies
implementation(kotlin("stdlib"))
implementation("com.sparkjava:spark-core:2.5.4")
implementation("org.slf4j:slf4j-simple:1.7.30")
testImplementation("com.squareup.okhttp:okhttp:2.5.0")
testImplementation("io.kotest:kotest-runner-junit5-jvm:4.0.5")
testImplementation("io.kotest:kotest-assertions-core-jvm:4.0.5") // for kotest core jvm assertions
testImplementation("io.kotest:kotest-property-jvm:4.0.5")
sourceSets
create("integTest")
kotlin
compileClasspath += main.get().output + configurations.testRuntimeClasspath
runtimeClasspath += output + compileClasspath
val integTest = task<Test>("integTest")
description = "Runs the integTest tests"
group = "verification"
testClassesDirs = sourceSets["integTest"].output.classesDirs
classpath = sourceSets["integTest"].runtimeClasspath
mustRunAfter(tasks["test"])
tasks.check
dependsOn(integTest)
sourceSets
create("journeyTest")
kotlin
compileClasspath += main.get().output + configurations.testRuntimeClasspath
runtimeClasspath += output + compileClasspath
val journeyTest = task<Test>("journeyTest")
description = "Runs the JourneyTest tests"
group = "verification"
testClassesDirs = sourceSets["journeyTest"].output.classesDirs
classpath = sourceSets["journeyTest"].runtimeClasspath
mustRunAfter(tasks["integTest"])
tasks.check
dependsOn(journeyTest)
我希望这会有所帮助。 :)
【讨论】:
以上是关于使用 Gradle Kotlin DSL 进行集成测试的主要内容,如果未能解决你的问题,请参考以下文章
如何使用 Gradle Kotlin DSL 对 Spring Boot 应用程序进行 Dockerize
Gradle Kotlin DSL:在独特的地方定义 Kotlin 版本
使用 Gradle Kotlin DSL 发布 Kotlin MPP 元数据