使用 TestKit 測試建置邏輯
Gradle TestKit(又稱 TestKit)是一個程式庫,有助於測試 Gradle 外掛和一般建置邏輯。目前,它專注於功能測試。也就是說,透過將建置邏輯作為程式執行建置的一部分來進行測試。隨著時間推移,TestKit 可能會擴充以利進行其他類型的測試。
用法
若要使用 TestKit,請在您的外掛建置中包含下列內容
dependencies {
testImplementation(gradleTestKit())
}
dependencies {
testImplementation gradleTestKit()
}
gradleTestKit()
包含 TestKit 的類別,以及 Gradle Tooling API 客户端。它不包含 JUnit、TestNG 或任何其他測試執行架構的版本。此類相依性必須明確宣告。
dependencies {
testImplementation("org.junit.jupiter:junit-jupiter:5.7.1")
testRuntimeOnly("org.junit.platform:junit-platform-launcher")
}
tasks.named<Test>("test") {
useJUnitPlatform()
}
dependencies {
testImplementation("org.junit.jupiter:junit-jupiter:5.7.1")
testRuntimeOnly("org.junit.platform:junit-platform-launcher")
}
tasks.named('test', Test) {
useJUnitPlatform()
}
使用 Gradle 執行器進行功能測試
GradleRunner 有助於以程式方式執行 Gradle 建置,並檢查結果。
可以建立一個人工建置(例如透過程式或範本),來執行「測試中的邏輯」。然後可以執行建置,可能使用各種方式(例如不同任務和引數的組合)。然後可以透過斷言下列事項,可能組合使用,來驗證邏輯的正確性
-
建置的輸出;
-
建置的記錄(即主控台輸出);
-
建置執行的任務組及其結果(例如,FAILED、UP-TO-DATE 等)。
建立並設定執行器執行個體後,可以透過 GradleRunner.build() 或 GradleRunner.buildAndFail() 方法執行建置,具體取決於預期的結果。
以下說明在 Java JUnit 測試中使用 Gradle 執行器的用法
範例:與 Java 和 JUnit 搭配使用 GradleRunner
import org.gradle.testkit.runner.BuildResult;
import org.gradle.testkit.runner.GradleRunner;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.io.TempDir;
import java.io.BufferedWriter;
import java.io.File;
import java.io.FileWriter;
import java.io.IOException;
import static org.gradle.testkit.runner.TaskOutcome.SUCCESS;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertTrue;
public class BuildLogicFunctionalTest {
@TempDir File testProjectDir;
private File settingsFile;
private File buildFile;
@BeforeEach
public void setup() {
settingsFile = new File(testProjectDir, "settings.gradle");
buildFile = new File(testProjectDir, "build.gradle");
}
@Test
public void testHelloWorldTask() throws IOException {
writeFile(settingsFile, "rootProject.name = 'hello-world'");
String buildFileContent = "task helloWorld {" +
" doLast {" +
" println 'Hello world!'" +
" }" +
"}";
writeFile(buildFile, buildFileContent);
BuildResult result = GradleRunner.create()
.withProjectDir(testProjectDir)
.withArguments("helloWorld")
.build();
assertTrue(result.getOutput().contains("Hello world!"));
assertEquals(SUCCESS, result.task(":helloWorld").getOutcome());
}
private void writeFile(File destination, String content) throws IOException {
BufferedWriter output = null;
try {
output = new BufferedWriter(new FileWriter(destination));
output.write(content);
} finally {
if (output != null) {
output.close();
}
}
}
}
可以使用任何測試執行架構。
由於 Gradle 建置指令碼也可以用 Groovy 程式語言撰寫,因此通常會選擇用 Groovy 撰寫 Gradle 功能測試。此外,建議使用(基於 Groovy)Spock 測試執行架構,因為它提供了許多比使用 JUnit 更吸引人的功能。
以下說明在 Groovy Spock 測試中使用 Gradle 執行器的用法
範例:與 Groovy 和 Spock 搭配使用 GradleRunner
import org.gradle.testkit.runner.GradleRunner
import static org.gradle.testkit.runner.TaskOutcome.*
import spock.lang.TempDir
import spock.lang.Specification
class BuildLogicFunctionalTest extends Specification {
@TempDir File testProjectDir
File settingsFile
File buildFile
def setup() {
settingsFile = new File(testProjectDir, 'settings.gradle')
buildFile = new File(testProjectDir, 'build.gradle')
}
def "hello world task prints hello world"() {
given:
settingsFile << "rootProject.name = 'hello-world'"
buildFile << """
task helloWorld {
doLast {
println 'Hello world!'
}
}
"""
when:
def result = GradleRunner.create()
.withProjectDir(testProjectDir)
.withArguments('helloWorld')
.build()
then:
result.output.contains('Hello world!')
result.task(":helloWorld").outcome == SUCCESS
}
}
實作任何本質上較複雜的客製化建置邏輯(例如外掛和任務類型)為獨立專案中的外部類別,是一種常見的做法。這種方法背後的主要驅動力是將編譯的程式碼打包成 JAR 檔案,將其發佈到二進位儲存庫,並在各種專案中重複使用。
將測試中的外掛納入測試建置
GradleRunner 使用 工具 API 來執行建置。這表示建置會在一個獨立的程序中執行(也就是說,不是執行測試的同一個程序)。因此,測試建置不會與測試程序共用同一個類別路徑或類別載入器,而且受測程式碼不會隱含地提供給測試建置。
GradleRunner 支援與工具 API 相同範圍的 Gradle 版本。支援的版本定義在 相容性矩陣 中。 使用舊版 Gradle 版本的建置可能會正常運作,但無法保證。 |
從版本 2.13 開始,Gradle 提供一個慣例機制,將受測程式碼注入測試建置中。
使用 Java Gradle 外掛開發外掛自動注入
Java Gradle 外掛開發外掛 可用於協助開發 Gradle 外掛。從 Gradle 版本 2.13 開始,此外掛提供與 TestKit 的直接整合。套用至專案時,此外掛會自動將 gradleTestKit()
相依性新增至 testApi
組態。此外,它會自動產生受測程式碼的類別路徑,並透過 GradleRunner.withPluginClasspath() 注入它,供使用者建立的任何 GradleRunner
執行個體使用。請務必注意,此機制目前僅適用於使用 外掛 DSL 套用受測外掛的情況。如果 目標 Gradle 版本 早於 2.8,則不會執行自動外掛類別路徑注入。
此外掛使用下列慣例套用 TestKit 相依性並注入類別路徑
-
包含受測程式碼的來源組:
sourceSets.main
-
用於注入外掛類別路徑的來源組:
sourceSets.test
可以使用 GradlePluginDevelopmentExtension 類別重新設定這些慣例中的任何一個。
以下基於 Groovy 的範例示範如何使用 Java Gradle 外掛開發外掛套用的標準慣例,自動注入外掛類別路徑。
plugins {
groovy
`java-gradle-plugin`
}
dependencies {
testImplementation("org.spockframework:spock-core:2.2-groovy-3.0") {
exclude(group = "org.codehaus.groovy")
}
testRuntimeOnly("org.junit.platform:junit-platform-launcher")
}
plugins {
id 'groovy'
id 'java-gradle-plugin'
}
dependencies {
testImplementation('org.spockframework:spock-core:2.2-groovy-3.0') {
exclude group: 'org.codehaus.groovy'
}
testRuntimeOnly 'org.junit.platform:junit-platform-launcher'
}
範例:自動將受測程式碼類別注入測試建置中
def "hello world task prints hello world"() {
given:
settingsFile << "rootProject.name = 'hello-world'"
buildFile << """
plugins {
id 'org.gradle.sample.helloworld'
}
"""
when:
def result = GradleRunner.create()
.withProjectDir(testProjectDir)
.withArguments('helloWorld')
.withPluginClasspath()
.build()
then:
result.output.contains('Hello world!')
result.task(":helloWorld").outcome == SUCCESS
}
以下建置指令碼示範如何重新設定 Java Gradle Plugin Development 外掛提供的慣例,以供專案使用自訂的 Test
來源組。
新的組態 DSL 可用於建模以下 functionalTest 套件,可透過孵化中的 JVM Test Suite 外掛取得。
|
plugins {
groovy
`java-gradle-plugin`
}
val functionalTest = sourceSets.create("functionalTest")
val functionalTestTask = tasks.register<Test>("functionalTest") {
group = "verification"
testClassesDirs = functionalTest.output.classesDirs
classpath = functionalTest.runtimeClasspath
useJUnitPlatform()
}
tasks.check {
dependsOn(functionalTestTask)
}
gradlePlugin {
testSourceSets(functionalTest)
}
dependencies {
"functionalTestImplementation"("org.spockframework:spock-core:2.2-groovy-3.0") {
exclude(group = "org.codehaus.groovy")
}
"functionalTestRuntimeOnly"("org.junit.platform:junit-platform-launcher")
}
plugins {
id 'groovy'
id 'java-gradle-plugin'
}
def functionalTest = sourceSets.create('functionalTest')
def functionalTestTask = tasks.register('functionalTest', Test) {
group = 'verification'
testClassesDirs = sourceSets.functionalTest.output.classesDirs
classpath = sourceSets.functionalTest.runtimeClasspath
useJUnitPlatform()
}
tasks.named("check") {
dependsOn functionalTestTask
}
gradlePlugin {
testSourceSets sourceSets.functionalTest
}
dependencies {
functionalTestImplementation('org.spockframework:spock-core:2.2-groovy-3.0') {
exclude group: 'org.codehaus.groovy'
}
functionalTestRuntimeOnly 'org.junit.platform:junit-platform-launcher'
}
控制建置環境
執行器會在隔離的環境中執行測試建置,方法是在 JVM 暫存目錄內的目錄中指定專用的「工作目錄」(例如由 java.io.tmpdir
系統屬性指定的位址,通常為 /tmp
)。預設 Gradle 使用者主目錄 (例如 ~/.gradle/gradle.properties
) 中的任何組態都不會用於測試執行。TestKit 沒有提供機制可精細控制環境的所有面向 (例如 JDK)。TestKit 的未來版本將提供更進階的組態選項。
TestKit 使用專用的守護程序,這些程序會在測試執行後自動關閉。
執行器不會在建置後刪除專用的工作目錄。TestKit 提供兩種方法來指定定期清理的位置,例如專案的建置資料夾
-
org.gradle.testkit.dir
系統屬性; -
GradleRunner.withTestKitDir(file testKitDir)
方法。
用於測試的 Gradle 版本
Gradle 執行器需要 Gradle 發行版才能執行建置。TestKit 不依賴於 Gradle 的所有實作。
預設情況下,執行器會嘗試根據 GradleRunner
類別載入的位置尋找 Gradle 發行版。也就是說,預期類別是從 Gradle 發行版載入的,就像使用 gradleTestKit()
相依宣告時一樣。
當使用 runner 作為由 Gradle 執行的測試的一部分(例如執行外掛專案的 test
任務)時,runner 會使用與執行測試相同的發行版。當使用 runner 作為由 IDE 執行的測試的一部分時,將會使用匯入專案時使用的相同 Gradle 發行版。這表示外掛將實際上使用與建置時相同的 Gradle 版本進行測試。
或者,可以透過下列任何 GradleRunner
方法指定要使用的不同特定 Gradle 版本
這可以潛在用於跨 Gradle 版本測試建置邏輯。下列範例示範撰寫為 Groovy Spock 測試的跨版本相容性測試
範例:指定 Gradle 版本以執行測試
import org.gradle.testkit.runner.GradleRunner
import static org.gradle.testkit.runner.TaskOutcome.*
import spock.lang.TempDir
import spock.lang.Specification
class BuildLogicFunctionalTest extends Specification {
@TempDir File testProjectDir
File settingsFile
File buildFile
def setup() {
settingsFile = new File(testProjectDir, 'settings.gradle')
buildFile = new File(testProjectDir, 'build.gradle')
}
def "can execute hello world task with Gradle version #gradleVersion"() {
given:
buildFile << """
task helloWorld {
doLast {
logger.quiet 'Hello world!'
}
}
"""
settingsFile << ""
when:
def result = GradleRunner.create()
.withGradleVersion(gradleVersion)
.withProjectDir(testProjectDir)
.withArguments('helloWorld')
.build()
then:
result.output.contains('Hello world!')
result.task(":helloWorld").outcome == SUCCESS
where:
gradleVersion << ['5.0', '6.0.1']
}
}
使用不同 Gradle 版本進行測試時的功能支援
可以使用 GradleRunner 執行 Gradle 1.0 及後續版本的建置。但是,某些 runner 功能在較早版本中不受支援。在這種情況下,runner 會在嘗試使用該功能時擲回例外狀況。
下表列出對所使用的 Gradle 版本敏感的功能。
功能 | 最低版本 | 說明 |
---|---|---|
檢查已執行的任務 |
2.5 |
使用 BuildResult.getTasks() 和類似方法檢查已執行的任務。 |
2.8 |
透過 GradleRunner.withPluginClasspath(java.lang.Iterable) 注入測試中的程式碼。 |
|
2.9 |
使用 BuildResult.getOutput() 檢查在偵錯模式下執行的建置文字輸出。 |
|
2.13 |
透過套用 Java Gradle Plugin Development 外掛,透過 GradleRunner.withPluginClasspath() 自動注入測試中的程式碼。 |
|
設定建置要使用的環境變數。 |
3.5 |
Gradle Tooling API 僅支援在後續版本中設定環境變數。 |
偵錯建置邏輯
執行器使用 Tooling API 來執行建置。這表示建置會在個別的處理序中執行 (也就是與執行測試的處理序不同)。因此,在偵錯模式下執行您的測試並不會讓您偵錯建置邏輯,就像您預期的。在您的 IDE 中設定的任何中斷點都不會觸發測試建置所執行的程式碼。
TestKit 提供兩種不同的方式來啟用偵錯模式
-
將「
org.gradle.testkit.debug
」系統屬性設定為true
,適用於使用GradleRunner
的 JVM (也就是不是使用執行器執行的建置);
當需要啟用偵錯支援,但又不想臨時變更執行器設定時,可以使用系統屬性方法。大多數 IDE 都提供設定 JVM 系統屬性以執行測試的功能,可以使用此功能設定此系統屬性。
使用建置快取進行測試
若要在測試中啟用 建置快取,您可以將 --build-cache
參數傳遞給 GradleRunner,或使用 啟用建置快取 中所述的其他方法之一。然後,當您的外掛程式自訂任務快取時,您可以檢查任務結果 TaskOutcome.FROM_CACHE。此結果僅對 Gradle 3.5 及更新版本有效。
範例:測試可快取的任務
def "cacheableTask is loaded from cache"() {
given:
buildFile << """
plugins {
id 'org.gradle.sample.helloworld'
}
"""
when:
def result = runner()
.withArguments( '--build-cache', 'cacheableTask')
.build()
then:
result.task(":cacheableTask").outcome == SUCCESS
when:
new File(testProjectDir, 'build').deleteDir()
result = runner()
.withArguments( '--build-cache', 'cacheableTask')
.build()
then:
result.task(":cacheableTask").outcome == FROM_CACHE
}
請注意,TestKit 會在測試之間重複使用 Gradle 使用者主目錄 (請參閱 GradleRunner.withTestKitDir(java.io.File)),其中包含本機建置快取的預設位置。對於使用建置快取的測試,建置快取目錄應該在測試之間清除。最簡單的方法是將本機建置快取設定為使用暫存目錄。
範例:清除測試之間的建置快取
@TempDir File testProjectDir
File buildFile
File localBuildCacheDirectory
def setup() {
localBuildCacheDirectory = new File(testProjectDir, 'local-cache')
buildFile = new File(testProjectDir,'settings.gradle') << """
buildCache {
local {
directory '${localBuildCacheDirectory.toURI()}'
}
}
"""
buildFile = new File(testProjectDir,'build.gradle')
}