【问题标题】:Integration tests with Gradle Kotlin DSL使用 Gradle Kotlin DSL 进行集成测试
【发布时间】:2019-03-25 01:38:03
【问题描述】:

我正在使用this blog post 为 Spring Boot 项目配置集成测试,但我一直坚持声明源集。我也找到了this post on StackOverflow,但我想我已经走得更远了。

我的项目结构是

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 包。

【问题讨论】:

    标签: gradle kotlin integration-testing gradle-kotlin-dsl


    【解决方案1】:

    多亏了 Kotlin Slack 频道上的一些帮助,我终于弄明白了。首先,我必须升级到 Gradle 版本 4.10.2。

    有关更多信息,请查看 Gradle 的这两个页面:

    然后我只需要为 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 ("integrationTest") { .... } tasks.check {dependOn(integrationTest) }
    【解决方案2】:

    我不喜欢 withConvention 的使用以及如何设置 kotlin src 目录。所以在查看了 gradle 文档 herehere 之后,我想出了这个:

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

      【讨论】:

      【解决方案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)
      }
      

      我希望这会有所帮助。 :)

      【讨论】:

        猜你喜欢
        • 2019-02-04
        • 2018-09-13
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2020-07-24
        • 1970-01-01
        • 2020-07-22
        相关资源
        最近更新 更多