Gradle 專案

先決條件

要將 Spring Cloud Contract Verifier 與 WireMock 一起使用,必須使用 Gradle 或 Maven 外掛。

如果要在專案中使用 Spock,必須單獨新增 spock-corespock-spring 模組。有關更多資訊,請參閱 Spock 的文件

新增帶有依賴的 Gradle 外掛

要新增帶有依賴的 Gradle 外掛,可以使用類似以下程式碼

外掛 DSL GA 版本
// build.gradle
plugins {
  id "groovy"
  // this will work only for GA versions of Spring Cloud Contract
  id "org.springframework.cloud.contract" version "$\{GAVerifierVersion}"
}

dependencyManagement {
	imports {
		mavenBom "org.springframework.cloud:spring-cloud-contract-dependencies:$\{GAVerifierVersion}"
	}
}

dependencies {
	testImplementation "org.apache.groovy:groovy-all:$\{groovyVersion}"
	// example with adding Spock core and Spock Spring
	testImplementation "org.spockframework:spock-core:$\{spockVersion}"
	testImplementation "org.spockframework:spock-spring:$\{spockVersion}"
	testImplementation 'org.springframework.cloud:spring-cloud-starter-contract-verifier'
}
外掛 DSL 非 GA 版本
// settings.gradle
pluginManagement {
	plugins {
		id "org.springframework.cloud.contract" version "$\{verifierVersion}"
	}
    repositories {
        // to pick from local .m2
        mavenLocal()
        // for snapshots
        maven { url "https://repo.spring.io/snapshot" }
        // for milestones
        maven { url "https://repo.spring.io/milestone" }
        // for GA versions
        gradlePluginPortal()
    }
}

// build.gradle
plugins {
  id "groovy"
  id "org.springframework.cloud.contract"
}

dependencyManagement {
	imports {
		mavenBom "org.springframework.cloud:spring-cloud-contract-dependencies:$\{verifierVersion}"
	}
}

dependencies {
	testImplementation "org.apache.groovy:groovy-all:$\{groovyVersion}"
	// example with adding Spock core and Spock Spring
	testImplementation "org.spockframework:spock-core:$\{spockVersion}"
	testImplementation "org.spockframework:spock-spring:$\{spockVersion}"
	testImplementation 'org.springframework.cloud:spring-cloud-starter-contract-verifier'
}
傳統外掛應用
// build.gradle
buildscript {
	repositories {
		mavenCentral()
	}
	dependencies {
		classpath "org.springframework.boot:spring-boot-gradle-plugin:$\{springboot_version}"
		classpath "org.springframework.cloud:spring-cloud-contract-gradle-plugin:$\{verifier_version}"
        // here you can also pass additional dependencies such as Kotlin spec e.g.:
        // classpath "org.springframework.cloud:spring-cloud-contract-spec-kotlin:$\{verifier_version}"
	}
}

apply plugin: 'groovy'
apply plugin: 'org.springframework.cloud.contract'

dependencyManagement {
	imports {
		mavenBom "org.springframework.cloud:spring-cloud-contract-dependencies:$\{verifier_version}"
	}
}

dependencies {
	testImplementation "org.apache.groovy:groovy-all:$\{groovyVersion}"
	// example with adding Spock core and Spock Spring
	testImplementation "org.spockframework:spock-core:$\{spockVersion}"
	testImplementation "org.spockframework:spock-spring:$\{spockVersion}"
	testImplementation 'org.springframework.cloud:spring-cloud-starter-contract-verifier'
}

Gradle 和 Rest Assured 2.0

預設情況下,Rest Assured 3.x 已新增到類路徑中。但是,要使用 Rest Assured 2.x,可以新增它代替,如以下列表所示

buildscript {
	repositories {
		mavenCentral()
	}
	dependencies {
		classpath "org.springframework.boot:spring-boot-gradle-plugin:$\{springboot_version}"
		classpath "org.springframework.cloud:spring-cloud-contract-gradle-plugin:$\{verifier_version}"
	}
}

dependencies {
    // all dependencies
    // you can exclude rest-assured from spring-cloud-contract-verifier
    testCompile "com.jayway.restassured:rest-assured:2.5.0"
    testCompile "com.jayway.restassured:spring-mock-mvc:2.5.0"
}

這樣,外掛會自動檢測到類路徑中存在 Rest Assured 2.x,並相應地修改匯入。

Gradle 的快照版本

可以將額外的快照倉庫新增到 settings.gradle 中以使用快照版本,這些版本在每次成功構建後自動上傳。

新增存根

預設情況下,Spring Cloud Contract Verifier 會在 src/contractTest/resources/contracts 目錄中查詢存根。出於過渡目的,外掛也會在 src/test/resources/contracts 中查詢合約,但該目錄自 Spring Cloud Contract 3.0.0 起已棄用。

還應注意,使用這個新的 Gradle 源集時,您還應該將合約測試中使用的任何基類遷移到 src/contractTest/{language},其中 {language} 應根據需要替換為 Java 或 Groovy。

包含存根定義的目錄被視為類名,每個存根定義被視為一個測試。Spring Cloud Contract Verifier 假定它至少包含一層目錄,這些目錄將用作測試類名。如果存在多層巢狀目錄,除了最後一層之外的所有目錄都將用作包名。考慮以下結構

src/contractTest/resources/contracts/myservice/shouldCreateUser.groovy
src/contractTest/resources/contracts/myservice/shouldReturnUser.groovy

給定上述結構,Spring Cloud Contract Verifier 會建立一個名為 defaultBasePackage.MyService 的測試類,其中包含兩個方法

  • shouldCreateUser()

  • shouldReturnUser()

執行外掛

外掛將自身註冊到在 check 任務之前呼叫。如果希望它成為構建過程的一部分,則無需執行其他操作。如果只想生成測試,請呼叫 generateContractTests 任務。

預設設定

預設的 Gradle 外掛設定建立了構建的以下 Gradle 部分(虛擬碼)

contracts {
    testFramework ='JUNIT'
    testMode = 'MockMvc'
    generatedTestJavaSourcesDir = project.file("$\{project.buildDir}/generated-test-sources/contractTest/java")
    generatedTestGroovySourcesDir = project.file("$\{project.buildDir}/generated-test-sources/contractTest/groovy")
    generatedTestResourcesDir = project.file("$\{project.buildDir}/generated-test-resources/contracts")
    contractsDslDir = project.file("$\{project.projectDir}/src/contractTest/resources/contracts")
    basePackageForTests = 'org.springframework.cloud.verifier.tests'
    stubsOutputDir = project.file("$\{project.buildDir}/stubs")
    sourceSet = null
}

def verifierStubsJar = tasks.register(type: Jar, name: 'verifierStubsJar', dependsOn: 'generateClientStubs') {
    baseName = project.name
    classifier = contracts.stubsSuffix
    from contractVerifier.stubsOutputDir
}

def copyContracts = tasks.register(type: Copy, name: 'copyContracts') {
    from contracts.contractsDslDir
    into contracts.stubsOutputDir
}

verifierStubsJar.dependsOn copyContracts

配置外掛

要更改預設配置,可以向 Gradle 配置新增 contracts 程式碼片段,如以下列表所示

contracts {
	testMode = 'MockMvc'
	baseClassForTests = 'org.mycompany.tests'
	generatedTestJavaSourcesDir = project.file('src/generatedContract')
}

要從遠端源下載合約,可以根據需要使用以下程式碼片段

contracts {
    // If your contracts exist in a JAR archive published to a Maven repository
    contractDependency {
        stringNotation = ''
        // OR
        groupId = ''
        artifactId = ''
        version = ''
        classifier = ''
    }

    // If your contracts exist in a Git SCM repository
    contractRepository {
        repositoryUrl = ''
        // username = ''
        // password = ''
    }

    // controls the nested location to find the contracts in either the JAR or Git SCM source
    contractsPath = ''
}

由於我們使用了 Gradle 的 Jar 打包任務,因此您可能希望利用一些選項和功能來進一步擴充套件 verifierStubsJar 建立的內容。為此,您可以使用 Gradle 直接提供的原生機制來定製現有任務,如下所示

為了示例目的,我們希望向 verifierStubsJar 新增一個 git.properties 檔案。
verifierStubsJar {
    from("$\{buildDir}/resources/main/") {
        include("git.properties")
    }
}

還應注意,自 3.0.0 版本起,預設釋出已停用。因此,這意味著您可以建立任何命名的 jar 並像往常透過 Gradle 配置選項那樣釋出它。這意味著您可以按照自己的方式定製構建一個 jar 檔案,併發布它,以完全控制 jar 的佈局和內容。

配置選項

  • testMode: 定義驗收測試模式。預設模式為 MockMvc,基於 Spring 的 MockMvc。它也可以更改為 WebTestClient、JaxRsClient 或 Explicit(用於真實的 HTTP 呼叫)。

  • imports: 建立一個數組,其中包含應包含在生成的測試中的匯入(例如,['org.myorg.Matchers'])。預設情況下,它建立一個空陣列。

  • staticImports: 建立一個數組,其中包含應包含在生成的測試中的靜態匯入(例如,['org.myorg.Matchers.*'])。預設情況下,它建立一個空陣列。

  • basePackageForTests: 指定所有生成測試的基包。如果未設定,則從 baseClassForTests 的包和從 packageWithBaseClasses 中獲取值。如果這些值都未設定,則值設定為 org.springframework.cloud.contract.verifier.tests

  • baseClassForTests: 為所有生成的測試建立一個基類。預設情況下,如果您使用 Spock 類,則該類為 spock.lang.Specification

  • packageWithBaseClasses: 定義所有基類所在的包。此設定優先於 baseClassForTests

  • baseClassMappings: 顯式地將合約包對映到基類的完全限定名 (FQN)。此設定優先於 packageWithBaseClassesbaseClassForTests

  • ignoredFiles: 使用 Antmatcher 允許定義應跳過處理的存根檔案。預設情況下,它是一個空陣列。

  • contractsDslDir: 指定包含使用 GroovyDSL 編寫的合約的目錄。預設情況下,其值為 $projectDir/src/contractTest/resources/contracts

  • generatedTestSourcesDir: 指定應放置從 Groovy DSL 生成的測試的測試源目錄。(已棄用)

  • generatedTestJavaSourcesDir: 指定應放置從 Groovy DSL 生成的 Java/JUnit 測試的測試源目錄。預設情況下,其值為 $buildDir/generated-tes-sources/contractTest/java

  • generatedTestGroovySourcesDir: 指定應放置從 Groovy DSL 生成的 Groovy/Spock 測試的測試源目錄。預設情況下,其值為 $buildDir/generated-test-sources/contractTest/groovy

  • generatedTestResourcesDir: 指定應放置從 Groovy DSL 生成的測試所使用的資源的測試資源目錄。預設情況下,其值為 $buildDir/generated-test-resources/contractTest

  • stubsOutputDir: 指定應放置從 Groovy DSL 生成的 WireMock 存根的目錄。

  • testFramework: 指定要使用的目標測試框架。目前支援 Spock、JUnit 4 (TestFramework.JUNIT) 和 JUnit 5,其中 JUnit 4 是預設框架。

  • contractsProperties: 一個 Map,包含要傳遞給 Spring Cloud Contract 元件的屬性。這些屬性可能由(例如)內建或自定義的 Stub 下載器使用。

  • sourceSet: 儲存合約的源集。如果未提供,將假定為 contractTest(例如,JUnit 為 project.sourceSets.contractTest.java,Spock 為 project.sourceSets.contractTest.groovy)。

您可以使用以下屬性來指定包含合約的 JAR 的位置

  • contractDependency: 指定提供 groupid:artifactid:version:classifier 座標的依賴項。可以使用 contractDependency closure 來進行設定。

  • contractsPath: 指定 jar 的路徑。如果下載了合約依賴項,路徑預設為 groupid/artifactid,其中 groupid 是斜槓分隔的。否則,它會掃描提供的目錄下的合約。

  • contractsMode: 指定下載合約的模式(例如,JAR 是否離線、遠端等)。

  • deleteStubsAfterTest: 如果設定為 false,則不會從臨時目錄中刪除任何下載的合約。

  • failOnNoContracts: 啟用時,如果找不到合約,將丟擲異常。預設為 true

  • failOnInProgress: 如果設定為 true,則如果找到任何正在進行中的合約,它們將導致構建失敗。在生產者端,您需要明確表明您有正在進行中的合約,並考慮到這可能會導致消費者端的測試結果出現誤報。預設為 true

還有一個 contractRepository { …​ } closure,其中包含以下屬性

  • repositoryUrl: 包含合約定義的倉庫 URL

  • username : 倉庫使用者名稱

  • password : 倉庫密碼

  • proxyPort : 代理埠

  • proxyHost : 代理主機

  • cacheDownloadedContracts : 如果設定為 true,則快取非快照合約 artifacts 下載到的資料夾。預設為 true

您還可以在外掛中開啟以下實驗性功能

  • convertToYaml: 將所有 DSL 轉換為宣告式 YAML 格式。這在使用外部庫編寫 Groovy DSL 時非常有用。透過開啟此功能(將其設定為 true),消費者端無需新增庫依賴。

  • assertJsonSize: 您可以在生成的測試中檢查 JSON 陣列的大小。此功能預設停用。

所有測試的單一基類

在 MockMvc(預設)中使用 Spring Cloud Contract Verifier 時,需要為所有生成的驗收測試建立一個基本規範。在此類中,您需要指向應被驗證的端點。以下示例展示瞭如何做到這一點

abstract class BaseMockMvcSpec extends Specification {

	def setup() {
		RestAssuredMockMvc.standaloneSetup(new PairIdController())
	}

	void isProperCorrelationId(Integer correlationId) {
		assert correlationId == 123456
	}

	void isEmpty(String value) {
		assert value == null
	}

}

如果使用 Explicit 模式,可以使用基類初始化整個被測試的應用程式,就像在常規整合測試中一樣。如果使用 JAXRSCLIENT 模式,此基類還應包含一個受保護的 WebTarget webTarget 欄位。目前,測試 JAX-RS API 的唯一選項是啟動 Web 伺服器。

不同合約使用不同的基類

如果不同合約使用不同的基類,可以告知 Spring Cloud Contract 外掛哪個類應該被自動生成的測試所擴充套件。您有兩種選擇

  • 透過提供 packageWithBaseClasses 遵循約定

  • 透過使用 baseClassMappings 提供顯式對映

透過約定

約定是這樣的:如果您在(例如)src/contractTest/resources/contract/foo/bar/baz/ 中有一個合約,並將 packageWithBaseClasses 屬性的值設定為 com.example.base,那麼 Spring Cloud Contract Verifier 會假定在 com.example.base 包下有一個 BarBazBase 類。換句話說,系統會取包的最後兩部分(如果存在),並形成一個帶有 Base 字尾的類。此規則優先於 baseClassForTests

透過對映

您可以手動將合約包的正則表示式對映到匹配合約的基類的完全限定名。您必須提供一個名為 baseClassMappings 的列表,該列表由 baseClassMapping 物件組成,這些物件採用 contractPackageRegexbaseClassFQN 的對映。

假設您在以下目錄中有合約

  • src/contractTest/resources/contract/com/

  • src/contractTest/resources/contract/foo/

透過提供 baseClassForTests,我們在對映失敗的情況下有一個回退選項。(您也可以提供 packageWithBaseClasses 作為回退。)這樣,從 src/contractTest/resources/contract/com/ 合約生成的測試將擴充套件 com.example.ComBase,而其餘測試將擴充套件 com.example.FooBase

呼叫生成的測試

為確保提供者端符合您定義的合約,需要執行以下命令

./gradlew contractTest

將存根釋出到 Artifact 倉庫

如果使用二進位制 artifact 倉庫來儲存存根,則需要配置 Gradle 的釋出部分以包含 verifierStubsJar。為此,您可以使用以下示例配置

apply plugin: 'maven-publish'

publishing {
    publications {
        maven(MavenPublication) {
            // other configuration

            artifact verifierStubsJar
        }
    }
}

自 3.0.0 版本起,內部存根釋出已被棄用並預設停用。建議將 verifierStubsJar 與您自己的釋出一同包含。

將存根推送到 SCM

如果使用 SCM 倉庫來儲存合約和存根,您可能希望自動化將存根推送到倉庫的步驟。為此,可以透過執行以下命令來呼叫 pushStubsToScm 任務

$ ./gradlew pushStubsToScm

使用 SCM 存根下載器 下,您可以找到所有可能的配置選項,這些選項可以透過 contractsProperties 欄位(例如,contracts { contractsProperties = [foo:"bar"] })、透過 contractsProperties 方法(例如,contracts { contractsProperties([foo:"bar"]) })傳遞,或者透過系統屬性或環境變數傳遞。

消費者端的 Spring Cloud Contract Verifier

在消費服務中,您需要像提供者一樣精確配置 Spring Cloud Contract Verifier 外掛。如果不想使用 Stub Runner,需要複製儲存在 src/contractTest/resources/contracts 中的合約,並使用以下命令生成 WireMock JSON 存根

./gradlew generateClientStubs
必須設定 stubsOutputDir 選項才能使存根生成工作。

存在時,您可以在自動化測試中使用 JSON 存根來消費服務。以下示例展示瞭如何做到這一點

@ContextConfiguration(loader == SpringApplicationContextLoader, classes == Application)
class LoanApplicationServiceSpec extends Specification {

 @ClassRule
 @Shared
 WireMockClassRule wireMockRule == new WireMockClassRule()

 @Autowired
 LoanApplicationService sut

 def 'should successfully apply for loan'() {
   given:
 	LoanApplication application =
			new LoanApplication(client: new Client(clientPesel: '12345678901'), amount: 123.123)
   when:
	LoanApplicationResult loanApplication == sut.loanApplication(application)
   then:
	loanApplication.loanApplicationStatus == LoanApplicationStatus.LOAN_APPLIED
	loanApplication.rejectionReason == null
 }
}

在前面的示例中,LoanApplication 對 FraudDetection 服務發起呼叫。此請求由配置了由 Spring Cloud Contract Verifier 生成的存根的 WireMock 伺服器處理。