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-based 測試的集合。

測試套件目標

對於這個外掛的初始版本,每個測試組件都有單一目標。這會導致測試組件、測試組件目標和匹配的 測試 任務之間產生 1:1:1 的關係。測試 任務的名稱來自組件名稱。外掛的未來迭代將允許根據其他屬性(例如特定 JDK/JRE 執行時間)定義多個目標。

每個測試組件都有一些組態,這些組態在組件中包含的所有測試中都是通用的

  • 測試架構

  • 來源

  • 相依性

在未來,可以在測試組件中指定其他屬性,這些屬性可能會影響選取來編譯和執行測試的工具鏈。

與目標關聯的 測試 任務從組件繼承其名稱。測試 任務的其他屬性是可以組態的。

測試組件類型

必須為每個測試組件指定一個類型。類型可用於在建置中將相關測試組件分組到多個 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 組態內建的 測試 組件。這個組件會自動建立,以提供向後相容性。您必須指定一個測試架構才能執行這些測試(例如 JUnit 4、JUnit Jupiter)。這個組件是唯一會自動存取生產來源的 實作 相依性的組件,所有其他組件都必須明確宣告這些相依性。
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 建置指令碼之外,您必須明確呼叫 DependencyAdderadd 的 getter。

    • 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 區塊中存取測試套件的組態,但測試套件所使用的組態的確切名稱應視為可能會變更的實作細節。

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