JVM 测试套件插件
JVM 测试套件插件(插件 ID: jvm-test-suite
)提供 DSL 和 API,用于在基于 JVM 的项目中将多组自动化测试建模为测试套件。测试套件旨在按其目的进行分组,并且可以拥有独立的依赖项并使用不同的测试框架。
例如,此插件可用于定义一组集成测试 (Integration Tests),其运行时间可能比单元测试长得多,并且具有不同的环境要求。
JVM 测试套件插件是一个 孵化中 的 API,在未来版本中可能会发生变化。 |
用法
此插件由 java
插件自动应用,但如果需要,也可以显式应用。如果没有应用 JVM 语言插件,此插件将无法使用,因为它依赖于 java
插件的多个约定。
plugins {
java
`jvm-test-suite`
}
plugins {
id 'java'
id 'jvm-test-suite'
}
该插件向项目添加以下对象
-
一个
testing
扩展(类型:TestingExtension),用于配置测试套件。
与 Java 插件 一起使用时
-
一个名为
test
的测试套件(类型:JvmTestSuite)。 -
一个
test
SourceSet。 -
几个派生自
test
SourceSet 名称的配置:testImplementation
,testCompileOnly
,testRuntimeOnly
-
一个由名为
test
的任务支持的单个测试套件目标。
test
任务、SourceSet 和派生配置在名称和功能上与之前 Gradle 版本中使用的相同。
配置
请参阅 API 文档中的 TestingExtension 类。
声明一个附加测试套件
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"))
}
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
测试套件
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
}
}
}
}
}
}
}
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 。 |
配置测试套件的依赖项
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)
}
}
}
}
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 , runtimeOnly 和 annotationProcessor 依赖项。 |
3 | 将 Auto Value 注解处理器添加到套件的注解处理器类路径,以便在编译测试时运行它。 |
配置测试套件的依赖项以引用项目输出
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)
}
}
}
}
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() 依赖项添加到套件的编译和运行时类路径。此依赖项提供对项目输出以及在其 api 和 compileOnlyApi 配置上声明的任何依赖项的访问。 |
配置测试套件的源目录
testing {
suites {
val integrationTest by registering(JvmTestSuite::class) { (1)
sources { (2)
java { (3)
setSrcDirs(listOf("src/it/java")) (4)
}
}
}
}
}
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
任务
testing {
suites {
val integrationTest by getting(JvmTestSuite::class) {
targets {
all { (1)
testTask.configure {
setMaxHeapSize("512m") (2)
}
}
}
}
}
}
testing {
suites {
integrationTest {
targets {
all { (1)
testTask.configure {
maxHeapSize = '512m' (2)
}
}
}
}
}
}
1 | 配置通过声明同名套件而创建的 integrationTest 任务。 |
2 | 配置 Test 任务属性。 |
与测试套件目标关联的 Test
任务也可以直接按名称配置。通过测试套件 DSL 配置不是必需的。
在多个测试套件之间共享配置
有几种方法可以在多个测试套件之间共享配置,以避免依赖项或其他配置的重复。每种方法都在灵活性与声明式和命令式配置风格的使用之间进行权衡。
-
在
suites
容器上使用configureEach
方法,以相同的方式配置每个测试套件。 -
将
withType
和matching
与configureEach
一起使用,以过滤测试套件并配置其中的一部分。 -
将配置块提取到本地变量,并仅将其应用于所需的测试套件。
方法 1:使用 configureEach
这是跨每个测试套件共享配置最直接的方式。配置将应用于每个测试套件。
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")
}
}
}
}
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
, matching
和 configureEach
此方法添加过滤命令,仅将配置应用于部分测试套件,这些套件由名称标识。
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")
}
}
}
}
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:提取自定义配置块
此方法最灵活,但也是最命令式的。
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")
}
}
}
}
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 测试套件 |
JvmTestSuite 和 Test 任务类型上相似方法之间的差异
JvmTestSuite 的实例具有方法 useJUnit() 和 useJUnitJupiter(),这些方法在名称上与 Test 任务类型上的方法相似:useJUnit() 和 useJUnitPlatform()。然而,它们之间存在重要差异。
与 Test 任务上的方法不同,JvmTestSuite 方法会执行另外两项配置
-
JvmTestSuite#useJUnit(), #useJUnitJupiter() 以及其他特定于框架的方法会自动将相关的测试框架库应用于套件目标的编译和运行时类路径。注意像 JvmTestSuite#useJUnit(String) 和 #useJUnitJupiter(String) 这样的重载方法允许您提供特定版本的框架依赖项。
-
JvmTestSuite#useJUnit() 和 #useJUnitJupiter() 会自动配置套件目标的 Test 任务使用指定的框架执行。
此行为的一个示例显示在上面的第一个配置示例中:配置内置的 test
测试套件。
与 Test 任务不同,JvmTestSuite 上述方法不接受用于配置框架的闭包或 Action。这些框架配置选项可以在单个目标上设置。 |
输出变体
每个测试套件创建一个包含其测试执行结果的输出变体。这些变体旨在供 测试报告聚合插件 使用。
属性将类似于以下内容。用户可配置的属性在示例下方突出显示。
--------------------------------------------------
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()。 |