如何在 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
现在您应该能够为每个测试配置不同的行为。
参考资料
我有一个使用 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 |
现在您应该能够为每个测试配置不同的行为。