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'
}

此外掛程式將以下物件新增至專案

  • 用於配置測試套件的 testing 擴充功能(類型:TestingExtension)。

當與 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 任務的其他屬性是可配置的。

配置範例

以下是一些範例,用於說明測試套件的可配置性。

宣告額外的測試套件

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

為測試套件配置 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 測試套件

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 上的上述方法不接受用於配置框架的閉包或動作。這些框架配置選項可以在個別目標上設定。

輸出變體

每個測試套件都會建立一個輸出變體,其中包含其測試執行結果。這些變體旨在供 測試報告彙總外掛程式 使用。

屬性將類似於以下內容。使用者可配置的屬性在範例下方突出顯示。

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()