Integration tests with Gradle Kotlin DSL

前端 未结 4 960
梦谈多话
梦谈多话 2020-12-31 06:07

I\'m using this blog post to configure integration tests for a Spring Boot project, but I\'m pretty stuck on declaring the source sets. I also found this post on StackOverfl

相关标签:
4条回答
  • 2020-12-31 06:22

    I was finally able to figure it out thanks to some help on the Kotlin Slack channel. First of all I had to upgrade to Gradle version 4.10.2.

    For more info have a look at these two pages from 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

    Then I just had to create the sourceSets for the integrationTests

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

    This would work just fine for Java, but since I'm working with Kotlin I had to add an extra withConvention wrapper

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

    In the docs they only put runtimeClasspath += output + compileClasspath, but I added sourceSets["test"].runtimeClasspath so I can directly use the test dependencies instead of declaring new dependencies for the integrationTest task.

    Once the sourceSets were created it was a matter of declaring a new task

    task<Test>("integrationTest") {
        description = "Runs the integration tests"
        group = "verification"
        testClassesDirs = sourceSets["integrationTest"].output.classesDirs
        classpath = sourceSets["integrationTest"].runtimeClasspath
        mustRunAfter(tasks["test"])
    }
    

    After this the tests still didn't run, but that was because I'm using JUnit4. So I just had to add useJUnitPlatform() which makes this the final code

    task<Test>("integrationTest") {
        description = "Runs the integration tests"
        group = "verification"
        testClassesDirs = sourceSets["integrationTest"].output.classesDirs
        classpath = sourceSets["integrationTest"].runtimeClasspath
        mustRunAfter(tasks["test"])
        useJUnitPlatform()
    }
    
    0 讨论(0)
  • 2020-12-31 06:39

    Here is git repo that you can refer to: 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)
    }
    

    I hope this helps. :)

    0 讨论(0)
  • 2020-12-31 06:44

    As of Gradle 5.2.1 see 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) }
    
    0 讨论(0)
  • 2020-12-31 06:47

    I didnt like the use of withConvention and how the kotlin src dir was set. So after check out both gradle docs here and here, I came up with this:

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

    I preferr the less verbose style when using kotlin { and the use of variable for the new integrationTestTask.

    0 讨论(0)
提交回复
热议问题