91. Spring Cloud Contract Verifier Setup

您可以通过以下方式设置 Spring Cloud Contract Verifier:

91.1 Gradle Project

要了解如何为 Spring Cloud Contract Verifier 设置 Gradle 项目,请阅读以下部分:

91.1.1 先决条件

在 order 中使用 Spring Cloud Contract Verifier 和 WireMock,你可以使用 Gradle 或 Maven 插件。

如果要在项目中使用 Spock,则必须单独添加spock-corespock-spring模块。检查Spock docs 了解更多信息

91.1.2 添加带有依赖项的 Gradle 插件

要添加带依赖项的 Gradle 插件,请使用与此类似的 code:

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}"
	}
}

apply plugin: 'groovy'
apply plugin: 'spring-cloud-contract'

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

dependencies {
	testCompile 'org.codehaus.groovy:groovy-all:2.4.6'
	// example with adding Spock core and Spock Spring
	testCompile 'org.spockframework:spock-core:1.0-groovy-2.4'
	testCompile 'org.spockframework:spock-spring:1.0-groovy-2.4'
	testCompile 'org.springframework.cloud:spring-cloud-starter-contract-verifier'
}

91.1.3 Gradle 和 Rest Assured 2.0

默认情况下,Rest Assured 3.x 将添加到 classpath。但是,要使用 Rest Assured 2.x,您可以将其添加到插件 classpath 中,如下所示:

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}"
		classpath "com.jayway.restassured:rest-assured:2.5.0"
		classpath "com.jayway.restassured:spring-mock-mvc:2.5.0"
	}
}

depenendencies {
    // 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 出现在 classpath 上,并相应地修改导入。

91.1.4 Gradle 的快照版本

将附加快照 repository 添加到 build.gradle 以使用快照版本,这些版本会在每次成功 build 后自动上载,如下所示:

buildscript {
	repositories {
		mavenCentral()
		mavenLocal()
		maven { url "http://repo.spring.io/snapshot" }
		maven { url "http://repo.spring.io/milestone" }
		maven { url "http://repo.spring.io/release" }
	}
}

91.1.5 添加存根

默认情况下,Spring Cloud Contract Verifier 正在查找src/test/resources/contracts目录中的存根。

包含存根定义的目录被视为 class name,每个存根定义被视为单个测试。 Spring Cloud Contract Verifier 假定它至少包含一个 level 目录,用作 test class name。如果存在多个嵌套目录的 level,则除最后一个之外的所有目录都将用作包 name。对于 example,具有以下结构:

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

Spring Cloud Contract Verifier 使用两种方法创建名为defaultBasePackage.MyService的测试 class:

  • shouldCreateUser()

  • shouldReturnUser()

91.1.6 运行插件

插件注册自己在check任务之前调用。如果您希望它成为 build process 的一部分,则无需执行任何操作。如果您只想生成测试,请调用generateContractTests任务。

91.1.7 默认设置

默认的 Gradle 插件设置会创建 build 的以下 Gradle 部分(伪代码):

contracts {
    targetFramework = 'JUNIT'
    testMode = 'MockMvc'
    generatedTestSourcesDir = project.file("${project.buildDir}/generated-test-sources/contracts")
    contractsDslDir = "${project.rootDir}/src/test/resources/contracts"
    basePackageForTests = 'org.springframework.cloud.verifier.tests'
    stubsOutputDir = project.file("${project.buildDir}/stubs")

    // the following properties are used when you want to provide where the JAR with contract lays
    contractDependency {
        stringNotation = ''
    }
    contractsPath = ''
    contractsWorkOffline = false
    contractRepository {
        cacheDownloadedContracts(true)
    }
}

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

project.artifacts {
    archives task
}

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

verifierStubsJar.dependsOn 'copyContracts'

publishing {
    publications {
        stubs(MavenPublication) {
            artifactId project.name
            artifact verifierStubsJar
        }
    }
}

91.1.8 配置插件

要更改默认的 configuration,请在 Gradle 配置中添加contracts片段,如下所示:

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

91.1.9 Configuration 选项

  • testMode:定义验收测试的模式。默认情况下,模式是 MockMvc,它基于 Spring 的 MockMvc。对于真正的 HTTP calls,它也可以更改为JaxRsClientExplicit

  • imports:使用应包含在生成的测试中的导入创建一个 array(对于 example [56])。默认情况下,它会创建一个空的 array。

  • staticImports:使用应包含在生成的 tests(for example [57]中的静态导入创建一个 array。默认情况下,它会创建一个空的 array。

  • basePackageForTests:指定所有生成的测试的基础包。如果未设置,则从baseClassForTests's package and from packageWithBaseClasses . If neither of these values are set, then the value is set to org.springframework.cloud.contract.verifier.tests`中选择 value。

  • baseClassForTests:为所有生成的测试创建 base class。默认情况下,如果使用 Spock classes,则 class 为spock.lang.Specification

  • packageWithBaseClasses:定义一个包含所有 base classes 的包。此设置优先于baseClassForTests

  • baseClassMappings:显式 maps contract 包到 base class 的 FQN。此设置优先于packageWithBaseClassesbaseClassForTests

  • ruleClassForTests:指定应添加到生成的测试 classes 的规则。

  • ignoredFiles:使用Antmatcher允许定义应跳过处理的 stub files。默认情况下,它是一个空的 array。

  • contractsDslDir:指定包含使用 GroovyDSL 编写的 contracts 的目录。默认情况下,其 value 为$rootDir/src/test/resources/contracts

  • generatedTestSourcesDir:指定应放置从 Groovy DSL 生成的测试的测试源目录。默认情况下,value 为$buildDir/generated-test-sources/contractVerifier

  • stubsOutputDir:指定应放置从 Groovy DSL 生成的 WireMock 存根的目录。

  • targetFramework:指定要使用的目标测试 framework。目前,支持 Spock 和 JUnit,JUnit 是默认的 framework。

  • contractsProperties:包含要传递给 Spring Cloud Contract 组件的 properties 的 map。那些 properties 可能被 e.g 使用。内置或自定义 Stub 下载器。

当您要指定包含 contracts 的 JAR 的位置时,将使用以下 properties:* contractDependency:指定提供groupid:artifactid:version:classifier坐标的依赖项。您可以使用contractDependency闭包来设置它。 * contractsPath:指定 jar 的路径。如果下载了 contract 依赖项,则路径默认为groupid/artifactid,其中groupid是斜杠分隔。否则,它会扫描提供的目录下的 contracts。 * contractsMode:指定下载 contracts 的模式(JAR 是否可脱机使用,远程 etc.) * contractsSnapshotCheckSkip:如果设置为true将不断言下载的存根/ contract JAR 是否从 remote 位置下载或本地 one(only 适用于 Maven 回购,而不是 Git 或 Pact)。 * deleteStubsAfterTest:如果设置为false将不会从临时目录中删除任何下载的 contracts

91.1.10 单个 Base Class for All Tests

在默认的 MockMvc 中使用 Spring Cloud Contract Verifier 时,您需要为所有生成的验收测试创建基本规范。在此 class 中,您需要指向应验证的端点。

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模式,则可以使用 base class 初始化整个测试的应用程序,就像在常规 integration 测试中看到的那样。如果使用JAXRSCLIENT模式,则此 base class 也应包含protected WebTarget webTarget字段。现在,测试 JAX-RS API 的唯一选择是启动 web 服务器。

91.1.11 Contracts 的 Base Classes

如果你的 base classes 在 contracts 之间有所不同,你可以告诉 Spring Cloud Contract 插件哪个 class 应该由自动生成的测试扩展。您有两种选择:

  • 通过提供packageWithBaseClasses遵循惯例

  • 通过baseClassMappings提供显式映射

按照惯例

约定是这样的,如果你有一个 contract(for example)src/test/resources/contract/foo/bar/baz/并将packageWithBaseClasses property 的 value 设置为com.example.base,那么 Spring Cloud Contract Verifier 假定com.example.base包下有一个BarBazBase class。换句话说,系统采用包的最后两部分(如果存在),并形成带有Base后缀的 class。此规则优先于baseClassForTests。这是闭包中它如何工作的一个例子:

packageWithBaseClasses = 'com.example.base'

通过映射

您可以手动将 contract 包的正则表达式 map 映射到匹配的 contract 的 base class 的完全限定 name。您必须提供一个名为baseClassMappings的列表,其中包含baseClassMapping objects,它采用contractPackageRegexbaseClassFQN映射。考虑以下 example:

baseClassForTests = "com.example.FooBase"
baseClassMappings {
	baseClassMapping('.*/com/.*', 'com.example.ComBase')
	baseClassMapping('.*/bar/.*':'com.example.BarBase')
}

我们假设你有 contracts - src/test/resources/contract/com/ - src/test/resources/contract/foo/

通过提供baseClassForTests,我们有一个后备,因为映射没有成功。 (你也可以将packageWithBaseClasses提供为 fallback.)那样,从src/test/resources/contract/com/ contracts 生成的测试扩展com.example.ComBase,而测试的 rest 扩展com.example.FooBase

91.1.12 调用生成的测试

要确保提供者端符合已定义的 contracts,您需要调用:

./gradlew generateContractTests test

91.1.13 将存根推送到 SCM

如果您正在使用 SCM repository 来保留 contracts 和 stubs,那么您可能希望自动执行将存根推送到 repository 的步骤。要做到这一点,就足以调用pushStubsToScm任务。 例:

$ ./gradlew pushStubsToScm

第 97.6 节,“使用 SCM Stub Downloader”下,您可以找到所有可以通过contractsProperties字段 e.g 传递的 configuration 选项。 contracts { contractsProperties = [foo:"bar"] },通过contractsProperties方法 e.g. contracts { contractsProperties([foo:"bar"]) },系统 property 或环境变量。

Consumer 侧的 91.1.14 Spring Cloud Contract Verifier

在 consuming 服务中,您需要以与提供者完全相同的方式配置 Spring Cloud Contract Verifier 插件。如果您不想使用 Stub Runner,则需要复制存储在src/test/resources/contracts中的 contracts 并使用以下命令生成 WireMock JSON 存根:

./gradlew generateClientStubs

必须设置stubsOutputDir选项才能使存根生成生效。

如果存在,JSON 存根可以用于 consuming 服务的自动化测试。

@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 服务器处理。

91.2 Maven Project

要了解如何为 Spring Cloud Contract Verifier 设置 Maven 项目,请阅读以下部分:

91.2.1 添加 maven 插件

以类似于此的方式添加 Spring Cloud Contract BOM:

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

接下来,添加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>

您可以在Spring Cloud Contract Maven 插件文档(示例 2.0.0.RELEASE version)中阅读更多内容。

91.2.2 Maven 和 Rest Assured 2.0

默认情况下,Rest Assured 3.x 将添加到 classpath。但是,您可以通过将 Rest Assured 2.x 添加到插件 classpath 来使用它,如下所示:

<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 3.x 出现在 classpath 上,并相应地修改导入。

91.2.3 Maven 的快照版本

对于 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>
	<repository>
		<id>spring-releases</id>
		<name>Spring Releases</name>
		<url>https://repo.spring.io/release</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>
	<pluginRepository>
		<id>spring-releases</id>
		<name>Spring Releases</name>
		<url>https://repo.spring.io/release</url>
		<snapshots>
			<enabled>false</enabled>
		</snapshots>
	</pluginRepository>
</pluginRepositories>

91.2.4 添加存根

默认情况下,Spring Cloud Contract Verifier 正在查找src/test/resources/contracts目录中的存根。包含存根定义的目录被视为 class name,每个存根定义被视为单个测试。我们假设它至少包含一个用作 test class name 的目录。如果有多个 level 的嵌套目录,则除最后一个之外的所有目录都将用作包 name。对于 example,具有以下结构:

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

Spring Cloud Contract Verifier 使用两种方法创建名为defaultBasePackage.MyService的测试 class

  • shouldCreateUser()

  • shouldReturnUser()

91.2.5 Run 插件

插件目标generateTests被指定在名为generate-test-sources的阶段中调用。如果您希望它成为 build process 的一部分,则无需执行任何操作。如果您只想生成测试,请调用generateTests目标。

91.2.6 配置插件

要更改默认的 configuration,只需在插件定义或execution定义中添加configuration部分,如下所示:

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

91.2.7 Configuration 选项

  • testMode:定义验收测试的模式。默认情况下,模式是 MockMvc,它基于 Spring 的 MockMvc。对于真正的 HTTP calls,它也可以更改为JaxRsClientExplicit

  • basePackageForTests:指定所有生成的测试的基础包。如果未设置,则从baseClassForTests's package and from packageWithBaseClasses . If neither of these values are set, then the value is set to org.springframework.cloud.contract.verifier.tests`中选择 value。

  • ruleClassForTests:指定应添加到生成的测试 classes 的规则。

  • baseClassForTests:为所有生成的测试创建 base class。默认情况下,如果使用 Spock classes,则 class 为spock.lang.Specification

  • contractsDirectory:指定包含用 GroovyDSL 编写的 contracts 的目录。默认目录是/src/test/resources/contracts

  • testFramework:指定要使用的目标测试 framework。目前,支持 Spock 和 JUnit,JUnit 是默认的 framework

  • packageWithBaseClasses:定义一个包含所有 base classes 的包。此设置优先于baseClassForTests。约定是这样的,如果你有一个 contract(for example)src/test/resources/contract/foo/bar/baz/并将packageWithBaseClasses property 的 value 设置为com.example.base,那么 Spring Cloud Contract Verifier 假定com.example.base包下有一个BarBazBase class。换句话说,系统采用包的最后两部分(如果存在),并形成带有Base后缀的 class。

  • baseClassMappings:指定提供contractPackageRegex的 base class 映射列表,该列表根据 contract 所在的包进行检查,baseClassFQN,maps 到匹配 contract 的 base class 的完全限定 name。例如,如果src/test/resources/contract/foo/bar/baz/下有 contract,property .* → com.example.base.BaseClass有 map,那么从这些 contracts 生成的 test class 会扩展com.example.base.BaseClass。此设置优先于packageWithBaseClassesbaseClassForTests

  • contractsProperties:包含要传递给 Spring Cloud Contract 组件的 properties 的 map。那些 properties 可能被 e.g 使用。内置或自定义 Stub 下载器。

如果要从 Maven repository 下载 contract 定义,可以使用以下选项:

  • contractDependency:包含所有打包的 contracts 的 contract 依赖项。

  • contractsPath:带有 contracts 的 JAR 中具体 contracts 的路径。默认为groupid/artifactid,其中gropuid是斜杠分隔。

  • contractsMode:选择将找到并注册存根的模式

  • contractsSnapshotCheckSkip:如果true则不会断言是否从本地或 remote 位置下载了 stub/_conract JAR

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

  • contractsRepositoryUrl:带有 contracts 的 artifact 的 repo 的 URL。如果未提供,请使用当前的 Maven。

  • contractsRepositoryUsername:用于使用 contracts 连接到 repo 的用户 name。

  • contractsRepositoryPassword:用于通过 contracts 连接到 repo 的密码。

  • contractsRepositoryProxyHost:用于通过 contracts 连接到 repo 的代理 host。

  • contractsRepositoryProxyPort:用于通过 contracts 连接到 repo 的 proxy port。

我们只缓存 non-snapshot,显式提供的版本(对于 example +1.0.0.BUILD-SNAPSHOT不会被缓存)。默认情况下,此 feature 已打开。

91.2.8 单个 Base Class for All Tests

在默认的 MockMvc 中使用 Spring Cloud Contract Verifier 时,您需要为所有生成的验收测试创建基本规范。在此 class 中,您需要指向应验证的端点。

package org.mycompany.tests

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

如有必要,您还可以设置整个 context。

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模式,则可以使用 base class 类似地初始化整个测试的应用程序,就像在常规 integration 测试中找到的那样。

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模式,则此 base class 也应包含protected WebTarget webTarget字段。现在,测试 JAX-RS API 的唯一选择是启动 web 服务器。

91.2.9 contracts 的 base classes 不同

如果你的 base classes 在 contracts 之间有所不同,你可以告诉 Spring Cloud Contract 插件哪个 class 应该由自动生成的测试扩展。您有两种选择:

  • 通过提供packageWithBaseClasses遵循惯例

  • 通过baseClassMappings提供显式映射

按照惯例

约定是这样的,如果你有一个 contract(for example)src/test/resources/contract/foo/bar/baz/并将packageWithBaseClasses property 的 value 设置为com.example.base,那么 Spring Cloud Contract Verifier 假定com.example.base包下有一个BarBazBase class。换句话说,系统采用包的最后两部分(如果存在),并形成带有Base后缀的 class。此规则优先于baseClassForTests。这是闭包中它如何工作的一个例子:

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

通过映射

您可以手动将 contract 包的正则表达式 map 映射到匹配的 contract 的 base class 的完全限定 name。您必须提供一个名为baseClassMappings的列表,其中包含baseClassMapping objects,它采用contractPackageRegexbaseClassFQN映射。考虑以下 example:

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

假设您在这两个位置下有 contracts:* src/test/resources/contract/com/ * src/test/resources/contract/foo/

通过提供baseClassForTests,我们有一个后备,因为映射没有成功。 (你也可以提供packageWithBaseClasses作为 fallback.)这样,从src/test/resources/contract/com/ contracts 生成的测试扩展com.example.ComBase,而测试的 rest 扩展com.example.FooBase

91.2.10 调用生成的测试

Spring Cloud Contract Maven 插件在名为/generated-test-sources/contractVerifier的目录中生成验证 code,并将此目录附加到testCompile目标。

对于 Groovy Spock code,请使用以下命令:

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

要确保提供者方符合已定义的 contracts,您需要调用mvn generateTest test

91.2.11 将存根推送到 SCM

如果您正在使用 SCM repository 来保留 contracts 和 stubs,那么您可能希望自动执行将存根推送到 repository 的步骤。要做到这一点,添加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>

第 97.6 节,“使用 SCM Stub Downloader”下,您可以找到所有可以通过<configuration><contractProperties> map,系统 property 或环境变量传递的 configuration 选项。

91.2.12 Maven 插件和 STS

如果在使用 STS 时看到以下 exception:

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>

91.3 存根和传递依赖关系

Maven 和 Gradle 插件,为您添加创建存根 jar 的任务。出现的一个问题是,在重用存根时,您可能会错误地 import 所有存根的依赖关系。当 building a Maven artifact 时,即使你有几个不同的 jars,它们都共享一个 pom:

├── github-webhook-0.0.1.BUILD-20160903.075506-1-stubs.jar
├── github-webhook-0.0.1.BUILD-20160903.075506-1-stubs.jar.sha1
├── github-webhook-0.0.1.BUILD-20160903.075655-2-stubs.jar
├── github-webhook-0.0.1.BUILD-20160903.075655-2-stubs.jar.sha1
├── github-webhook-0.0.1.BUILD-SNAPSHOT.jar
├── github-webhook-0.0.1.BUILD-SNAPSHOT.pom
├── github-webhook-0.0.1.BUILD-SNAPSHOT-stubs.jar
├── ...
└── ...

使用这些依赖项有三种可能性,以免对传递依赖项产生任何问题:

  • 将所有 application 依赖项标记为可选

  • 为存根创建单独的 artifactid

  • 排除 consumer 方面的依赖关系

将所有 application 依赖项标记为可选

如果在github-webhook application 中将所有依赖项标记为可选,则在另一个 application 中包含github-webhook存根时(或者当 Stub Runner 下载该依赖项时),因为所有依赖项都是可选的,它们将无法获取下载。

为存根创建单独的 artifactid

如果您创建一个单独的artifactid,那么您可以按照您希望的任何方式进行设置。例如,您可能决定根本没有依赖关系。

排除 consumer 方面的依赖关系

作为 consumer,如果将存根依赖项添加到 classpath,则可以显式排除不需要的依赖项。

91.4 CI 服务器设置

在 CI,共享环境中获取存根/ contracts 时,可能会发生的情况是 producer 和 consumer 都重用相同的本地 Maven repository。因此,负责从 remote 位置下载存根 JAR 的 framework 无法决定应该选择哪个 JAR,本地还是 remote。这导致"The artifact was found in the local repository but you have explicitly stated that it should be downloaded from a remote one" exception 并使 build 失败。

对于这种情况,我们引入了 property 和插件设置机制:

  • 通过stubrunner.snapshot-check-skip system property

  • 通过STUBRUNNER_SNAPSHOT_CHECK_SKIP环境变量

如果这些值中的任何一个设置为true,那么存根下载器将不会验证下载的 JAR 的来源。

对于插件,您需要将contractsSnapshotCheckSkip property 设置为true

91.5 场景

您可以使用 Spring Cloud Contract Verifier 处理方案。您需要做的就是在创建 contracts 时坚持正确的命名约定。该约定要求包含 order 数字后跟下划线。这将考虑您是否使用 YAML 或 Groovy。 例:

my_contracts_dir\
  scenario1\
    1_login.groovy
    2_showCart.groovy
    3_logout.groovy

这样的树导致 Spring Cloud Contract Verifier 生成具有 name scenario1的 WireMock 场景以及以下三个步骤:

  • 登录标记为Started指向...

  • showCart 标记为Step1指向...

  • 注销标记为Step2,将关闭方案。

有关 WireMock 方案的更多详细信息,请访问http://wiremock.org/docs/stateful-behaviour/

Spring Cloud Contract Verifier 还会生成具有保证执行顺序的测试。

91.6 Docker 项目

我们发布了一个springcloud/spring-cloud-contract Docker 镜像,其中包含一个项目,该项目将生成测试并在EXPLICIT模式下针对 running application 执行它们。

EXPLICIT模式意味着从 contracts 生成的测试将发送实际请求而不是模拟的请求。

91.6.1 Maven,JARs 和二进制存储的简介

由于非 JVM 项目可以使用 Docker 镜像,因此最好解释 Spring Cloud Contract 打包默认设置背后的基本术语。

部分以下定义取自Maven 词汇表

  • Project:Maven 从项目角度思考。你将 build 的所有东西都是项目。这些项目遵循明确定义的“Project Object Model”。项目可以依赖于其他项目,在这种情况下,后者称为“依赖项”。一个项目可能与几个子项目一致,但这些子项目仍然被视为项目。

  • Artifact:artifact 是由项目生成或使用的东西。 Maven 为项目生成的 artifacts 示例包括:JARs,源代码和二进制分发。每个 artifact 都由 group id 和 artifact ID 唯一标识,该 ID 在 group 中是唯一的。

  • JAR:JAR 代表 Java ARchive。这是一种基于 ZIP 文件格式的格式。 Spring Cloud Contract 将 contracts 和生成的存根打包在 JAR 文件中。

  • GroupId:group ID 是项目的通用唯一标识符。虽然这通常只是项目 name(例如 commons-collections),但使用 fully-qualified package name 将其与具有类似 name(例如 org.apache.maven)的其他项目区分开是有帮助的。通常,当发布到 Artifact Manager 时,GroupId将被斜杠分隔并形成 URL 的一部分。 E.g。对于 group id com.example和 artifact id application将是/com/example/application/

  • Classifier:Maven 依赖表示法如下所示:groupId:artifactId:version:classifier。分类器是传递给依赖项的附加后缀。 E.g。 stubssources。相同的依赖 e.g. com.example:application可以使用分类器生成多个彼此不同的 artifact。

  • Artifact manager:当您生成二进制文件/源/包时,您希望其他人可以下载/ reference 或重用它们。在 JVM 世界的情况下,那些 artifacts 将是 JARs,对于 Ruby 来说这些是宝石,而对于 Docker 来说,那些将是 Docker 镜像。您可以在 manager 中存储这些 artifacts。这种 managers 的例子可以是Artifactory关系

91.6.2 它是如何工作

图像在/contracts文件夹下搜索 contracts。 running 测试的输出将在/spring-cloud-contract/build文件夹下提供(它对调试很有用)。

它足以让您挂载 contracts,传递环境变量,图像将:

  • 生成 contract 测试

  • 根据提供的 URL 执行测试

  • 生成WireMock存根

  • (可选 - 默认情况下处于启用状态)将存根发布到 Artifact Manager

环境变量

Docker 镜像需要一些环境变量来指向__unning application,指向 Artifact manager 实例等。

  • PROJECT_GROUP - 您项目的 group id。默认为com.example

  • PROJECT_VERSION - 您项目的 version。默认为0.0.1-SNAPSHOT

  • PROJECT_NAME - artifact id。默认为example

  • REPO_WITH_BINARIES_URL - Artifact Manager 的 URL。默认为http://localhost:8081/artifactory/libs-release-local,这是Artifactory running 本地的默认 URL

  • 当 Artifact Manager 受到保护时REPO_WITH_BINARIES_USERNAME - (可选)用户名

  • 当 Artifact Manager 受到保护时REPO_WITH_BINARIES_PASSWORD - (可选)密码

  • PUBLISH_ARTIFACTS - 如果设置为true,则会将 artifact 发布到二进制存储。默认为true

当 contracts 位于外部 repository 中时,将使用这些环境变量。要启用此 feature,您必须设置EXTERNAL_CONTRACTS_ARTIFACT_ID环境变量。

  • EXTERNAL_CONTRACTS_GROUP_ID - 带有 contracts 的项目的 group id。默认为com.example

  • EXTERNAL_CONTRACTS_ARTIFACT_ID - 带有 contracts 的项目的 artifact id。

  • EXTERNAL_CONTRACTS_CLASSIFIER - 带有 contracts 的项目分类器。默认为空

  • EXTERNAL_CONTRACTS_VERSION - 带有 contracts 的项目 version。默认为+,相当于选择最新

  • EXTERNAL_CONTRACTS_REPO_WITH_BINARIES_URL - Artifact Manager 的 URL。默认为REPO_WITH_BINARIES_URL env var 的 value。如果未设置,则默认为http://localhost:8081/artifactory/libs-release-local,这是Artifactory running 本地的默认 URL

  • EXTERNAL_CONTRACTS_PATH - 带有 contracts 的项目内给定项目的 contracts 路径。默认为斜杠分隔EXTERNAL_CONTRACTS_GROUP_ID/EXTERNAL_CONTRACTS_ARTIFACT_ID连接。 E.g。对于 group id foo.bar和 artifact id baz,将导致foo/bar/baz contracts 路径。

  • EXTERNAL_CONTRACTS_WORK_OFFLINE - 如果设置为true,则将从容器的.m2中使用 contracts 检索 artifact。将本地.m2挂载为容器/root/.m2路径上的可用卷。您不能同时设置EXTERNAL_CONTRACTS_WORK_OFFLINEEXTERNAL_CONTRACTS_REPO_WITH_BINARIES_URL

执行测试时使用这些环境变量:

  • APPLICATION_BASE_URL - 应该执行测试的 url。请记住,必须可以从 Docker 容器访问它(e.g. localhost将无效)

  • APPLICATION_USERNAME - (可选)用于对 application 进行基本身份验证的用户名

  • APPLICATION_PASSWORD - (可选)用于 application 基本身份验证的密码

91.6.3 使用示例

我们来看一个简单的 MVC application

$ git clone https://github.com/spring-cloud-samples/spring-cloud-contract-nodejs
$ cd bookstore

contracts 在/contracts文件夹下可用。

91.6.4 服务器端(nodejs)

由于我们想要 run 测试,我们可以执行:

$ npm test

但是,出于学习目的,我们将它分成几部分:

# Stop docker infra (nodejs, artifactory)
$ ./stop_infra.sh
# Start docker infra (nodejs, artifactory)
$ ./setup_infra.sh

# Kill & Run app
$ pkill -f "node app"
$ nohup node app &

# Prepare environment variables
$ SC_CONTRACT_DOCKER_VERSION="..."
$ APP_IP="192.168.0.100"
$ APP_PORT="3000"
$ ARTIFACTORY_PORT="8081"
$ APPLICATION_BASE_URL="http://${APP_IP}:${APP_PORT}"
$ ARTIFACTORY_URL="http://${APP_IP}:${ARTIFACTORY_PORT}/artifactory/libs-release-local"
$ CURRENT_DIR="$( pwd )"
$ CURRENT_FOLDER_NAME=${PWD##*/}
$ PROJECT_VERSION="0.0.1.RELEASE"

# Execute contract tests
$ docker run  --rm -e "APPLICATION_BASE_URL=${APPLICATION_BASE_URL}" -e "PUBLISH_ARTIFACTS=true" -e "PROJECT_NAME=${CURRENT_FOLDER_NAME}" -e "REPO_WITH_BINARIES_URL=${ARTIFACTORY_URL}" -e "PROJECT_VERSION=${PROJECT_VERSION}" -v "${CURRENT_DIR}/contracts/:/contracts:ro" -v "${CURRENT_DIR}/node_modules/spring-cloud-contract/output:/spring-cloud-contract-output/" springcloud/spring-cloud-contract:"${SC_CONTRACT_DOCKER_VERSION}"

# Kill app
$ pkill -f "node app"

会发生什么是通过 bash 脚本:

要查看 client 端的外观,请查看第 93.9 节,“Stub Runner Docker”部分。