Maven 專案

新增 Maven 外掛程式

若要新增 Spring Cloud Contract BOM,請在您的 pom.xml 檔案中包含以下區段

<dependency>
	<groupId>org.springframework.cloud</groupId>
	<artifactId>spring-cloud-contract-dependencies</artifactId>
	<version>${spring-cloud-contract.version}</version>
	<type>pom</type>
	<scope>import</scope>

接下來,新增 Spring Cloud Contract Verifier Maven 外掛程式,如下所示

			<plugin>
				<groupId>org.springframework.cloud</groupId>
				<artifactId>spring-cloud-contract-maven-plugin</artifactId>
				<version>${spring-cloud-contract.version}</version>
				<extensions>true</extensions>
				<configuration>
					<packageWithBaseClasses>com.example.fraud</packageWithBaseClasses>
<!--					<convertToYaml>true</convertToYaml>-->
				</configuration>
			</plugin>

您可以在 spring-cloud-contract-maven-plugin/index.html[Spring Cloud Contract Maven 外掛程式文件] 中閱讀更多資訊。

有時,無論選擇哪個 IDE,您可能會看到 target/generated-test-source 資料夾在 IDE 的類別路徑中不可見。為了確保它始終存在,您可以將以下條目新增至您的 pom.xml

<plugin>
	<groupId>org.codehaus.mojo</groupId>
	<artifactId>build-helper-maven-plugin</artifactId>
	<executions>
		<execution>
			<id>add-source</id>
			<phase>generate-test-sources</phase>
			<goals>
				<goal>add-test-source</goal>
			</goals>
			<configuration>
				<sources>
					<source>${project.build.directory}/generated-test-sources/contracts/</source>
				</sources>
			</configuration>
		</execution>
	</executions>
</plugin>

Maven 和 Rest Assured 2.0

預設情況下,Rest Assured 3.x 會新增至類別路徑。但是,您可以透過將 Rest Assured 2.x 新增至外掛程式類別路徑來使用它,如下所示

<plugin>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-contract-maven-plugin</artifactId>
    <version>${spring-cloud-contract.version}</version>
    <extensions>true</extensions>
    <configuration>
        <packageWithBaseClasses>com.example</packageWithBaseClasses>
    </configuration>
    <dependencies>
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-contract-verifier</artifactId>
            <version>${spring-cloud-contract.version}</version>
        </dependency>
        <dependency>
           <groupId>com.jayway.restassured</groupId>
           <artifactId>rest-assured</artifactId>
           <version>2.5.0</version>
           <scope>compile</scope>
        </dependency>
        <dependency>
           <groupId>com.jayway.restassured</groupId>
           <artifactId>spring-mock-mvc</artifactId>
           <version>2.5.0</version>
           <scope>compile</scope>
        </dependency>
    </dependencies>
</plugin>

<dependencies>
    <!-- all dependencies -->
    <!-- you can exclude rest-assured from spring-cloud-contract-verifier -->
    <dependency>
       <groupId>com.jayway.restassured</groupId>
       <artifactId>rest-assured</artifactId>
       <version>2.5.0</version>
       <scope>test</scope>
    </dependency>
    <dependency>
       <groupId>com.jayway.restassured</groupId>
       <artifactId>spring-mock-mvc</artifactId>
       <version>2.5.0</version>
       <scope>test</scope>
    </dependency>
</dependencies>

這樣一來,外掛程式會自動看到 Rest Assured 2.x 存在於類別路徑中,並據此修改匯入。

針對 Maven 使用快照和里程碑版本

若要使用快照和里程碑版本,您必須將以下區段新增至您的 pom.xml

<repositories>
	<repository>
		<id>spring-snapshots</id>
		<name>Spring Snapshots</name>
		<url>https://repo.spring.io/snapshot</url>
		<snapshots>
			<enabled>true</enabled>
		</snapshots>
	</repository>
	<repository>
		<id>spring-milestones</id>
		<name>Spring Milestones</name>
		<url>https://repo.spring.io/milestone</url>
		<snapshots>
			<enabled>false</enabled>
		</snapshots>
	</repository>
</repositories>
<pluginRepositories>
	<pluginRepository>
		<id>spring-snapshots</id>
		<name>Spring Snapshots</name>
		<url>https://repo.spring.io/snapshot</url>
		<snapshots>
			<enabled>true</enabled>
		</snapshots>
	</pluginRepository>
	<pluginRepository>
		<id>spring-milestones</id>
		<name>Spring Milestones</name>
		<url>https://repo.spring.io/milestone</url>
		<snapshots>
			<enabled>false</enabled>
		</snapshots>
	</pluginRepository>
</pluginRepositories>

新增 stub

預設情況下,Spring Cloud Contract Verifier 會在 src/test/resources/contracts 目錄中尋找 stub。包含 stub 定義的目錄會被視為類別名稱,而每個 stub 定義都會被視為單一測試。我們假設它至少包含一個目錄,以用作測試類別名稱。如果有多個巢狀目錄層級,則除了最後一個目錄之外的所有目錄都會用作套件名稱。請考慮以下結構

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

假設有該結構,Spring Cloud Contract Verifier 會建立一個名為 defaultBasePackage.MyService 的測試類別,其中包含兩個方法

  • shouldCreateUser()

  • shouldReturnUser()

執行外掛程式

generateTests 外掛程式目標已指定為在名為 generate-test-sources 的階段中叫用。如果您希望它成為建置流程的一部分,則無需執行任何操作。如果您只想產生測試,請叫用 generateTests 目標。

如果您想要從 Maven 執行 stub,請使用要執行的 stub 作為 spring.cloud.contract.verifier.stubs 系統屬性來呼叫 run 目標,如下所示

mvn org.springframework.cloud:spring-cloud-contract-maven-plugin:run \ -Dspring.cloud.contract.verifier.stubs="com.acme:service-name"

組態外掛程式

若要變更預設組態,您可以將 configuration 區段新增至外掛程式定義或 execution 定義,如下所示

<plugin>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-contract-maven-plugin</artifactId>
    <executions>
        <execution>
            <goals>
                <goal>convert</goal>
                <goal>generateStubs</goal>
                <goal>generateTests</goal>
            </goals>
        </execution>
    </executions>
    <configuration>
        <basePackageForTests>org.springframework.cloud.verifier.twitter.place</basePackageForTests>
        <baseClassForTests>org.springframework.cloud.verifier.twitter.place.BaseMockMvcSpec</baseClassForTests>
    </configuration>
</plugin>

組態選項

  • testMode:定義驗收測試的模式。預設情況下,模式為 MockMvc,其基於 Spring 的 MockMvc。您也可以將其變更為 WebTestClientJaxRsClientExplicit(用於真實 HTTP 呼叫)。

  • basePackageForTests:指定所有產生測試的基礎套件。如果未設定,則會從 baseClassForTests 的套件以及 packageWithBaseClasses 中選取值。如果這些值都未設定,則值會設定為 org.springframework.cloud.contract.verifier.tests

  • ruleClassForTests:指定應新增至產生測試類別的規則。

  • baseClassForTests:為所有產生測試建立基礎類別。預設情況下,如果您使用 Spock 類別,則類別為 spock.lang.Specification

  • contractsDirectory:指定包含以 Groovyn DSL 撰寫的契約的目錄。預設目錄為 /src/test/resources/contracts

  • generatedTestSourcesDir:指定應放置從 Groovy DSL 產生的測試的測試來源目錄。預設情況下,其值為 $buildDir/generated-test-sources/contracts

  • generatedTestResourcesDir:指定產生測試所使用資源的測試資源目錄。

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

  • packageWithBaseClasses:定義所有基礎類別所在的套件。此設定優先於 baseClassForTests。慣例是,如果您在 (例如) src/test/resources/contract/foo/bar/baz/ 下有一個契約,並將 packageWithBaseClasses 屬性的值設定為 com.example.base,則 Spring Cloud Contract Verifier 會假設在 com.example.base 套件下有一個 BarBazBase 類別。換句話說,系統會採用套件的最後兩個部分(如果存在),並形成一個以 Base 作為字尾的類別。

  • baseClassMappings:指定基礎類別對應的清單,其提供 contractPackageRegex(針對契約所在的套件進行檢查)和 baseClassFQN(其對應至相符契約的基礎類別的完整限定名稱)。例如,如果您在 src/test/resources/contract/foo/bar/baz/ 下有一個契約,並對應 .* → com.example.base.BaseClass 屬性,則從這些契約產生的測試類別會擴充 com.example.base.BaseClass。此設定優先於 packageWithBaseClassesbaseClassForTests

  • contractsProperties:包含要傳遞至 Spring Cloud Contract 元件的屬性的對應。這些屬性可能會被 (例如) 內建或自訂 Stub 下載器使用。

  • failOnNoContracts:啟用時,如果找不到任何契約,將會擲回例外狀況。預設值為 true

  • failOnInProgress:如果設定為 true,則如果找到任何正在進行中的契約,它們會中斷建置。在生產者端,您需要明確說明您有正在進行中的契約,並考慮到您可能會在消費者端造成誤判的測試結果。預設值為 true

  • incrementalContractTests:啟用時,僅當契約自上次建置以來已變更時,才會建立測試。預設值為 true

  • incrementalContractStubs:啟用時,僅當契約自上次建置以來已變更時,才會建立 stub。預設值為 true

  • incrementalContractStubsJar:啟用時,僅當 stub 自上次建置以來已變更時,才會建立 stub jar。*httpPort:用於服務 stub 的 WireMock 伺服器的 HTTP 連接埠。目前,spring.cloud.contract.verifier.http.port 屬性僅在從目錄提供 stub 時才有效。否則,當提供 stub ID 時,連接埠必須包含在 ID 字串中。*skip:將此設定為 true 以略過驗證器執行。*skipTestOnly:將此設定為 true 以略過驗證器測試產生。*stubs:要下載並以冒號分隔的 Ivy 標記法執行的 stub 清單。*minPort:指定 stub 應開始的最小連接埠。*maxPort:指定 stub 應開始的最大連接埠。*waitForKeyPressed:指定外掛程式是否應在啟動 stub 後等待使用者按下按鍵。*stubsClassifier:指定 stub 成品使用的分類器。

如果您想要從 Maven 儲存庫下載契約定義,可以使用以下選項

  • contractDependency:包含所有封裝契約的契約依賴項。

  • contractsPath:JAR 中封裝契約的具體契約路徑。預設值為 groupid/artifactid,其中 gropuid 以斜線分隔。

  • contractsMode:選取尋找和註冊 stub 的模式。

  • deleteStubsAfterTest:如果設定為 false,則不要從暫存目錄中移除任何已下載的契約。

  • contractsRepositoryUrl:具有包含契約的成品之儲存庫的 URL。如果未提供,則使用目前的 Maven 儲存庫。

  • contractsRepositoryUsername:用於連線至具有契約之儲存庫的使用者名稱。

  • contractsRepositoryPassword:用於連線至具有契約之儲存庫的密碼。

  • contractsRepositoryProxyHost:用於連線至具有契約之儲存庫的 Proxy 主機。

  • contractsRepositoryProxyPort:用於連線至具有契約之儲存庫的 Proxy 連接埠。

我們僅快取非快照、明確提供的版本(例如,+1.0.0.BUILD-SNAPSHOT 不會被快取)。預設情況下,此功能已開啟。

以下清單說明您可以在外掛程式中開啟的實驗性功能

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

  • assertJsonSize:您可以檢查產生測試中 JSON 陣列的大小。預設情況下,此功能已停用。

所有測試的單一基礎類別

在預設 (MockMvc) 中使用 Spring Cloud Contract Verifier 時,您需要為所有產生的驗收測試建立基礎規格。在此類別中,您需要指向應驗證的端點。以下範例示範如何執行此操作

import org.mycompany.ExampleSpringController
import com.jayway.restassured.module.mockmvc.RestAssuredMockMvc
import spock.lang.Specification

class MvcSpec extends Specification {
  def setup() {
   RestAssuredMockMvc.standaloneSetup(new ExampleSpringController())
  }
}

如有必要,您也可以設定整個內容,如下列範例所示

import io.restassured.module.mockmvc.RestAssuredMockMvc;
import org.junit.Before;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.context.junit4.SpringRunner;
import org.springframework.web.context.WebApplicationContext;

@RunWith(SpringRunner.class)
@SpringBootTest(webEnvironment = WebEnvironment.RANDOM_PORT, classes = SomeConfig.class, properties="some=property")
public abstract class BaseTestClass {

	@Autowired
	WebApplicationContext context;

	@Before
	public void setup() {
		RestAssuredMockMvc.webAppContextSetup(this.context);
	}
}

如果您使用 EXPLICIT 模式,則可以使用基礎類別來初始化整個測試應用程式,類似於您在一般整合測試中所做的事情。以下範例示範如何執行此操作

import io.restassured.RestAssured;
import org.junit.Before;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.boot.web.server.LocalServerPort;
import org.springframework.test.context.junit4.SpringRunner;
import org.springframework.web.context.WebApplicationContext;

@RunWith(SpringRunner.class)
@SpringBootTest(webEnvironment = WebEnvironment.RANDOM_PORT, classes = SomeConfig.class, properties="some=property")
public abstract class BaseTestClass {

	@LocalServerPort
	int port;

	@Before
	public void setup() {
		RestAssured.baseURI = "http://localhost:" + this.port;
	}
}

如果您使用 JAXRSCLIENT 模式,則此基礎類別也應包含 protected WebTarget webTarget 欄位。目前,測試 JAX-RS API 的唯一方法是啟動 Web 伺服器。

針對契約使用不同的基礎類別

如果您的基礎類別在契約之間有所不同,您可以告知 Spring Cloud Contract 外掛程式,自動產生的測試應擴充哪個類別。您有兩個選項

  • 透過提供 packageWithBaseClasses 的值來遵循慣例

  • 使用 baseClassMappings 提供明確對應

依慣例

慣例是,如果您在 (例如) src/test/resources/contract/foo/bar/baz/ 下有一個契約,並將 packageWithBaseClasses 屬性的值設定為 com.example.base,則 Spring Cloud Contract Verifier 會假設在 com.example.base 套件下有一個 BarBazBase 類別。換句話說,系統會採用套件的最後兩個部分(如果存在),並形成一個以 Base 作為字尾的類別。此規則優先於 baseClassForTests。以下範例示範其在 contracts 封閉包中的運作方式

<plugin>
	<groupId>org.springframework.cloud</groupId>
	<artifactId>spring-cloud-contract-maven-plugin</artifactId>
	<configuration>
		<packageWithBaseClasses>hello</packageWithBaseClasses>
	</configuration>
</plugin>

依對應

您可以手動將契約套件的規則運算式對應至相符契約的基礎類別的完整限定名稱。您必須提供名為 baseClassMappings 的清單,其中包含 baseClassMapping 物件,每個物件都採用 contractPackageRegexbaseClassFQN 的對應。請考慮以下範例

<plugin>
	<groupId>org.springframework.cloud</groupId>
	<artifactId>spring-cloud-contract-maven-plugin</artifactId>
	<configuration>
		<baseClassForTests>com.example.FooBase</baseClassForTests>
		<baseClassMappings>
			<baseClassMapping>
				<contractPackageRegex>.*com.*</contractPackageRegex>
				<baseClassFQN>com.example.TestBase</baseClassFQN>
			</baseClassMapping>
		</baseClassMappings>
	</configuration>
</plugin>

假設您在以下兩個位置有契約

  • src/test/resources/contract/com/

  • src/test/resources/contract/foo/

透過提供 baseClassForTests,我們有一個備用方案,以防對應失敗。(您也可以提供 packageWithBaseClasses 作為備用方案。)這樣一來,從 src/test/resources/contract/com/ 契約產生的測試會擴充 com.example.ComBase,而其餘測試則會擴充 com.example.FooBase

叫用產生的測試

Spring Cloud Contract Maven 外掛程式會在名為 /generated-test-sources/contractVerifier 的目錄中產生驗證碼,並將此目錄附加至 testCompile 目標。

對於 Groovy Spock 程式碼,您可以使用以下程式碼

<plugin>
	<groupId>org.codehaus.gmavenplus</groupId>
	<artifactId>gmavenplus-plugin</artifactId>
	<version>1.5</version>
	<executions>
		<execution>
			<goals>
				<goal>testCompile</goal>
			</goals>
		</execution>
	</executions>
	<configuration>
		<testSources>
			<testSource>
				<directory>${project.basedir}/src/test/groovy</directory>
				<includes>
					<include>**/*.groovy</include>
				</includes>
			</testSource>
			<testSource>
				<directory>${project.build.directory}/generated-test-sources/contractVerifier</directory>
				<includes>
					<include>**/*.groovy</include>
				</includes>
			</testSource>
		</testSources>
	</configuration>
</plugin>

為了確保提供者端符合已定義的契約,您需要叫用 mvn generateTest test

將 Stub 推送至 SCM

如果您使用 SCM(原始碼控制管理)儲存庫來保存契約和 stub,您可能想要自動執行將 stub 推送至儲存庫的步驟。若要執行此操作,您可以新增 pushStubsToScm 目標。以下範例示範如何執行此操作

<plugin>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-contract-maven-plugin</artifactId>
    <version>${spring-cloud-contract.version}</version>
    <extensions>true</extensions>
    <configuration>
        <!-- Base class mappings etc. -->

        <!-- We want to pick contracts from a Git repository -->
        <contractsRepositoryUrl>git://https://github.com/spring-cloud-samples/spring-cloud-contract-nodejs-contracts-git.git</contractsRepositoryUrl>

        <!-- We reuse the contract dependency section to set up the path
        to the folder that contains the contract definitions. In our case the
        path will be /groupId/artifactId/version/contracts -->
        <contractDependency>
            <groupId>${project.groupId}</groupId>
            <artifactId>${project.artifactId}</artifactId>
            <version>${project.version}</version>
        </contractDependency>

        <!-- The contracts mode can't be classpath -->
        <contractsMode>REMOTE</contractsMode>
    </configuration>
    <executions>
        <execution>
            <phase>package</phase>
            <goals>
                <!-- By default we will not push the stubs back to SCM,
                you have to explicitly add it as a goal -->
                <goal>pushStubsToScm</goal>
            </goals>
        </execution>
    </executions>
</plugin>

使用 SCM Stub 下載器 下,您可以找到所有可能透過 <configuration><contractsProperties> 對應、系統屬性或環境變數傳遞的組態選項。例如,您可以指定要簽出的具體分支,而不是預設分支

<plugin>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-contract-maven-plugin</artifactId>
    <version>${spring-cloud-contract.version}</version>
    <extensions>true</extensions>
    <configuration>
        <!-- Base class mappings etc. -->

        <!-- We want to pick contracts from a Git repository -->
        <contractsRepositoryUrl>git://https://github.com/spring-cloud-samples/spring-cloud-contract-nodejs-contracts-git.git</contractsRepositoryUrl>
	<contractsProperties>
            <git.branch>another_branch</git.branch>
        </contractsProperties>

        <!-- We reuse the contract dependency section to set up the path
        to the folder that contains the contract definitions. In our case the
        path will be /groupId/artifactId/version/contracts -->
        <contractDependency>
            <groupId>${project.groupId}</groupId>
            <artifactId>${project.artifactId}</artifactId>
            <version>${project.version}</version>
        </contractDependency>

        <!-- The contracts mode can't be classpath -->
        <contractsMode>REMOTE</contractsMode>
    </configuration>
    <executions>
        <execution>
            <phase>package</phase>
            <goals>
                <!-- By default we will not push the stubs back to SCM,
                you have to explicitly add it as a goal -->
                <goal>pushStubsToScm</goal>
            </goals>
        </execution>
    </executions>
</plugin>

Maven 外掛程式和 STS

下圖顯示當您使用 STS 時可能會看到的例外狀況

STS Exception

當您按一下錯誤標記時,您應該會看到類似以下內容

 plugin:1.1.0.M1:convert:default-convert:process-test-resources) org.apache.maven.plugin.PluginExecutionException: Execution default-convert of goal org.springframework.cloud:spring-
 cloud-contract-maven-plugin:1.1.0.M1:convert failed. at org.apache.maven.plugin.DefaultBuildPluginManager.executeMojo(DefaultBuildPluginManager.java:145) at
 org.eclipse.m2e.core.internal.embedder.MavenImpl.execute(MavenImpl.java:331) at org.eclipse.m2e.core.internal.embedder.MavenImpl$11.call(MavenImpl.java:1362) at
...
 org.eclipse.core.internal.jobs.Worker.run(Worker.java:55) Caused by: java.lang.NullPointerException at
 org.eclipse.m2e.core.internal.builder.plexusbuildapi.EclipseIncrementalBuildContext.hasDelta(EclipseIncrementalBuildContext.java:53) at
 org.sonatype.plexus.build.incremental.ThreadBuildContext.hasDelta(ThreadBuildContext.java:59) at

若要修正此問題,請在您的 pom.xml 中提供以下區段

<build>
    <pluginManagement>
        <plugins>
            <!--This plugin's configuration is used to store Eclipse m2e settings
                only. It has no influence on the Maven build itself. -->
            <plugin>
                <groupId>org.eclipse.m2e</groupId>
                <artifactId>lifecycle-mapping</artifactId>
                <version>1.0.0</version>
                <configuration>
                    <lifecycleMappingMetadata>
                        <pluginExecutions>
                             <pluginExecution>
                                <pluginExecutionFilter>
                                    <groupId>org.springframework.cloud</groupId>
                                    <artifactId>spring-cloud-contract-maven-plugin</artifactId>
                                    <versionRange>[1.0,)</versionRange>
                                    <goals>
                                        <goal>convert</goal>
                                    </goals>
                                </pluginExecutionFilter>
                                <action>
                                    <execute />
                                </action>
                             </pluginExecution>
                        </pluginExecutions>
                    </lifecycleMappingMetadata>
                </configuration>
            </plugin>
        </plugins>
    </pluginManagement>
</build>

搭配 Spock 測試的 Maven 外掛程式

您可以選取 Spock Framework,以使用 Maven 和 Gradle 建立和執行自動產生的契約驗證測試。但是,雖然使用 Gradle 很簡單,但在 Maven 中,您需要進行一些額外設定,才能讓測試正確編譯和執行。

首先,您必須使用外掛程式,例如 GMavenPlus 外掛程式,以將 Groovy 新增至您的專案。在 GMavenPlus 外掛程式中,您需要明確設定測試來源,包括定義基礎測試類別的路徑和新增產生契約測試的路徑。以下範例示範如何執行此操作。

如果您堅持 Spock 慣例,以 Spec 結尾測試類別名稱,您也需要調整您的 Maven Surefire 外掛程式設定,如 以下範例所示。