gradle-testing-setup

Compare original and translation side by side

🇺🇸

Original

English
🇨🇳

Translation

Chinese

Gradle Testing Setup

Gradle 测试环境搭建

Table of Contents

目录

When to Use This Skill

何时使用本技能

Use this skill when you need to:
  • Set up JUnit 5 (Jupiter) testing framework
  • Configure TestContainers for integration tests with real databases
  • Separate unit tests from integration tests
  • Measure code coverage with JaCoCo
  • Enforce minimum code coverage thresholds
  • Configure parallel test execution for faster test runs
  • Set up test logging and reporting in CI/CD
  • Create separate source sets for integration tests
当你需要以下功能时可使用本技能:
  • 搭建JUnit 5(Jupiter)测试框架
  • 配置TestContainers以使用真实数据库进行集成测试
  • 分离单元测试与集成测试
  • 使用JaCoCo统计代码覆盖率
  • 强制执行最低代码覆盖率阈值
  • 配置并行测试执行以加快测试运行速度
  • 在CI/CD中配置测试日志与报告
  • 为集成测试创建独立的源码集

Quick Start

快速开始

Add to
build.gradle.kts
:
kotlin
dependencies {
    testImplementation("org.springframework.boot:spring-boot-starter-test")
    testImplementation("org.junit.jupiter:junit-jupiter")
    testImplementation("org.testcontainers:testcontainers:1.21.0")
    testImplementation("org.testcontainers:junit-jupiter:1.21.0")
    testImplementation("org.testcontainers:postgresql:1.21.0")

    testRuntimeOnly("org.junit.platform:junit-platform-launcher")
}

plugins {
    id("jacoco")
}

tasks.test {
    useJUnitPlatform()
}

tasks.jacocoTestReport {
    dependsOn(tasks.test)
    finalizedBy(tasks.jacocoTestCoverageVerification)
}

tasks.check {
    dependsOn(tasks.jacocoTestReport)
}
Run tests:
bash
./gradlew test                    # Run unit tests
./gradlew test jacocoTestReport   # With coverage report
./gradlew integrationTest         # Run integration tests (if configured)
build.gradle.kts
中添加以下配置:
kotlin
dependencies {
    testImplementation("org.springframework.boot:spring-boot-starter-test")
    testImplementation("org.junit.jupiter:junit-jupiter")
    testImplementation("org.testcontainers:testcontainers:1.21.0")
    testImplementation("org.testcontainers:junit-jupiter:1.21.0")
    testImplementation("org.testcontainers:postgresql:1.21.0")

    testRuntimeOnly("org.junit.platform:junit-platform-launcher")
}

plugins {
    id("jacoco")
}

tasks.test {
    useJUnitPlatform()
}

tasks.jacocoTestReport {
    dependsOn(tasks.test)
    finalizedBy(tasks.jacocoTestCoverageVerification)
}

tasks.check {
    dependsOn(tasks.jacocoTestReport)
}
运行测试:
bash
./gradlew test                    # 运行单元测试
./gradlew test jacocoTestReport   # 生成覆盖率报告
./gradlew integrationTest         # 运行集成测试(已配置的情况下)

Instructions

操作步骤

Step 1: Configure JUnit 5 (Jupiter)

步骤1:配置JUnit 5(Jupiter)

Add JUnit 5 dependencies:
kotlin
dependencies {
    testImplementation("org.springframework.boot:spring-boot-starter-test")
    testImplementation("org.junit.jupiter:junit-jupiter:5.11.0")
    testRuntimeOnly("org.junit.platform:junit-platform-launcher:1.11.0")
}

tasks.test {
    useJUnitPlatform()
}
Advanced JUnit 5 configuration:
kotlin
tasks.test {
    useJUnitPlatform {
        // Include/exclude tags
        includeTags("unit", "integration")
        excludeTags("slow", "manual")

        // Include/exclude by engine
        includeEngines("junit-jupiter")
        excludeEngines("junit-vintage")
    }

    // Filter tests by pattern
    filter {
        includeTestsMatching("*Test")
        includeTestsMatching("*Tests")
        excludeTestsMatching("*IntegrationTest")
    }

    // Parallel test execution
    maxParallelForks = Runtime.getRuntime().availableProcessors() / 2

    // System properties for tests
    systemProperty("junit.jupiter.execution.parallel.enabled", "true")
    systemProperty("junit.jupiter.execution.parallel.mode.default", "concurrent")

    // Detailed logging
    testLogging {
        events("passed", "skipped", "failed", "standardOut")
        showExceptions = true
        showStackTraces = true
        showCauses = true
        exceptionFormat = org.gradle.api.tasks.testing.logging.TestExceptionFormat.FULL
    }
}
添加JUnit 5依赖:
kotlin
dependencies {
    testImplementation("org.springframework.boot:spring-boot-starter-test")
    testImplementation("org.junit.jupiter:junit-jupiter:5.11.0")
    testRuntimeOnly("org.junit.platform:junit-platform-launcher:1.11.0")
}

tasks.test {
    useJUnitPlatform()
}
JUnit 5高级配置
kotlin
tasks.test {
    useJUnitPlatform {
        // 包含/排除标签
        includeTags("unit", "integration")
        excludeTags("slow", "manual")

        // 包含/排除测试引擎
        includeEngines("junit-jupiter")
        excludeEngines("junit-vintage")
    }

    // 按模式过滤测试
    filter {
        includeTestsMatching("*Test")
        includeTestsMatching("*Tests")
        excludeTestsMatching("*IntegrationTest")
    }

    // 并行测试执行
    maxParallelForks = Runtime.getRuntime().availableProcessors() / 2

    // 测试系统属性
    systemProperty("junit.jupiter.execution.parallel.enabled", "true")
    systemProperty("junit.jupiter.execution.parallel.mode.default", "concurrent")

    // 详细日志
    testLogging {
        events("passed", "skipped", "failed", "standardOut")
        showExceptions = true
        showStackTraces = true
        showCauses = true
        exceptionFormat = org.gradle.api.tasks.testing.logging.TestExceptionFormat.FULL
    }
}

Step 2: Set Up TestContainers for Integration Tests

步骤2:为集成测试配置TestContainers

Add TestContainers dependencies:
kotlin
dependencies {
    testImplementation("org.testcontainers:testcontainers:1.21.0")
    testImplementation("org.testcontainers:junit-jupiter:1.21.0")
    testImplementation("org.testcontainers:postgresql:1.21.0")
    testImplementation("org.testcontainers:gcloud:1.21.0")  // For Pub/Sub emulator
}

tasks.test {
    useJUnitPlatform()

    // Docker socket configuration
    systemProperty("testcontainers.reuse.enable", "true")
}
Example integration test with TestContainers:
java
@SpringBootTest
@Testcontainers
class SupplierChargesIntegrationTest {

    @Container
    static PostgreSQLContainer<?> postgres = new PostgreSQLContainer<>("postgres:15")
        .withDatabaseName("testdb")
        .withUsername("test")
        .withPassword("test");

    @Container
    static GenericContainer<?> pubsub = new GenericContainer<>("google/cloud-sdk:emulators")
        .withExposedPorts(8085)
        .withCommand("gcloud", "beta", "emulators", "pubsub", "start", "--host-port=0.0.0.0:8085");

    @DynamicPropertySource
    static void configureProperties(DynamicPropertyRegistry registry) {
        registry.add("spring.datasource.url", postgres::getJdbcUrl);
        registry.add("spring.datasource.username", postgres::getUsername);
        registry.add("spring.datasource.password", postgres::getPassword);
        registry.add("spring.cloud.gcp.pubsub.emulator-host",
            () -> "localhost:" + pubsub.getMappedPort(8085));
    }

    @Test
    void testDatabaseAndPubSub() {
        // Test with real PostgreSQL and Pub/Sub emulator
    }
}
添加TestContainers依赖:
kotlin
dependencies {
    testImplementation("org.testcontainers:testcontainers:1.21.0")
    testImplementation("org.testcontainers:junit-jupiter:1.21.0")
    testImplementation("org.testcontainers:postgresql:1.21.0")
    testImplementation("org.testcontainers:gcloud:1.21.0")  // 用于Pub/Sub模拟器
}

tasks.test {
    useJUnitPlatform()

    // Docker套接字配置
    systemProperty("testcontainers.reuse.enable", "true")
}
使用TestContainers的集成测试示例
java
@SpringBootTest
@Testcontainers
class SupplierChargesIntegrationTest {

    @Container
    static PostgreSQLContainer<?> postgres = new PostgreSQLContainer<>("postgres:15")
        .withDatabaseName("testdb")
        .withUsername("test")
        .withPassword("test");

    @Container
    static GenericContainer<?> pubsub = new GenericContainer<>("google/cloud-sdk:emulators")
        .withExposedPorts(8085)
        .withCommand("gcloud", "beta", "emulators", "pubsub", "start", "--host-port=0.0.0.0:8085");

    @DynamicPropertySource
    static void configureProperties(DynamicPropertyRegistry registry) {
        registry.add("spring.datasource.url", postgres::getJdbcUrl);
        registry.add("spring.datasource.username", postgres::getUsername);
        registry.add("spring.datasource.password", postgres::getPassword);
        registry.add("spring.cloud.gcp.pubsub.emulator-host",
            () -> "localhost:" + pubsub.getMappedPort(8085));
    }

    @Test
    void testDatabaseAndPubSub() {
        // 使用真实PostgreSQL和Pub/Sub模拟器进行测试
    }
}

Step 3: Separate Unit and Integration Tests

步骤3:分离单元测试与集成测试

Create separate source sets and tasks:
kotlin
sourceSets {
    create("integrationTest") {
        java {
            srcDir("src/integrationTest/java")
            compileClasspath += sourceSets.main.get().output + sourceSets.test.get().output
            runtimeClasspath += sourceSets.main.get().output + sourceSets.test.get().output
        }
        resources {
            srcDir("src/integrationTest/resources")
        }
    }
}

// Create integration test task
val integrationTest = tasks.register<Test>("integrationTest") {
    description = "Run integration tests"
    group = "verification"

    testClassesDirs = sourceSets["integrationTest"].output.classesDirs
    classpath = sourceSets["integrationTest"].runtimeClasspath

    useJUnitPlatform()

    // Run after unit tests
    shouldRunAfter(tasks.test)

    // Logging
    testLogging {
        events("passed", "skipped", "failed")
    }
}

// Include integration tests in overall check
tasks.check {
    dependsOn(integrationTest)
}
Directory structure:
src/
├── main/
│   └── java/
├── test/                          # Unit tests
│   ├── java/
│   │   └── com/example/
│   │       ├── ServiceTest.java
│   │       └── ControllerTest.java
│   └── resources/
│       └── application-test.yml
└── integrationTest/               # Integration tests
    ├── java/
    │   └── com/example/
    │       └── ServiceIntegrationTest.java
    └── resources/
        └── application-integration.yml
创建独立的源码集和任务:
kotlin
sourceSets {
    create("integrationTest") {
        java {
            srcDir("src/integrationTest/java")
            compileClasspath += sourceSets.main.get().output + sourceSets.test.get().output
            runtimeClasspath += sourceSets.main.get().output + sourceSets.test.get().output
        }
        resources {
            srcDir("src/integrationTest/resources")
        }
    }
}

// 创建集成测试任务
val integrationTest = tasks.register<Test>("integrationTest") {
    description = "Run integration tests"
    group = "verification"

    testClassesDirs = sourceSets["integrationTest"].output.classesDirs
    classpath = sourceSets["integrationTest"].runtimeClasspath

    useJUnitPlatform()

    // 在单元测试后运行
    shouldRunAfter(tasks.test)

    // 日志配置
    testLogging {
        events("passed", "skipped", "failed")
    }
}

// 将集成测试纳入整体检查流程
tasks.check {
    dependsOn(integrationTest)
}
目录结构
src/
├── main/
│   └── java/
├── test/                          # 单元测试
│   ├── java/
│   │   └── com/example/
│   │       ├── ServiceTest.java
│   │       └── ControllerTest.java
│   └── resources/
│       └── application-test.yml
└── integrationTest/               # 集成测试
    ├── java/
    │   └── com/example/
    │       └── ServiceIntegrationTest.java
    └── resources/
        └── application-integration.yml

Step 4: Configure Code Coverage with JaCoCo

步骤4:使用JaCoCo配置代码覆盖率

Add JaCoCo plugin and configuration:
kotlin
plugins {
    id("jacoco")
}

jacoco {
    toolVersion = "0.8.12"
}

tasks.jacocoTestReport {
    dependsOn(tasks.test)

    reports {
        xml.required = true
        html.required = true
        csv.required = false

        xml.outputLocation = layout.buildDirectory.file("reports/jacoco/test/jacocoTestReport.xml")
        html.outputLocation = layout.buildDirectory.dir("reports/jacoco/test/html")
    }

    finalizedBy(tasks.jacocoTestCoverageVerification)
}

// Enforce coverage minimums
tasks.jacocoTestCoverageVerification {
    violationRules {
        // Overall coverage requirement
        rule {
            element = "BUNDLE"
            limit {
                minimum = BigDecimal("0.60")  // 60% minimum
            }
        }

        // Class-level requirements
        rule {
            element = "CLASS"
            excludes = listOf("**/config/*", "**/dto/*")
            limit {
                counter = "LINE"
                value = "COVEREDRATIO"
                minimum = BigDecimal("0.50")  // 50% per class
            }
        }

        // Method-level requirements
        rule {
            element = "METHOD"
            limit {
                counter = "LINE"
                value = "COVEREDRATIO"
                minimum = BigDecimal("0.40")  // 40% per method
            }
        }
    }
}

// Generate report after tests
tasks.test {
    finalizedBy(tasks.jacocoTestReport)
}

// Include in overall check
tasks.check {
    dependsOn(tasks.jacocoTestReport)
}
添加JaCoCo插件与配置:
kotlin
plugins {
    id("jacoco")
}

jacoco {
    toolVersion = "0.8.12"
}

tasks.jacocoTestReport {
    dependsOn(tasks.test)

    reports {
        xml.required = true
        html.required = true
        csv.required = false

        xml.outputLocation = layout.buildDirectory.file("reports/jacoco/test/jacocoTestReport.xml")
        html.outputLocation = layout.buildDirectory.dir("reports/jacoco/test/html")
    }

    finalizedBy(tasks.jacocoTestCoverageVerification)
}

// 强制执行最低覆盖率要求
tasks.jacocoTestCoverageVerification {
    violationRules {
        // 整体覆盖率要求
        rule {
            element = "BUNDLE"
            limit {
                minimum = BigDecimal("0.60")  // 最低60%
            }
        }

        // 类级别覆盖率要求
        rule {
            element = "CLASS"
            excludes = listOf("**/config/*", "**/dto/*")
            limit {
                counter = "LINE"
                value = "COVEREDRATIO"
                minimum = BigDecimal("0.50")  // 每个类最低50%
            }
        }

        // 方法级别覆盖率要求
        rule {
            element = "METHOD"
            limit {
                counter = "LINE"
                value = "COVEREDRATIO"
                minimum = BigDecimal("0.40")  // 每个方法最低40%
            }
        }
    }
}

// 测试完成后生成报告
tasks.test {
    finalizedBy(tasks.jacocoTestReport)
}

// 将覆盖率检查纳入整体流程
tasks.check {
    dependsOn(tasks.jacocoTestReport)
}

Step 5: Configure Test Logging

步骤5:配置测试日志

Use prettier test output with test-logger plugin:
kotlin
plugins {
    id("com.adarshr.test-logger") version "4.0.0"
}

testlogger {
    theme = "mocha"  // Options: plain, standard, mocha, standard-parallel, mocha-parallel
    showExceptions = true
    showStackTraces = true
    showFullStackTraces = false
    showCauses = true
    slowThreshold = 2000  // Warn for tests > 2 seconds
    showSummary = true
    showPassed = true
    showSkipped = true
    showFailed = true
    showStandardStreams = false
}
使用test-logger插件优化测试输出:
kotlin
plugins {
    id("com.adarshr.test-logger") version "4.0.0"
}

testlogger {
    theme = "mocha"  // 可选主题:plain, standard, mocha, standard-parallel, mocha-parallel
    showExceptions = true
    showStackTraces = true
    showFullStackTraces = false
    showCauses = true
    slowThreshold = 2000  // 测试耗时超过2秒时发出警告
    showSummary = true
    showPassed = true
    showSkipped = true
    showFailed = true
    showStandardStreams = false
}

Step 6: Configure Docker for TestContainers

步骤6:为TestContainers配置Docker

Ensure Docker is properly configured:
bash
undefined
确保Docker已正确配置:
bash
undefined

Verify Docker is running

验证Docker是否运行

docker ps
docker ps

On macOS with Docker Desktop

在macOS的Docker Desktop环境下

export TESTCONTAINERS_DOCKER_SOCKET_OVERRIDE=/var/run/docker.sock export DOCKER_HOST=unix://${HOME}/.docker/run/docker.sock
export TESTCONTAINERS_DOCKER_SOCKET_OVERRIDE=/var/run/docker.sock export DOCKER_HOST=unix://${HOME}/.docker/run/docker.sock

Run tests

运行测试

./gradlew test

In `gradle.properties` for CI/CD:

```properties
org.gradle.logging.level=info
testcontainers.reuse.enable=true
./gradlew test

在CI/CD的`gradle.properties`中配置:

```properties
org.gradle.logging.level=info
testcontainers.reuse.enable=true

Examples

示例

Example: Running Different Test Suites

示例:运行不同测试套件

bash
undefined
bash
undefined

Unit tests only

仅运行单元测试

./gradlew test
./gradlew test

Integration tests only

仅运行集成测试

./gradlew integrationTest
./gradlew integrationTest

All tests

运行所有测试

./gradlew check
./gradlew check

With coverage report

生成覆盖率报告

./gradlew test jacocoTestReport
./gradlew test jacocoTestReport

Specific test class

运行指定测试类

./gradlew test --tests "com.example.ServiceTest"
./gradlew test --tests "com.example.ServiceTest"

Tests matching pattern

运行匹配模式的测试

./gradlew test --tests "*IntegrationTest"
./gradlew test --tests "*IntegrationTest"

Single test method

运行单个测试方法

./gradlew test --tests "com.example.ServiceTest.testMethod"
./gradlew test --tests "com.example.ServiceTest.testMethod"

Verbose output

详细输出

./gradlew test --info
./gradlew test --info

Continuous mode (rerun on changes)

持续模式(代码变更时自动重跑)

./gradlew test --continuous


For advanced examples including TestContainers integration, GitHub Actions with coverage reports, and CI coverage verification, see [references/advanced-examples.md](references/advanced-examples.md).
./gradlew test --continuous

如需包含TestContainers集成、GitHub Actions覆盖率报告、CI覆盖率验证等高级示例,请查看[references/advanced-examples.md](references/advanced-examples.md)。

Commands Reference

命令参考

See references/commands-and-troubleshooting.md for complete command reference and troubleshooting guide.
完整的命令参考与故障排除指南请查看references/commands-and-troubleshooting.md

See Also

相关链接