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>
</dependency>

接下来,按如下方式添加 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>
	</configuration>
</plugin>

有时,无论你选择哪个 IDE,都可能会看到 target/generated-test-source 文件夹在 IDE 的 classpath 中不可见。为确保它始终存在,你可以将以下条目添加到你的 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 会被添加到 classpath。但是,你可以通过将其添加到插件的 classpath 来使用 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 存在于 classpath 中,并相应地修改导入。

使用 Maven 的 Snapshot 和 Milestone 版本

要使用 Snapshot 和 Milestone 版本,你必须将以下部分添加到你的 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,请调用 run 目标,并将要运行的 stub 指定为 spring.cloud.contract.verifier.stubs 系统属性,如下所示

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: 一个 Map,包含传递给 Spring Cloud Contract 组件的属性。这些属性可能被(例如)内置或自定义的 Stub Downloaders 使用。

  • failOnNoContracts: 启用时,如果未找到任何契约,则抛出异常。默认为 true

  • failOnInProgress: 如果设置为 true,那么如果发现任何正在进行中的契约,它们将导致构建失败。在生产者侧,你需要明确表明你有正在进行中的契约,并考虑到这可能会在消费者侧导致误报测试结果。默认为 true

  • incrementalContractTests: 启用时,仅当契约自上次构建以来发生更改时才创建测试。默认为 true

  • incrementalContractStubs: 启用时,仅当契约自上次构建以来发生更改时才创建 stub。默认为 true

  • incrementalContractStubsJar: 启用时,仅当 stub 自上次构建以来发生更改时才创建 stub jar。默认为 true。 *httpPort:WireMock 服务器的 HTTP 端口,用于提供 stub。目前 spring.cloud.contract.verifier.http.port 属性仅在从目录提供 stub 时有效。否则,提供 stub id 时,端口必须包含在 id 字符串中。 *skip:设置为 true 可跳过验证器执行。 *skipTestOnly:设置为 true 可跳过验证器测试生成。 *stubs:要下载和运行的 stub 列表,使用冒号分隔的 Ivy 符号。 *minPort:指定 stub 启动的最小端口。 *maxPort:指定 stub 启动的最大端口。 *waitForKeyPressed:指定启动 stub 后插件是否应等待用户按键。 *stubsClassifier:指定 stub artifact 使用的分类器。

如果你想从 Maven 仓库下载你的契约定义,可以使用以下选项

  • contractDependency: 包含所有打包契约的契约依赖项。

  • contractsPath: 打包契约的 JAR 中具体契约的路径。默认值为 groupid/artifactid,其中 gropuid 是斜杠分隔的。

  • contractsMode: 选择查找和注册 stub 的模式。

  • deleteStubsAfterTest: 如果设置为 false,则不从临时目录中删除任何下载的契约。

  • contractsRepositoryUrl: 包含契约 artifact 的仓库 URL。如果未提供,则使用当前 Maven 仓库。

  • contractsRepositoryUsername: 用于连接到包含契约的仓库的用户名。

  • contractsRepositoryPassword: 用于连接到包含契约的仓库的密码。

  • contractsRepositoryProxyHost: 用于连接到包含契约的仓库的代理主机。

  • contractsRepositoryProxyPort: 用于连接到包含契约的仓库的代理端口。

我们仅缓存非 snapshot、明确提供的版本(例如 +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.web.context.WebApplicationContext;

@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 closure 中工作

<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 (Source Control Management) 仓库来保存契约和 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> map、系统属性或环境变量传递的配置选项。例如,你可以指定要 checkout 的具体分支,而不是默认分支

<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 插件的设置,以下示例展示了如何做到这一点。