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

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

JVM 测试套件插件是一个 孵化中 的 API,在未来版本中可能会发生变化。

用法

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

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

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

  • 一个 testing 扩展(类型:TestingExtension),用于配置测试套件。

Java 插件 一起使用时

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

  • 一个 test SourceSet

  • 几个派生自 test SourceSet 名称的配置:testImplementation, testCompileOnly, testRuntimeOnly

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

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

任务

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

testTest

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

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

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

配置

请参阅 API 文档中的 TestingExtension 类。

术语和建模

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

测试套件

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

测试套件目标

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

每个测试套件都有一些对套件中包含的所有测试通用的配置

  • 测试框架

  • 源文件

  • 依赖项

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

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

配置示例

以下是几个示例,用以说明测试套件的可配置性。

声明一个附加测试套件

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 块已经为该测试套件设定了范围。不必知道全局配置名称,测试套件在这个块中有个一致的名称,用于声明 implementation, compileOnly, runtimeOnlyannotationProcessor 依赖项。
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

配置测试套件的 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:使用 withType, matchingconfigureEach

此方法添加过滤命令,仅将配置应用于部分测试套件,这些套件由名称标识。

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 测试套件

JvmTestSuiteTest 任务类型上相似方法之间的差异

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 上述方法不接受用于配置框架的闭包或 Action。这些框架配置选项可以在单个目标上设置。

输出变体

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

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

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.verificationtype = test-results

Artifacts
    - build/test-results/test/binary (artifactType = directory)
1 TestSuiteName 属性;值派生自 TestSuite#getName()