如何在 Gradle/Spring Boot/Kotlin 应用程序中为单元、集成和端到端测试设置不同的文件夹?

How can I have distinct folders for unit, integration and end-to-end tests in a Gradle/Spring Boot/Kotlin application?

我有一个使用 Spring Boot、Gradle 和 Kotlin 构建的应用程序,它与 Kafka 和 PostgreSQL 连接。我使用 resources/application.yml 作为配置文件。 我想为每一种此类测试创建一个特定的包:单元测试、集成测试和端到端测试,但现在我只有一个用于单元测试的包。做这个的最好方式是什么?我正在使用 Junit5。

我想要这样的东西:

application/
├─ src/
│  ├─ e2e/
│  ├─ integration/
│  ├─ main/
│  ├─ test/
build.gradle.kts
gradlew
gradlew.properties
settings.gradle.kts

此外,我希望在 运行 单元测试时我的应用程序不连接到 Kafka 或 Postgres,但在 运行 集成测试时我希望它连接。我该怎么做?

我为 Kafka 尝试了这个配置,但没有成功:

spring
    autoconfigure:
        exclude: org.springframework.boot.autoconfigure.kafka.KafkaAutoConfiguration

我还创建了一个注解来模拟所有 Kafka 消费者和生产者(这有效,但我不想在每个测试中都添加这个注解 class)

@MockBeans(MockBean(MyConsumer::class), MockBean(MyProducer::class))
annotation class MockKafka

@SpringBootTest
@MockKafka
class MyAwesomeTest { // tests here }

我的消费者看起来像这样:

@Component
class MyConsumer() {
    private val logger = LoggerFactory.getLogger(this.javaClass)

    @RetryableTopic(
        attempts = "${kafka-config.consumer.properties.retry.attempts}",
        backoff = Backoff(delayExpression = "${kafka-config.consumer.properties.retry.delay}"),
        fixedDelayTopicStrategy = FixedDelayStrategy.SINGLE_TOPIC
    )
    @KafkaListener(
        topics = ["${spring.kafka.topic.my-topic}"],
        groupId = "${spring.kafka.group-id}"
    )
    fun consumer(event: MyEvent) { // logic here }
}

我的 application.yaml 文件如下所示:

spring:
  profiles:
    active: test
  autoconfigure:
    exclude: org.springframework.boot.autoconfigure.kafka.KafkaAutoConfiguration
  application:
    name: nice_app
  datasource:
    platform: postgres
    url: jdbc:postgresql://localhost:5432/nice_app
  jpa:
    hibernate:
      ddl-auto: validate
    properties:
      hibernate:
        hbm2ddl:
          extra_physical_table_types: "PARTITIONED TABLE"
        dialect: org.hibernate.dialect.PostgreSQLDialect
    show-sql: true
  kafka:
    bootstrap-servers: localhost:9092
    group-id: my-group
    topic:
      my-topic: my-topic

kafka-config:
  consumer:
    properties:
      retry:
        attempts: 5
        delay: 3000
Kotlin version: 1.5.0
Spring Boot version: 2.4.6
Gradlew version: 7.0.2

提前致谢!

为不同的测试分隔文件夹

实际上,Gradle 确实在 official guide 中描述了如何实现这一点。这是您的案例的示例 build.gradle.kts

plugins {
    kotlin("jvm") version "1.5.0"

    id("org.springframework.boot") version "2.5.2"
    id("io.spring.dependency-management") version "1.0.11.RELEASE"
}

group = "com.example.app"
version = "0.1.0-SNAPSHOT"

repositories {
    mavenCentral()
}

sourceSets {
    create("intTest") {
        compileClasspath += sourceSets.main.get().output
        runtimeClasspath += sourceSets.main.get().output
    }
    create("e2eTest") {
        compileClasspath += sourceSets.main.get().output
        runtimeClasspath += sourceSets.main.get().output
    }
}

val intTestImplementation: Configuration by configurations.getting {
    extendsFrom(configurations.implementation.get())
}

val e2eTestImplementation: Configuration by configurations.getting {
    extendsFrom(configurations.implementation.get())
}

configurations["intTestImplementation"].extendsFrom(configurations.runtimeOnly.get())
configurations["e2eTestImplementation"].extendsFrom(configurations.runtimeOnly.get())

dependencies {
    implementation(platform("org.jetbrains.kotlin:kotlin-bom"))
    implementation("org.jetbrains.kotlin:kotlin-stdlib-jdk8")
    implementation("org.springframework.boot:spring-boot-starter-web")

    testImplementation(platform("org.junit:junit-bom:5.7.2"))
    testImplementation("org.junit.jupiter:junit-jupiter")
    testImplementation("org.springframework.boot:spring-boot-starter-test")

    intTestImplementation(platform("org.junit:junit-bom:5.7.2"))
    intTestImplementation("org.junit.jupiter:junit-jupiter")
    intTestImplementation("org.springframework.boot:spring-boot-starter-test")

    e2eTestImplementation(platform("org.junit:junit-bom:5.7.2"))
    e2eTestImplementation("org.junit.jupiter:junit-jupiter")
    e2eTestImplementation("org.springframework.boot:spring-boot-starter-test")
}

val integrationTest = task<Test>("integrationTest") {
    description = "Runs integration tests."
    group = "verification"

    testClassesDirs = sourceSets["intTest"].output.classesDirs
    classpath = sourceSets["intTest"].runtimeClasspath
    shouldRunAfter("test")
}

val end2endTest = task<Test>("end2endTest") {
    description = "Runs end-to-end tests."
    group = "verification"

    testClassesDirs = sourceSets["e2eTest"].output.classesDirs
    classpath = sourceSets["e2eTest"].runtimeClasspath
    shouldRunAfter("test")
}

tasks.test {
    useJUnitPlatform()
}

tasks.getByName<Test>("integrationTest") {
    useJUnitPlatform()
}

tasks.getByName<Test>("end2endTest") {
    useJUnitPlatform()
}

tasks.check {
    dependsOn(integrationTest)
    dependsOn(end2endTest)
}

现在文件夹结构可能是这样的:

└── src
    ├── e2eTest
    │   ├── kotlin
    │   │   └── com/example/app/controller
    │   │       └── HelloControllerE2ETest.kt
    │   └── resources
    ├── intTest
    │   ├── kotlin
    │   │   └── com/example/app/controller
    │   │       └── HelloControllerIntTest.kt
    │   └── resources
    ├── main
    │   ├── kotlin
    │   │   └── com/example/app
    │   │       ├── controller/HelloController.kt
    │   │       └── App.kt
    │   └── resources
    └── test
        ├── kotlin
        │   └── com/example/app/controller
        │       └── HelloControllerTest.kt
        └── resources

不同测试的分离操作

我不熟悉 Kafka,但在我自己的项目中,我针对不同的 PostgreSQL 设置对每个测试应用了不同的 Spring 配置文件。

首先,为build.gradle.kts中的每种测试分配配置文件:

...

tasks.test {
    useJUnitPlatform()
    systemProperty("spring.profiles.active", "test")
}

tasks.getByName<Test>("integrationTest") {
    useJUnitPlatform()
    systemProperty("spring.profiles.active", "integration")
}

tasks.getByName<Test>("end2endTest") {
    useJUnitPlatform()
    systemProperty("spring.profiles.active", "e2e")
}

...

然后,将特定于配置文件的应用程序配置文件创建到相应的 resources/config:

└── src
    ├── e2eTest
    │   ├── kotlin
    │   └── resources/config
    │           └── application-e2e.yml
    ├── intTest
    │   ├── kotlin
    │   └── resources/config
    │           └── application-integration.yml
    ├── main
    │   ├── kotlin
    │   └── resources/config
    │           └── application.yml
    └── test
        ├── kotlin
        └── resources/config
                └── application-test.yml

应用程序将按以下方式加载配置:

Command Profile Config loading order
./gradlew bootRun default applicaiton.yml ( > application-default.yml)
./gradlew test test applicaiton.yml > application-test.yml
./gradlew integrationTest integration applicaiton.yml > application-integration.yml
./gradlew end2endTest e2e applicaiton.yml > application-e2e.yml

现在您应该能够为每个测试配置不同的行为。

参考资料