JVM 测试套件插件(插件 ID:jvm-test-suite)提供 DSL 和 API,用于将 JVM 基于项目的多个自动测试组建模为测试套件。测试套件旨在按其目的进行分组,并且可以具有单独的依赖项并使用不同的测试框架。

例如,此插件可用于定义一组集成测试,这些测试可能比单元测试运行时间长得多,并且具有不同的环境要求。

JVM 测试套件插件是一个 孵化 API,并且可能会在将来的版本中发生更改。

用法

此插件由 java 插件自动应用,但如果需要,也可以显式地额外应用。如果没有应用 JVM 语言插件,则无法使用此插件,因为它依赖于 java 插件的几个约定。

build.gradle.kts
plugins {
    java
    `jvm-test-suite`
}
build.gradle
plugins {
    id 'java'
    id 'jvm-test-suite'
}

该插件向项目添加以下对象

Java 插件一起使用时

  • 一个名为test的测试套件(类型:JvmTestSuite)。

  • 一个test SourceSet

  • test SourceSet 名称派生的几个配置:testImplementationtestCompileOnlytestRuntimeOnly

  • 一个由名为test的任务支持的单个测试套件目标。

test任务、SourceSet 和派生配置在名称和功能上与以前 Gradle 版本中使用的相同。

任务

JVM 测试套件插件向项目添加以下任务

testTest

依赖于:来自java插件的testClasses,以及生成测试运行时类路径的所有任务

使用为默认测试套件配置的框架运行测试。

通过testing扩展添加的每个测试套件将自动创建Test任务的附加实例。

配置

请参阅 API 文档中的TestingExtension 类。

术语和建模

JVM 测试套件插件引入了一些由新 API 支持的建模概念。以下是它们的定义。

测试套件

测试套件是基于 JVM 的测试的集合。

测试套件目标

对于此插件的初始版本,每个测试套件都有一个目标。这导致测试套件、测试套件目标和匹配的Test任务之间存在 1:1:1 的关系。Test任务的名称源自套件名称。该插件的未来迭代将允许根据其他属性(例如特定 JDK/JRE 运行时)定义多个目标。

每个测试套件都有一些配置,这些配置在套件中包含的所有测试中都是通用的

  • 测试框架

  • 来源

  • 依赖项

将来,可以在测试套件中指定其他属性,这些属性可能会影响选择用于编译和运行测试的工具链。

与目标关联的Test任务从套件继承其名称。Test任务的其他属性是可配置的。

测试套件类型

每个测试套件都必须分配一个类型。类型可用于在构建中的多个 Gradle 项目中对相关测试套件进行分组。

可以使用套件的测试类型属性配置测试套件的类型。类型在同一个 Gradle 项目中的所有测试套件中必须是唯一的。按照惯例,类型设置为测试套件的名称,转换为短横线分隔形式 - 除了内置测试套件,它使用值'unit-test'

常见值在TestSuiteType 中作为常量提供。

配置示例

以下是一些示例,用于说明测试套件的可配置性。

声明一个额外的测试套件

build.gradle.kts
testing {
    suites { (1)
        val test by getting(JvmTestSuite::class) { (2)
            useJUnitJupiter() (3)
        }

        register<JvmTestSuite>("integrationTest") { (4)
            dependencies {
                implementation(project()) (5)
            }

            targets { (6)
                all {
                    testTask.configure {
                        shouldRunAfter(test)
                    }
                }
            }
        }
    }
}

tasks.named("check") { (7)
    dependsOn(testing.suites.named("integrationTest"))
}
build.gradle
testing {
    suites { (1)
        test { (2)
            useJUnitJupiter() (3)
        }

        integrationTest(JvmTestSuite) { (4)
            dependencies {
                implementation project() (5)
            }

            targets { (6)
                all {
                    testTask.configure {
                        shouldRunAfter(test)
                    }
                }
            }
        }
    }
}

tasks.named('check') { (7)
    dependsOn(testing.suites.integrationTest)
}
1 为该项目配置所有测试套件。
2 配置内置的 test 套件。此套件是为向后兼容性而自动创建的。您**必须**指定要使用的测试框架才能运行这些测试(例如,JUnit 4、JUnit Jupiter)。此套件是唯一一个将自动访问生产源代码的 implementation 依赖项的套件,所有其他套件必须显式声明这些依赖项。
3 声明此测试套件使用 JUnit Jupiter。框架的依赖项会自动包含。即使需要 JUnit4,也始终需要显式配置内置的 test 套件。
4 定义一个名为 integrationTest 的新套件。请注意,除内置的 test 套件之外的所有套件在约定上都将按调用 useJUnitJupiter() 的方式工作。您**不必**在这些额外的套件上显式配置测试框架,除非您希望更改为另一个框架。
5 将对项目生产代码的依赖项添加到 integrationTest 套件目标。按照惯例,只有内置的 test 套件会自动依赖于项目的生产代码。
6 配置此套件的所有目标。按照惯例,测试套件目标与其他 Test 任务没有关系。此示例表明,速度较慢的集成测试套件目标应在 test 套件的所有目标完成后运行。
7 配置 check 任务以依赖于所有 integrationTest 目标。按照惯例,测试套件目标与 check 任务无关。

在上述配置的构建上调用 check 任务应显示类似于以下内容的输出

> Task :compileJava
> Task :processResources NO-SOURCE
> Task :classes
> Task :jar
> Task :compileTestJava
> Task :processTestResources NO-SOURCE
> Task :testClasses
> Task :test
> Task :compileIntegrationTestJava
> Task :processIntegrationTestResources NO-SOURCE
> Task :integrationTestClasses
> Task :integrationTest
> Task :check

BUILD SUCCESSFUL in 0s
6 actionable tasks: 6 executed

请注意,integrationTest 测试套件在 test 测试套件完成之前不会运行。

配置内置的 test 套件

build.gradle.kts
testing {
    suites {
        val test by getting(JvmTestSuite::class) {
            useTestNG() (1)

            targets {
                all {
                    testTask.configure { (2)
                        // set a system property for the test JVM(s)
                        systemProperty("some.prop", "value")
                        options { (3)
                            val options = this as TestNGOptions
                            options.preserveOrder = true
                        }
                    }
                }
            }
        }
    }
}
build.gradle
testing { (1)
    suites {
        test {
            useTestNG() (1)

            targets {
                all {
                    testTask.configure { (2)
                        // set a system property for the test JVM(s)
                        systemProperty 'some.prop', 'value'
                        options { (3)
                            preserveOrder = true
                        }
                    }
                }
            }
        }
    }
}
1 声明 test 测试套件使用 TestNG 测试框架。
2 延迟配置套件所有目标的测试任务;请注意 testTask 的返回类型是 TaskProvider<Test>
3 配置更详细的测试框架选项。options 将是 org.gradle.api.tasks.testing.TestFrameworkOptions 的子类,在本例中为 org.gradle.api.tasks.testing.testng.TestNGOptions

配置测试套件的依赖项

build.gradle.kts
testing {
    suites {
        val test by getting(JvmTestSuite::class) { (1)
            dependencies {
                // Note that this is equivalent to adding dependencies to testImplementation in the top-level dependencies block
                implementation("org.assertj:assertj-core:3.21.0") (2)
                annotationProcessor("com.google.auto.value:auto-value:1.9") (3)
            }
        }
    }
}
build.gradle
testing {
    suites {
        test { (1)
            dependencies {
                // Note that this is equivalent to adding dependencies to testImplementation in the top-level dependencies block
                implementation 'org.assertj:assertj-core:3.21.0' (2)
                annotationProcessor 'com.google.auto.value:auto-value:1.9' (3)
            }
        }
    }
}
1 配置内置的 test 测试套件。
2 将 assertj 库添加到测试的编译和运行时类路径中。测试套件中的 dependencies 块已经为该测试套件设置了作用域。无需了解配置的全局名称,测试套件具有一个一致的名称,您可以在此块中使用该名称来声明 implementationcompileOnlyruntimeOnlyannotationProcessor 依赖项。
3 将 Auto Value 注解处理器添加到套件的注解处理器类路径中,以便在编译测试时运行它。

配置测试套件的依赖项以引用项目输出

build.gradle.kts
dependencies {
    api("com.google.guava:guava:30.1.1-jre") (1)
    implementation("com.fasterxml.jackson.core:jackson-databind:2.13.3") (2)
}

testing {
    suites {
        val integrationTest by registering(JvmTestSuite::class) {
            dependencies {
                implementation(project()) (3)
            }
        }
    }
}
build.gradle
dependencies {
    api 'com.google.guava:guava:30.1.1-jre' (1)
    implementation 'com.fasterxml.jackson.core:jackson-databind:2.13.3' (2)
}

testing {
    suites {
        integrationTest(JvmTestSuite) {
            dependencies {
                implementation project() (3)
            }
        }
    }
}
1 添加一个生产依赖项,该依赖项用作库的公共 API 的一部分。
2 添加一个生产依赖项,该依赖项仅在内部使用,并且不作为此项目的公共类的一部分公开。
3 配置一个新的测试套件,该套件将 project() 依赖项添加到套件的编译和运行时类路径中。此依赖项提供对项目输出以及在其 apicompileOnlyApi 配置中声明的任何依赖项的访问。

配置测试套件的源目录

build.gradle.kts
testing {
    suites {
        val integrationTest by registering(JvmTestSuite::class) { (1)
            sources { (2)
                java { (3)
                    setSrcDirs(listOf("src/it/java")) (4)
                }
            }
        }
    }
}
build.gradle
testing {
    suites {
        integrationTest(JvmTestSuite) { (1)
            sources { (2)
                java { (3)
                    srcDirs = ['src/it/java'] (4)
                }
            }
        }
    }
}
1 声明并配置一个名为 integrationTest 的套件。SourceSet 和合成的 Test 任务将基于此名称。
2 配置测试套件的 sources
3 配置测试套件的 java SourceDirectorySet。
4 覆盖 srcDirs 属性,将传统的 src/integrationTest/java 位置替换为 src/it/java

配置测试套件的类型

build.gradle.kts
testing {
    suites {
        val secondaryTest by registering(JvmTestSuite::class) {
            testType = TestSuiteType.INTEGRATION_TEST (1)
        }
    }
}
build.gradle
testing {
    suites {
        secondaryTest(JvmTestSuite) {
            testType = TestSuiteType.INTEGRATION_TEST (1)
        }
    }
}
1 此套件默认情况下将使用 'secondary-test' 类型值。这明确地将类型设置为 'integration-test'。

配置测试套件的 Test 任务

build.gradle.kts
testing {
    suites {
        val integrationTest by getting(JvmTestSuite::class) {
            targets {
                all { (1)
                    testTask.configure {
                        setMaxHeapSize("512m") (2)
                    }
                }
            }
        }
    }
}
build.gradle
testing {
    suites {
        integrationTest {
            targets {
                all { (1)
                    testTask.configure {
                        maxHeapSize = '512m' (2)
                    }
                }
            }
        }
    }
}
1 配置通过声明同名套件创建的 integrationTest 任务。
2 配置 Test 任务属性。

与测试套件目标关联的 Test 任务也可以通过名称直接配置。无需通过测试套件 DSL 配置。

在多个测试套件之间共享配置

有几种方法可以在多个测试套件之间共享配置,以避免依赖项或其他配置的重复。每种方法都是灵活性和使用声明式与命令式配置风格之间的权衡。

  1. 使用 suites 容器上的 configureEach 方法以相同的方式配置每个测试套件。

  2. 使用 withTypematchingconfigureEach 一起过滤测试套件并配置其中的一部分。

  3. 将配置块提取到局部变量中,并仅将其应用于所需的测试套件。

方法 1:使用 configureEach

这是在所有测试套件之间共享配置的最直接方法。配置将应用于每个测试套件。

build.gradle.kts
testing {
    suites {
        withType<JvmTestSuite> { (1)
            useJUnitJupiter()
            dependencies { (2)
                implementation("org.mockito:mockito-junit-jupiter:4.6.1")
            }
        }
        (3)
        val integrationTest by registering(JvmTestSuite::class)
        val functionalTest by registering(JvmTestSuite::class) {
            dependencies { (4)
                implementation("org.apache.commons:commons-lang3:3.11")
            }
        }
    }
}
build.gradle
testing {
    suites {
        configureEach { (1)
            useJUnitJupiter()
            dependencies { (2)
                implementation('org.mockito:mockito-junit-jupiter:4.6.1')
            }
        }
        (3)
        integrationTest(JvmTestSuite)
        functionalTest(JvmTestSuite) {
            dependencies { (4)
                implementation('org.apache.commons:commons-lang3:3.11')
            }
        }
    }
}
1 配置每个 JVM 测试套件
2 提供所有测试套件共享的依赖项
3 定义在创建时将被配置的额外测试套件
4 添加特定于 functionalTest 测试套件的依赖项

方法 2:使用 withTypematchingconfigureEach

此方法添加了过滤命令,仅将配置应用于测试套件的子集,如名称所标识。

build.gradle.kts
testing {
    suites {
        withType(JvmTestSuite::class).matching { it.name in listOf("test", "integrationTest") }.configureEach { (1)
            useJUnitJupiter()
            dependencies {
                implementation("org.mockito:mockito-junit-jupiter:4.6.1")
            }
        }
        val integrationTest by registering(JvmTestSuite::class)
        val functionalTest by registering(JvmTestSuite::class) {
            useJUnit() (2)
            dependencies { (3)
                implementation("org.apache.commons:commons-lang3:3.11")
            }
        }
    }
}
build.gradle
testing {
    suites {
        withType(JvmTestSuite).matching { it.name in ['test', 'integrationTest'] }.configureEach { (1)
            useJUnitJupiter()
            dependencies {
                implementation('org.mockito:mockito-junit-jupiter:4.6.1')
            }
        }

        integrationTest(JvmTestSuite)
        functionalTest(JvmTestSuite) {
            useJUnit() (2)
            dependencies { (3)
                implementation('org.apache.commons:commons-lang3:3.11')
            }
        }
    }
}
1 配置与给定条件匹配的每个 JVM 测试套件
2 functionalTest 测试套件使用不同的测试框架
3 添加特定于 functionalTest 测试套件的依赖项

方法 3:提取自定义配置块

此方法最灵活,但也最命令式。

build.gradle.kts
testing {
    suites {
        val applyMockito = { suite: JvmTestSuite -> (1)
            suite.useJUnitJupiter()
            suite.dependencies {
                implementation("org.mockito:mockito-junit-jupiter:4.6.1")
            }
        }

        /* This is the equivalent of:
            val test by getting(JvmTestSuite::class) {
                applyMockito(this)
            }
         */
        val test by getting(JvmTestSuite::class, applyMockito)  (2)

        /* This is the equivalent of:
            val integrationTest by registering(JvmTestSuite::class)
            applyMockito(integrationTest.get())
         */
        val integrationTest by registering(JvmTestSuite::class, applyMockito) (3)

        val functionalTest by registering(JvmTestSuite::class) {
            useJUnit()
            dependencies {
                implementation("org.apache.commons:commons-lang3:3.11")
            }
        }
    }
}
build.gradle
testing {
    suites {
        def applyMockito = { suite -> (1)
            suite.useJUnitJupiter()
            suite.dependencies {
                implementation('org.mockito:mockito-junit-jupiter:4.6.1')
            }
        }

        /* This is the equivalent of:
            test {
                applyMockito(this)
            }
         */
        test(applyMockito) (2)

        /* This is the equivalent of:
            integrationTest(JvmTestSuite)
            applyMockito(integrationTest)
         */
        integrationTest(JvmTestSuite, applyMockito) (3)

        functionalTest(JvmTestSuite) {
            useJUnit()
            dependencies {
                implementation('org.apache.commons:commons-lang3:3.11')
            }
        }
    }
}
1 定义一个闭包,它接受一个 JvmTestSuite 参数并对其进行配置
2 将闭包应用于测试套件,使用默认(test)测试套件
3 在测试套件声明之外将配置闭包应用于测试套件的替代方法,使用 integrationTest 测试套件

测试套件 dependencies 块与顶层 dependencies 块之间的区别

Gradle 7.6 更改了测试套件的 dependencies 块的 API,使其类型更强。

每个依赖项范围都返回一个 DependencyAdder,它提供强类型方法来添加和配置依赖项。

还有一个 DependencyFactory,它具有用于从不同表示法创建新依赖项的工厂方法。可以使用这些工厂方法延迟创建依赖项,如下所示。

此 API 与顶层 dependencies 块的不同之处在于以下方面

  • 依赖项必须使用StringDependency实例、FileCollectionDependencyProviderMinimalExternalModuleDependencyProviderConvertible声明。

  • 在 Gradle 构建脚本之外,您必须显式调用DependencyAdder的 getter 和add

    • dependencies.add("implementation", x) 变为 getImplementation().add(x)

  • 您不能使用 Kotlin 和 Java 中的Map 符号声明依赖项。在 Kotlin 和 Java 中使用多参数方法代替。

    • Kotlin:compileOnly(mapOf("group" to "foo", "name" to "bar")) 变为 compileOnly(module(group = "foo", name = "bar"))

    • Java:compileOnly(Map.of("group", "foo", "name", "bar")) 变为 getCompileOnly().add(module("foo", "bar", null))

  • 您不能使用Project实例添加依赖项。您必须先将其转换为ProjectDependency

  • 您不能直接添加版本目录包。相反,请在每个配置上使用bundle方法。

    • Kotlin 和 Groovy:implementation(libs.bundles.testing) 变为 implementation.bundle(libs.bundles.testing)

  • 您不能直接使用非Dependency类型的提供者。相反,请使用DependencyFactory将它们映射到Dependency

    • Kotlin 和 Groovy:implementation(myStringProvider) 变为 implementation(myStringProvider.map { dependencyFactory.create(it) })

    • Java:implementation(myStringProvider) 变为 getImplementation().add(myStringProvider.map(getDependencyFactory()::create)

测试套件中的dependencies块可能无法提供与顶级dependencies相同的访问方法。要访问这些方法,您需要使用顶级dependencies块。

通过测试套件自己的dependencies块添加依赖项是首选和推荐的方式。您也可以在创建测试套件后在顶级dependencies块中访问测试套件的配置,但测试套件使用的配置的具体名称应被视为可能会更改的实现细节。

JvmTestSuite 和 Test 任务类型上类似方法的差异

JvmTestSuite 实例具有useJUnit()useJUnitJupiter() 方法,它们在名称上与 Test 任务类型上的方法类似:useJUnit()useJUnitPlatform()。但是,存在重要的差异。

Test 任务上的方法不同,JvmTestSuite 方法执行了两个额外的配置步骤。

  1. JvmTestSuite#useJUnit()#useJUnitJupiter() 和其他特定于框架的方法会自动将相关的测试框架库应用到套件目标的编译和运行时类路径。请注意,像 JvmTestSuite#useJUnit(String)#useJUnitJupiter(String) 这样的重载方法允许您提供框架依赖项的特定版本。

  2. JvmTestSuite#useJUnit()#useJUnitJupiter() 会自动配置套件目标的 Test 任务,以使用指定的框架执行。

上述第一个配置示例 配置内置的 test 套件 展示了这种行为。

请注意,与 Test 任务不同,上述 JvmTestSuite 上的方法不接受用于配置框架的闭包或操作。这些框架配置选项可以在各个目标上设置。

输出变体

每个测试套件都会创建一个包含其测试执行结果的输出变体。这些变体旨在供 测试报告聚合插件 使用。

属性将类似于以下内容。用户可配置的属性在示例下方突出显示。

outgoingVariants 任务输出
--------------------------------------------------
Variant testResultsElementsForTest (i)
--------------------------------------------------
Description = Directory containing binary results of running tests for the test Test Suite's test target.

Capabilities
    - org.gradle.sample:list:1.0.2 (default capability)
Attributes
    - org.gradle.category              = verification
    - org.gradle.testsuite.name        = test           (1)
    - org.gradle.testsuite.target.name = test           (2)
    - org.gradle.testsuite.type        = unit-test      (3)
    - org.gradle.verificationtype      = test-results

Artifacts
    - build/test-results/test/binary (artifactType = directory)
1 TestSuiteName 属性;值来自 JvmTestSuite#getName()
2 TestSuiteTargetName 属性;值来自 JvmTestSuiteTarget#getName()
3 TestSuiteType 属性;值来自 JvmTestSuite#getTestType()