开始
1. 引入 Spring Cloud Contract
Spring Cloud Contract 将 TDD 提升到了软件架构的层面。 它允许您执行消费者驱动和生产者驱动的合同测试。
1.1. 历史
在成为 Spring Cloud Contract 之前,这个项目被称为 Accurest。 它是由 (Codearte) 的 Marcin Grzejszczak 和 Jakub Kubrynski 创建的。
这0.1.0发布于 2015 年 1 月 26 日,并随着1.0.02016 年 2 月 29 日发布。
1.1.2. 测试问题
如果我们想在前面的图像左上角测试应用程序 部分来确定它是否可以与其他服务通信,我们可以执行以下作之一 两件事:
- 
部署所有微服务并执行端到端测试。 
- 
在单元测试和集成测试中模拟其他微服务。 
两者都有其优点,但也有很多缺点。
部署所有微服务并执行端到端测试
优势:
- 
模拟生产。 
- 
测试服务之间的真实通信。 
弊:
- 
要测试一个微服务,我们必须部署六个微服务、几个数据库、 和其他项目。 
- 
运行测试的环境被锁定为一套测试(没有其他人 将能够同时运行测试)。 
- 
它们需要很长时间才能运行。 
- 
反馈在这个过程中很晚才出现。 
- 
它们极难调试。 
在单元测试和集成测试中模拟其他微服务
优势:
- 
他们提供非常快速的反馈。 
- 
它们没有基础设施要求。 
弊:
- 
服务的实现者创建可能与 现实。 
- 
您可以通过通过测试和失败的生产进入生产环境。 
为了解决上述问题,创建了 Spring Cloud Contract。主要思想是 给你非常快的反馈,无需设置 微服务的整个世界。如果您处理存根,那么您唯一需要的应用程序 是应用程序直接使用的那些。下图显示了关系 存根数:
 
Spring Cloud Contract 为您提供了您使用的存根的确定性 由调用的服务创建。另外,如果你可以使用它们,那就意味着它们 针对制片方进行了测试。简而言之,您可以信任这些存根。
1.2. 目的
Spring Cloud Contract 的主要用途是:
- 
确保 HTTP 和消息存根(在开发客户端时使用)准确执行 实际的服务器端实现的作用。 
- 
推广ATDD(验收测试驱动开发)方法和微服务架构风格。 
- 
提供一种发布合同更改的方法,这些更改在双方都立即可见。 
- 
生成要在服务器端使用的样板测试代码。 
默认情况下,Spring Cloud Contract 与 Wiremock 集成为 HTTP 服务器存根。
| Spring Cloud Contract 的目的不是开始编写业务 合同中的功能。假设我们有一个欺诈检查的业务用例。如果 用户可能出于 100 种不同的原因成为欺诈者,我们假设您将创建两个 合同,一个用于正面情况,一个用于负面情况。合约测试是 用于测试应用程序之间的合约,而不是模拟完整的行为。 | 
1.3. 什么是合约?
作为服务的消费者,我们需要定义我们到底想要实现什么。我们需要 制定我们的期望。这就是我们写合同的原因。换句话说,合同是 关于 API 或消息通信的外观的协议。请考虑以下示例:
假设您要发送包含客户公司 ID 和
它想从我们这里借来的金额。您还想将其发送到/fraudcheckURL 通过使用
这PUT方法。以下列表显示了用于检查客户端是否应
在 Groovy 和 YAML 中都被标记为欺诈:
/*
 * Copyright 2013-2020 the original author or authors.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      https://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package contracts
org.springframework.cloud.contract.spec.Contract.make {
    request { // (1)
        method 'PUT' // (2)
        url '/fraudcheck' // (3)
        body([ // (4)
               "client.id": $(regex('[0-9]{10}')),
               loanAmount : 99999
        ])
        headers { // (5)
            contentType('application/json')
        }
    }
    response { // (6)
        status OK() // (7)
        body([ // (8)
               fraudCheckStatus  : "FRAUD",
               "rejection.reason": "Amount too high"
        ])
        headers { // (9)
            contentType('application/json')
        }
    }
}
/*
From the Consumer perspective, when shooting a request in the integration test:
(1) - If the consumer sends a request
(2) - With the "PUT" method
(3) - to the URL "/fraudcheck"
(4) - with the JSON body that
 * has a field `client.id` that matches a regular expression `[0-9]{10}`
 * has a field `loanAmount` that is equal to `99999`
(5) - with header `Content-Type` equal to `application/json`
(6) - then the response will be sent with
(7) - status equal `200`
(8) - and JSON body equal to
 { "fraudCheckStatus": "FRAUD", "rejectionReason": "Amount too high" }
(9) - with header `Content-Type` equal to `application/json`
From the Producer perspective, in the autogenerated producer-side test:
(1) - A request will be sent to the producer
(2) - With the "PUT" method
(3) - to the URL "/fraudcheck"
(4) - with the JSON body that
 * has a field `client.id` that will have a generated value that matches a regular expression `[0-9]{10}`
 * has a field `loanAmount` that is equal to `99999`
(5) - with header `Content-Type` equal to `application/json`
(6) - then the test will assert if the response has been sent with
(7) - status equal `200`
(8) - and JSON body equal to
 { "fraudCheckStatus": "FRAUD", "rejectionReason": "Amount too high" }
(9) - with header `Content-Type` matching `application/json.*`
 */
request: # (1)
  method: PUT # (2)
  url: /yamlfraudcheck # (3)
  body: # (4)
    "client.id": 1234567890
    loanAmount: 99999
  headers: # (5)
    Content-Type: application/json
  matchers:
    body:
      - path: $.['client.id'] # (6)
        type: by_regex
        value: "[0-9]{10}"
response: # (7)
  status: 200 # (8)
  body:  # (9)
    fraudCheckStatus: "FRAUD"
    "rejection.reason": "Amount too high"
  headers: # (10)
    Content-Type: application/json
#From the Consumer perspective, when shooting a request in the integration test:
#
#(1) - If the consumer sends a request
#(2) - With the "PUT" method
#(3) - to the URL "/yamlfraudcheck"
#(4) - with the JSON body that
# * has a field `client.id`
# * has a field `loanAmount` that is equal to `99999`
#(5) - with header `Content-Type` equal to `application/json`
#(6) - and a `client.id` json entry matches the regular expression `[0-9]{10}`
#(7) - then the response will be sent with
#(8) - status equal `200`
#(9) - and JSON body equal to
# { "fraudCheckStatus": "FRAUD", "rejectionReason": "Amount too high" }
#(10) - with header `Content-Type` equal to `application/json`
#
#From the Producer perspective, in the autogenerated producer-side test:
#
#(1) - A request will be sent to the producer
#(2) - With the "PUT" method
#(3) - to the URL "/yamlfraudcheck"
#(4) - with the JSON body that
# * has a field `client.id` `1234567890`
# * has a field `loanAmount` that is equal to `99999`
#(5) - with header `Content-Type` equal to `application/json`
#(7) - then the test will assert if the response has been sent with
#(8) - status equal `200`
#(9) - and JSON body equal to
# { "fraudCheckStatus": "FRAUD", "rejectionReason": "Amount too high" }
#(10) - with header `Content-Type` equal to `application/json`| 预计合同来自可信来源。您永远不应下载来自不受信任位置的合约,也不应与之交互。 | 
2. 三秒游
这个非常简短的导览将演练如何使用 Spring Cloud Contract。它由 以下主题:
你可以在这里找到一个更长的游览。
下面的 UML 图显示了 Spring Cloud Contract 中各个部分的关系:
 
2.1. 在生产者方面
要开始使用 Spring Cloud Contract,您可以添加带有 REST 或消息传递契约的文件
以 Groovy DSL 或 YAML 表示到 contracts 目录,该目录由contractsDslDir财产。默认情况下,它是$rootDir/src/test/resources/contracts.
然后,您可以将 Spring Cloud Contract Verifier 依赖项和插件添加到构建文件中,如 以下示例显示:
<dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-contract-verifier</artifactId>
    <scope>test</scope>
</dependency>以下列表显示了如何添加插件,该插件应该放在 build/plugins 中 文件的一部分:
<plugin>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-contract-maven-plugin</artifactId>
    <version>${spring-cloud-contract.version}</version>
    <extensions>true</extensions>
</plugin>运行./mvnw clean install自动生成验证应用程序的测试
遵守添加的合同。默认情况下,测试在org.springframework.cloud.contract.verifier.tests..
由于合同中描述的功能尚未实现 现在,测试失败。
要使它们通过,您必须添加处理 HTTP 的正确实现
请求或消息。此外,您必须为自动生成的
测试到项目。此类由所有自动生成的测试扩展,并且它
应包含运行它们所需的所有设置信息(例如RestAssuredMockMvc控制器设置或消息传递测试设置)。
以下示例,来自pom.xml,显示如何指定基测试类:
<build>
        <plugins>
            <plugin>
                <groupId>org.springframework.cloud</groupId>
                <artifactId>spring-cloud-contract-maven-plugin</artifactId>
                <version>2.1.2.RELEASE</version>
                <extensions>true</extensions>
                <configuration>
                    <baseClassForTests>com.example.contractTest.BaseTestClass</baseClassForTests> (1)
                </configuration>
            </plugin>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
            </plugin>
        </plugins>
    </build>| 1 | 这 baseClassForTests元素允许您指定基本测试类。一定是孩子
的configuration元素spring-cloud-contract-maven-plugin. | 
实现和测试基类就位后,测试将通过,并且 应用程序和存根工件在本地 Maven 存储库中构建和安装。 现在可以合并更改,并且可以发布应用程序和存根工件 在在线存储库中。
2.2. 在消费者方面
您可以使用Spring Cloud Contract Stub Runner在集成测试中运行
WireMock 实例或消息传递路由,用于模拟实际服务。
为此,请将依赖项添加到Spring Cloud Contract Stub Runner,作为
以下示例显示:
<dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-contract-stub-runner</artifactId>
    <scope>test</scope>
</dependency>您可以通过以下两种方式之一将生产者端存根安装在 Maven 存储库中 方式:
- 
通过签出 Producer 端存储库并添加合约并生成存根 通过运行以下命令: $ cd local-http-server-repo $ ./mvnw clean install -DskipTests
| 跳过测试,因为生产者端契约实现不是 尚未到位,因此自动生成的合约测试失败。 | 
- 
通过从远程存储库获取现有的生产者服务存根。为此, 将存根工件 ID 和工件存储库 URL 传递为 Spring Cloud Contract Stub Runner属性,如以下示例所示:stubrunner: ids: 'com.example:http-server-dsl:+:stubs:8080' repositoryRoot: https://repo.spring.io/libs-snapshot
现在你可以使用@AutoConfigureStubRunner.在注释中,
提供group-id和artifact-id值Spring Cloud Contract Stub Runner自
为您运行协作者的存根,如以下示例所示:
@RunWith(SpringRunner.class)
@SpringBootTest(webEnvironment=WebEnvironment.NONE)
@AutoConfigureStubRunner(ids = {"com.example:http-server-dsl:+:stubs:6565"},
        stubsMode = StubRunnerProperties.StubsMode.LOCAL)
public class LoanApplicationServiceTests {
    . . .
}
| 使用 REMOTEstubsMode从在线存储库下载存根时,以及LOCAL用于离线工作。 | 
现在,在集成测试中,您可以接收 HTTP 响应的存根版本或 协作者服务预期发出的消息。
3. 开发您的第一个基于 Spring Cloud Contract 的应用程序
本简短的导览将介绍如何使用 Spring Cloud Contract。它由以下主题组成:
您可以在这里找到更简短的游览。
对于这个例子,这个例子,Stub Storage是 Nexus/Artifactory。
下面的 UML 图显示了 Spring Cloud Contract 各个部分的关系:
 
3.1. 在生产者方面
开始使用Spring Cloud Contract,可以添加 Spring Cloud Contract Verifier
依赖项和插件,如以下示例所示:
<dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-contract-verifier</artifactId>
    <scope>test</scope>
</dependency>以下列表显示了如何添加插件,该插件应该放在 build/plugins 中 文件的一部分:
<plugin>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-contract-maven-plugin</artifactId>
    <version>${spring-cloud-contract.version}</version>
    <extensions>true</extensions>
</plugin>| 最简单的入门方法是转到 Spring Initializr 并添加“Web”和“Contract Verifier”作为依赖项。这样做会拉入之前的
提到的依赖项以及您需要的所有其他内容   | 
现在您可以使用REST/消息传递协定
以 Groovy DSL 或 YAML 表示到 contracts 目录,该目录由contractsDslDir财产。默认情况下,它是$rootDir/src/test/resources/contracts.
请注意,文件名无关紧要。您可以在其中组织您的合同
目录,其中包含您喜欢的任何命名方案。
对于 HTTP 存根,契约定义了应为 给定的请求(考虑 HTTP 方法、URL、标头、状态代码等 上)。以下示例显示了 Groovy 和 YAML 中的 HTTP 存根协定:
package contracts
org.springframework.cloud.contract.spec.Contract.make {
    request {
        method 'PUT'
        url '/fraudcheck'
        body([
               "client.id": $(regex('[0-9]{10}')),
               loanAmount: 99999
        ])
        headers {
            contentType('application/json')
        }
    }
    response {
        status OK()
        body([
               fraudCheckStatus: "FRAUD",
               "rejection.reason": "Amount too high"
        ])
        headers {
            contentType('application/json')
        }
    }
}
request:
  method: PUT
  url: /fraudcheck
  body:
    "client.id": 1234567890
    loanAmount: 99999
  headers:
    Content-Type: application/json
  matchers:
    body:
      - path: $.['client.id']
        type: by_regex
        value: "[0-9]{10}"
response:
  status: 200
  body:
    fraudCheckStatus: "FRAUD"
    "rejection.reason": "Amount too high"
  headers:
    Content-Type: application/json;charset=UTF-8如果需要使用消息传递,可以定义:
- 
输入和输出消息(考虑到它从哪里 已发送、邮件正文和标头)。 
- 
收到消息后应调用的方法。 
- 
调用时应触发消息的方法。 
以下示例显示了 Camel 消息传递合约:
def contractDsl = Contract.make {
    name "foo"
    label 'some_label'
    input {
        messageFrom('jms:delete')
        messageBody([
                bookName: 'foo'
        ])
        messageHeaders {
            header('sample', 'header')
        }
        assertThat('bookWasDeleted()')
    }
}
label: some_label
input:
  messageFrom: jms:delete
  messageBody:
    bookName: 'foo'
  messageHeaders:
    sample: header
  assertThat: bookWasDeleted()运行./mvnw clean install自动生成验证应用程序的测试
遵守添加的合同。默认情况下,生成的测试位于org.springframework.cloud.contract.verifier.tests..
生成的测试可能会有所不同,具体取决于您设置的框架和测试类型 在您的插件中。
在下一个列表中,您可以找到:
- 
HTTP 合约的默认测试模式 MockMvc
- 
具有 JAXRS测试模式
- 
一个 WebTestClient基于测试(在使用 反应性的Web-Flux-based applications) 与WEBTESTCLIENT测试模式
- 
基于 Spock 的测试,使用 testFramework属性设置为SPOCK
| 您只需要其中一个测试框架。MockMvc 是默认值。使用一个 在其他框架中,将其库添加到您的类路径中。 | 
以下列表显示了所有框架的示例:
@Test
public void validate_shouldMarkClientAsFraud() throws Exception {
    // given:
        MockMvcRequestSpecification request = given()
                .header("Content-Type", "application/vnd.fraud.v1+json")
                .body("{\"client.id\":\"1234567890\",\"loanAmount\":99999}");
    // when:
        ResponseOptions response = given().spec(request)
                .put("/fraudcheck");
    // then:
        assertThat(response.statusCode()).isEqualTo(200);
        assertThat(response.header("Content-Type")).matches("application/vnd.fraud.v1.json.*");
    // and:
        DocumentContext parsedJson = JsonPath.parse(response.getBody().asString());
        assertThatJson(parsedJson).field("['fraudCheckStatus']").matches("[A-Z]{5}");
        assertThatJson(parsedJson).field("['rejection.reason']").isEqualTo("Amount too high");
}
public class FooTest {
  WebTarget webTarget;
  @Test
  public void validate_() throws Exception {
    // when:
      Response response = webTarget
              .path("/users")
              .queryParam("limit", "10")
              .queryParam("offset", "20")
              .queryParam("filter", "email")
              .queryParam("sort", "name")
              .queryParam("search", "55")
              .queryParam("age", "99")
              .queryParam("name", "Denis.Stepanov")
              .queryParam("email", "[email protected]")
              .request()
              .build("GET")
              .invoke();
      String responseAsString = response.readEntity(String.class);
    // then:
      assertThat(response.getStatus()).isEqualTo(200);
    // and:
      DocumentContext parsedJson = JsonPath.parse(responseAsString);
      assertThatJson(parsedJson).field("['property1']").isEqualTo("a");
  }
}
@Test
    public void validate_shouldRejectABeerIfTooYoung() throws Exception {
        // given:
            WebTestClientRequestSpecification request = given()
                    .header("Content-Type", "application/json")
                    .body("{\"age\":10}");
        // when:
            WebTestClientResponse response = given().spec(request)
                    .post("/check");
        // then:
            assertThat(response.statusCode()).isEqualTo(200);
            assertThat(response.header("Content-Type")).matches("application/json.*");
        // and:
            DocumentContext parsedJson = JsonPath.parse(response.getBody().asString());
            assertThatJson(parsedJson).field("['status']").isEqualTo("NOT_OK");
    }
given:
     ContractVerifierMessage inputMessage = contractVerifierMessaging.create(
        \'\'\'{"bookName":"foo"}\'\'\',
        ['sample': 'header']
    )
when:
     contractVerifierMessaging.send(inputMessage, 'jms:delete')
then:
     noExceptionThrown()
     bookWasDeleted()
由于合同中描述的功能尚未实现 现在,测试失败。
要使它们通过,您必须添加处理 HTTP 的正确实现
请求或消息。此外,您必须为自动生成的
测试到项目。此类由所有自动生成的测试扩展,并且应该
包含运行它们所需的所有设置必要信息(例如,RestAssuredMockMvc控制器设置或消息传递测试设置)。
以下示例,来自pom.xml,显示如何指定基测试类:
<build>
        <plugins>
            <plugin>
                <groupId>org.springframework.cloud</groupId>
                <artifactId>spring-cloud-contract-maven-plugin</artifactId>
                <version>2.1.2.RELEASE</version>
                <extensions>true</extensions>
                <configuration>
                    <baseClassForTests>com.example.contractTest.BaseTestClass</baseClassForTests> (1)
                </configuration>
            </plugin>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
            </plugin>
        </plugins>
    </build>| 1 | 这 baseClassForTests元素允许您指定基本测试类。一定是孩子
的configuration元素spring-cloud-contract-maven-plugin. | 
以下示例显示了一个最小(但功能性)基测试类:
package com.example.contractTest;
public class BaseTestClass {
    @Before
    public void setup() {
        RestAssuredMockMvc.standaloneSetup(new FraudController());
    }
}
这个最小的类确实是让您的测试正常工作所需的全部。它充当 自动生成的测试附加到的起始位置。
现在我们可以继续实施。为此,我们首先需要一个数据类,我们 然后在我们的控制器中使用。以下列表显示了数据类:
package com.example.Test;
public class LoanRequest {
    @JsonProperty("client.id")
    private String clientId;
    private Long loanAmount;
    public String getClientId() {
        return clientId;
    }
    public void setClientId(String clientId) {
        this.clientId = clientId;
    }
    public Long getLoanAmount() {
        return loanAmount;
    }
    public void setLoanRequestAmount(Long loanAmount) {
        this.loanAmount = loanAmount;
    }
}
前面的类提供了一个对象,我们可以在其中存储参数。因为
合约中的客户端 ID 称为client.id,我们需要使用@JsonProperty("client.id")参数将其映射到clientId田。
现在我们可以继续移动控制器,如下表所示:
package com.example.docTest;
@RestController
public class FraudController {
    @PutMapping(value = "/fraudcheck", consumes="application/json", produces="application/json")
    public String check(@RequestBody LoanRequest loanRequest) { (1)
        if (loanRequest.getLoanAmount() > 10000) { (2)
            return "{fraudCheckStatus: FRAUD, rejection.reason: Amount too high}"; (3)
        } else {
            return "{fraudCheckStatus: OK, acceptance.reason: Amount OK}"; (4)
        }
    }
}
| 1 | 我们将传入参数映射到 LoanRequest对象。 | 
| 2 | 我们检查请求的贷款金额,看看是否太多。 | 
| 3 | 如果太多,我们返回 JSON(此处使用简单字符串创建),其中 测试期望。 | 
| 4 | 如果我们有一个测试来捕获该数量何时允许,我们可以将其与此输出相匹配。 | 
这FraudController事情变得非常简单。您可以做更多的事情,包括
日志记录、验证客户端 ID 等。
实现和测试基类就位后,测试将通过,并且 应用程序和存根工件在本地 Maven 存储库中构建和安装。 有关将存根 jar 安装到本地存储库的信息显示在日志中,如 以下示例显示:
[INFO] --- spring-cloud-contract-maven-plugin:1.0.0.BUILD-SNAPSHOT:generateStubs (default-generateStubs) @ http-server ---
[INFO] Building jar: /some/path/http-server/target/http-server-0.0.1-SNAPSHOT-stubs.jar
[INFO]
[INFO] --- maven-jar-plugin:2.6:jar (default-jar) @ http-server ---
[INFO] Building jar: /some/path/http-server/target/http-server-0.0.1-SNAPSHOT.jar
[INFO]
[INFO] --- spring-boot-maven-plugin:1.5.5.BUILD-SNAPSHOT:repackage (default) @ http-server ---
[INFO]
[INFO] --- maven-install-plugin:2.5.2:install (default-install) @ http-server ---
[INFO] Installing /some/path/http-server/target/http-server-0.0.1-SNAPSHOT.jar to /path/to/your/.m2/repository/com/example/http-server/0.0.1-SNAPSHOT/http-server-0.0.1-SNAPSHOT.jar
[INFO] Installing /some/path/http-server/pom.xml to /path/to/your/.m2/repository/com/example/http-server/0.0.1-SNAPSHOT/http-server-0.0.1-SNAPSHOT.pom
[INFO] Installing /some/path/http-server/target/http-server-0.0.1-SNAPSHOT-stubs.jar to /path/to/your/.m2/repository/com/example/http-server/0.0.1-SNAPSHOT/http-server-0.0.1-SNAPSHOT-stubs.jar您现在可以合并更改并发布应用程序和存根工件 在在线存储库中。
3.2. 在消费者方面
您可以在集成测试中使用 Spring Cloud Contract Stub Runner 来运行 WireMock 实例或消息传递路由,用于模拟实际服务。
若要开始,请将依赖项添加到Spring Cloud Contract Stub Runner如下:
<dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-contract-stub-runner</artifactId>
    <scope>test</scope>
</dependency>您可以通过以下两种方式之一将生产者端存根安装在 Maven 存储库中 方式:
- 
通过签出生产者端存储库并添加合约并生成 存根,通过运行以下命令: $ cd local-http-server-repo $ ./mvnw clean install -DskipTests跳过测试,因为生产者端合约实现尚未 ,因此自动生成的合约测试失败。 
- 
通过从远程存储库获取现有的生产者服务存根。为此, 将存根工件 ID 和工件存储库 URL 传递为 Spring Cloud Contract Stub Runner属性,如以下示例所示:stubrunner: ids: 'com.example:http-server-dsl:+:stubs:8080' repositoryRoot: https://repo.spring.io/libs-snapshot
现在你可以使用@AutoConfigureStubRunner.在注释中,
提供group-id和artifact-id为Spring Cloud Contract Stub Runner运行
协作者的存根,如以下示例所示:
@RunWith(SpringRunner.class)
@SpringBootTest(webEnvironment=WebEnvironment.NONE)
@AutoConfigureStubRunner(ids = {"com.example:http-server-dsl:+:stubs:6565"},
        stubsMode = StubRunnerProperties.StubsMode.LOCAL)
public class LoanApplicationServiceTests {
    . . .
}
| 使用 REMOTEstubsMode从在线存储库下载存根时,以及LOCAL用于离线工作。 | 
在集成测试中,您可以接收 HTTP 响应或消息的存根版本 预期由协作者服务发出的。您可以看到类似的条目 到构建日志中的以下内容:
2016-07-19 14:22:25.403  INFO 41050 --- [           main] o.s.c.c.stubrunner.AetherStubDownloader  : Desired version is + - will try to resolve the latest version
2016-07-19 14:22:25.438  INFO 41050 --- [           main] o.s.c.c.stubrunner.AetherStubDownloader  : Resolved version is 0.0.1-SNAPSHOT
2016-07-19 14:22:25.439  INFO 41050 --- [           main] o.s.c.c.stubrunner.AetherStubDownloader  : Resolving artifact com.example:http-server:jar:stubs:0.0.1-SNAPSHOT using remote repositories []
2016-07-19 14:22:25.451  INFO 41050 --- [           main] o.s.c.c.stubrunner.AetherStubDownloader  : Resolved artifact com.example:http-server:jar:stubs:0.0.1-SNAPSHOT to /path/to/your/.m2/repository/com/example/http-server/0.0.1-SNAPSHOT/http-server-0.0.1-SNAPSHOT-stubs.jar
2016-07-19 14:22:25.465  INFO 41050 --- [           main] o.s.c.c.stubrunner.AetherStubDownloader  : Unpacking stub from JAR [URI: file:/path/to/your/.m2/repository/com/example/http-server/0.0.1-SNAPSHOT/http-server-0.0.1-SNAPSHOT-stubs.jar]
2016-07-19 14:22:25.475  INFO 41050 --- [           main] o.s.c.c.stubrunner.AetherStubDownloader  : Unpacked file to [/var/folders/0p/xwq47sq106x1_g3dtv6qfm940000gq/T/contracts100276532569594265]
2016-07-19 14:22:27.737  INFO 41050 --- [           main] o.s.c.c.stubrunner.StubRunnerExecutor    : All stubs are now running RunningStubs [namesAndPorts={com.example:http-server:0.0.1-SNAPSHOT:stubs=8080}]4. 消费者驱动合同 (CDC) 分步指南,生产方合同
考虑欺诈检测和贷款发放流程的示例。业务 我们想向人们发放贷款,但又不希望他们从中窃取贷款 我们。我们系统目前的实施向每个人提供贷款。
假设Loan Issuance是Fraud Detection服务器。在当前
sprint,我们必须开发一个新功能:如果客户想借太多钱,
我们将客户标记为欺诈者。
技术备注
- 
欺诈检测有一个 artifact-id之http-server.
- 
贷款发放有一个 artifact-id之http-client.
- 
两者都有一个 group-id之com.example.
- 
对于这个例子,这个例子, Stub Storage是 Nexus/Artifactory。
社交评论
- 
客户端和服务器开发团队都需要直接沟通,并且 在完成该过程的同时讨论更改。 
- 
CDC 是关于沟通的。 
服务器端代码可在 Spring Cloud Contract 的存储库中找到samples/standalone/dsl/http-server路径,客户端代码在 Spring Cloud Contract 的存储库下可用samples/standalone/dsl/http-client路径。
| 在这种情况下,生产者拥有合同。从物理上讲,所有合同都是 在生产者的存储库中。 | 
4.1. 技术说明
如果您使用 SNAPSHOT、Milestone 或 Release Candidate 版本,则需要将 以下部分:
<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>repositories {
    mavenCentral()
    mavenLocal()
    maven { url "https://repo.spring.io/snapshot" }
    maven { url "https://repo.spring.io/milestone" }
    maven { url "https://repo.spring.io/release" }
}
为简单起见,我们使用以下首字母缩略词:
- 
贷款发放 (LI):HTTP 客户端 
- 
欺诈检测 (FD):HTTP 服务器 
- 
SCC:Spring Cloud 合约 
4.2. 消费者方(贷款发放)
作为贷款发放服务的开发人员(欺诈检测服务器的使用者),您可以执行以下步骤:
- 
通过为您的功能编写测试来开始执行 TDD。 
- 
编写缺少的实现。 
- 
在本地克隆欺诈检测服务存储库。 
- 
在欺诈检测服务的存储库中本地定义合同。 
- 
添加 Spring Cloud Contract (SCC) 插件。 
- 
运行集成测试。 
- 
提交拉取请求。 
- 
创建初始实现。 
- 
接管拉取请求。 
- 
编写缺少的实现。 
- 
部署您的应用程序。 
- 
在线工作。 
我们从贷款发放流程开始,如下图所示:
 
4.2.1. 通过为你的功能编写一个测试来开始做 TDD
以下列表显示了一个测试,我们可能会用它来检查贷款金额是否太过 大:
@Test
public void shouldBeRejectedDueToAbnormalLoanAmount() {
    // given:
    LoanApplication application = new LoanApplication(new Client("1234567890"),
            99999);
    // when:
    LoanApplicationResult loanApplication = service.loanApplication(application);
    // then:
    assertThat(loanApplication.getLoanApplicationStatus())
            .isEqualTo(LoanApplicationStatus.LOAN_APPLICATION_REJECTED);
    assertThat(loanApplication.getRejectionReason()).isEqualTo("Amount too high");
}
假设你已经编写了新功能的测试。如果贷款申请大额 收到金额时,系统应拒绝该贷款申请并附上一些描述。
4.2.2. 编写缺失的实现
在某个时间点,您需要向欺诈检测服务发送请求。假设
您需要发送包含客户端 ID 和金额的请求
客户想借。您想将其发送到/fraudcheckURL 的PUT方法。
为此,可以使用类似于以下内容的代码:
ResponseEntity<FraudServiceResponse> response = restTemplate.exchange(
        "http://localhost:" + port + fraudCheck(), HttpMethod.PUT,
        new HttpEntity<>(request, httpHeaders), FraudServiceResponse.class);
为简单起见,欺诈检测服务的端口设置为8080和
应用程序运行在8090.
| 如果此时启动测试,它会中断,因为当前没有服务在端口上运行 8080. | 
4.2.3. 在本地克隆 Fraud Detection 服务存储库
您可以从尝试服务器端合约开始。为此,您必须首先 通过运行以下命令来克隆它:
$ git clone https://your-git-server.com/server-side.git local-http-server-repo4.2.4. 在欺诈检测服务的存储库中本地定义合约
作为消费者,您需要定义您到底想要实现什么。你需要制定 你的期望。为此,请编写以下合同:
| 将合约放在 src/test/resources/contracts/fraud文件夹。这fraud文件夹
很重要,因为生产者的测试基类名称引用了该文件夹。 | 
以下示例显示了我们的合约,在 Groovy 和 YAML 中:
/*
 * Copyright 2013-2020 the original author or authors.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      https://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package contracts
org.springframework.cloud.contract.spec.Contract.make {
    request { // (1)
        method 'PUT' // (2)
        url '/fraudcheck' // (3)
        body([ // (4)
               "client.id": $(regex('[0-9]{10}')),
               loanAmount : 99999
        ])
        headers { // (5)
            contentType('application/json')
        }
    }
    response { // (6)
        status OK() // (7)
        body([ // (8)
               fraudCheckStatus  : "FRAUD",
               "rejection.reason": "Amount too high"
        ])
        headers { // (9)
            contentType('application/json')
        }
    }
}
/*
From the Consumer perspective, when shooting a request in the integration test:
(1) - If the consumer sends a request
(2) - With the "PUT" method
(3) - to the URL "/fraudcheck"
(4) - with the JSON body that
 * has a field `client.id` that matches a regular expression `[0-9]{10}`
 * has a field `loanAmount` that is equal to `99999`
(5) - with header `Content-Type` equal to `application/json`
(6) - then the response will be sent with
(7) - status equal `200`
(8) - and JSON body equal to
 { "fraudCheckStatus": "FRAUD", "rejectionReason": "Amount too high" }
(9) - with header `Content-Type` equal to `application/json`
From the Producer perspective, in the autogenerated producer-side test:
(1) - A request will be sent to the producer
(2) - With the "PUT" method
(3) - to the URL "/fraudcheck"
(4) - with the JSON body that
 * has a field `client.id` that will have a generated value that matches a regular expression `[0-9]{10}`
 * has a field `loanAmount` that is equal to `99999`
(5) - with header `Content-Type` equal to `application/json`
(6) - then the test will assert if the response has been sent with
(7) - status equal `200`
(8) - and JSON body equal to
 { "fraudCheckStatus": "FRAUD", "rejectionReason": "Amount too high" }
(9) - with header `Content-Type` matching `application/json.*`
 */
request: # (1)
  method: PUT # (2)
  url: /yamlfraudcheck # (3)
  body: # (4)
    "client.id": 1234567890
    loanAmount: 99999
  headers: # (5)
    Content-Type: application/json
  matchers:
    body:
      - path: $.['client.id'] # (6)
        type: by_regex
        value: "[0-9]{10}"
response: # (7)
  status: 200 # (8)
  body:  # (9)
    fraudCheckStatus: "FRAUD"
    "rejection.reason": "Amount too high"
  headers: # (10)
    Content-Type: application/json
#From the Consumer perspective, when shooting a request in the integration test:
#
#(1) - If the consumer sends a request
#(2) - With the "PUT" method
#(3) - to the URL "/yamlfraudcheck"
#(4) - with the JSON body that
# * has a field `client.id`
# * has a field `loanAmount` that is equal to `99999`
#(5) - with header `Content-Type` equal to `application/json`
#(6) - and a `client.id` json entry matches the regular expression `[0-9]{10}`
#(7) - then the response will be sent with
#(8) - status equal `200`
#(9) - and JSON body equal to
# { "fraudCheckStatus": "FRAUD", "rejectionReason": "Amount too high" }
#(10) - with header `Content-Type` equal to `application/json`
#
#From the Producer perspective, in the autogenerated producer-side test:
#
#(1) - A request will be sent to the producer
#(2) - With the "PUT" method
#(3) - to the URL "/yamlfraudcheck"
#(4) - with the JSON body that
# * has a field `client.id` `1234567890`
# * has a field `loanAmount` that is equal to `99999`
#(5) - with header `Content-Type` equal to `application/json`
#(7) - then the test will assert if the response has been sent with
#(8) - status equal `200`
#(9) - and JSON body equal to
# { "fraudCheckStatus": "FRAUD", "rejectionReason": "Amount too high" }
#(10) - with header `Content-Type` equal to `application/json`YML 合约非常简单。但是,当您查看合同时
使用静态类型的 Groovy DSL 编写,您可能想知道value(client(…), server(…))零件是。通过使用这种表示法,Spring Cloud
Contract 允许您定义 JSON 块、URL 或其他动态结构的各个部分。在这种情况下
标识符或时间戳,则无需对值进行硬编码。您想允许一些
不同的值范围。要启用值范围,您可以设置正则表达式
与使用者端的这些值匹配。您可以通过以下任一方式提供身体
映射表示法或带有插值的字符串。我们强烈建议使用地图表示法。
| 要设置合同,您必须了解地图表示法。请参阅有关 JSON 的 Groovy 文档。 | 
前面显示的合同是双方之间的协议,包括:
- 
如果发送 HTTP 请求时包含以下所有内容: - 
一个 PUT方法/fraudcheck端点
- 
带有 client.id与正则表达式匹配的[0-9]{10}和loanAmount等于99999
- 
一个 Content-Type标头,值为application/vnd.fraud.v1+json
 
- 
- 
然后向消费者发送 HTTP 响应,该 - 
有地位 200
- 
包含一个 JSON 正文,其中包含 fraudCheckStatus字段包含FRAUD和 这rejectionReason值为Amount too high
- 
有一个 Content-Type标头,值为application/vnd.fraud.v1+json
 
- 
准备好在集成测试中实际检查 API 后,您需要 在本地安装存根。
4.2.5. 添加 Spring Cloud Contract Verifier 插件
我们可以添加 Maven 或 Gradle 插件。在此示例中,我们展示了如何添加 Maven。
首先,我们将Spring Cloud ContractBOM,如以下示例所示:
<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 VerifierMaven 插件,如以下示例所示:
            <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>
                <!-- if additional dependencies are needed e.g. for Pact -->
                <dependencies>
                    <dependency>
                        <groupId>org.springframework.cloud</groupId>
                        <artifactId>spring-cloud-contract-pact</artifactId>
                        <version>${spring-cloud-contract.version}</version>
                    </dependency>
                </dependencies>
            </plugin>由于添加了插件,您将获得Spring Cloud Contract Verifier功能,其中,从提供的合同中:
- 
生成和运行测试 
- 
生成和安装存根 
您不想生成测试,因为您作为消费者只想使用 存根。 您需要跳过测试生成和调用。为此,请运行以下命令:
$ cd local-http-server-repo
$ ./mvnw clean install -DskipTests运行这些命令后,您应该在日志中看到类似以下内容的内容:
[INFO] --- spring-cloud-contract-maven-plugin:1.0.0.BUILD-SNAPSHOT:generateStubs (default-generateStubs) @ http-server ---
[INFO] Building jar: /some/path/http-server/target/http-server-0.0.1-SNAPSHOT-stubs.jar
[INFO]
[INFO] --- maven-jar-plugin:2.6:jar (default-jar) @ http-server ---
[INFO] Building jar: /some/path/http-server/target/http-server-0.0.1-SNAPSHOT.jar
[INFO]
[INFO] --- spring-boot-maven-plugin:1.5.5.BUILD-SNAPSHOT:repackage (default) @ http-server ---
[INFO]
[INFO] --- maven-install-plugin:2.5.2:install (default-install) @ http-server ---
[INFO] Installing /some/path/http-server/target/http-server-0.0.1-SNAPSHOT.jar to /path/to/your/.m2/repository/com/example/http-server/0.0.1-SNAPSHOT/http-server-0.0.1-SNAPSHOT.jar
[INFO] Installing /some/path/http-server/pom.xml to /path/to/your/.m2/repository/com/example/http-server/0.0.1-SNAPSHOT/http-server-0.0.1-SNAPSHOT.pom
[INFO] Installing /some/path/http-server/target/http-server-0.0.1-SNAPSHOT-stubs.jar to /path/to/your/.m2/repository/com/example/http-server/0.0.1-SNAPSHOT/http-server-0.0.1-SNAPSHOT-stubs.jar以下行非常重要:
[INFO] Installing /some/path/http-server/target/http-server-0.0.1-SNAPSHOT-stubs.jar to /path/to/your/.m2/repository/com/example/http-server/0.0.1-SNAPSHOT/http-server-0.0.1-SNAPSHOT-stubs.jar它确认http-server已安装在本地 存储 库。
4.2.6. 运行集成测试
为了从自动存根下载的 Spring Cloud Contract Stub Runner 功能中获利,您必须在消费者端项目(Loan
Application service):
- 
添加 Spring Cloud ContractBOM,如下所示:<dependencyManagement> <dependencies> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-dependencies</artifactId> <version>${spring-cloud-release-train.version}</version> <type>pom</type> <scope>import</scope> </dependency> </dependencies> </dependencyManagement>
- 
将依赖项添加到 Spring Cloud Contract Stub Runner如下:<dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-contract-stub-runner</artifactId> <scope>test</scope> </dependency>
- 
使用 @AutoConfigureStubRunner.在注释中,提供group-id和artifact-id让存根运行器下载您的存根 合作。@SpringBootTest(webEnvironment = WebEnvironment.NONE) @AutoConfigureStubRunner(ids = { "com.example:http-server-dsl:0.0.1:stubs"}, stubsMode = StubRunnerProperties.StubsMode.LOCAL) public class LoanApplicationServiceTests {
- 
(可选)因为您离线与协作者一起玩,所以您 还可以提供离线工作开关( StubRunnerProperties.StubsMode.LOCAL).
现在,当您运行测试时,您会在日志中看到类似以下内容的输出:
2016-07-19 14:22:25.403  INFO 41050 --- [           main] o.s.c.c.stubrunner.AetherStubDownloader  : Desired version is + - will try to resolve the latest version
2016-07-19 14:22:25.438  INFO 41050 --- [           main] o.s.c.c.stubrunner.AetherStubDownloader  : Resolved version is 0.0.1-SNAPSHOT
2016-07-19 14:22:25.439  INFO 41050 --- [           main] o.s.c.c.stubrunner.AetherStubDownloader  : Resolving artifact com.example:http-server:jar:stubs:0.0.1-SNAPSHOT using remote repositories []
2016-07-19 14:22:25.451  INFO 41050 --- [           main] o.s.c.c.stubrunner.AetherStubDownloader  : Resolved artifact com.example:http-server:jar:stubs:0.0.1-SNAPSHOT to /path/to/your/.m2/repository/com/example/http-server/0.0.1-SNAPSHOT/http-server-0.0.1-SNAPSHOT-stubs.jar
2016-07-19 14:22:25.465  INFO 41050 --- [           main] o.s.c.c.stubrunner.AetherStubDownloader  : Unpacking stub from JAR [URI: file:/path/to/your/.m2/repository/com/example/http-server/0.0.1-SNAPSHOT/http-server-0.0.1-SNAPSHOT-stubs.jar]
2016-07-19 14:22:25.475  INFO 41050 --- [           main] o.s.c.c.stubrunner.AetherStubDownloader  : Unpacked file to [/var/folders/0p/xwq47sq106x1_g3dtv6qfm940000gq/T/contracts100276532569594265]
2016-07-19 14:22:27.737  INFO 41050 --- [           main] o.s.c.c.stubrunner.StubRunnerExecutor    : All stubs are now running RunningStubs [namesAndPorts={com.example:http-server:0.0.1-SNAPSHOT:stubs=8080}]此输出意味着存根运行程序已找到存根并为应用程序启动服务器
组 ID 为com.example项目 ID 为http-server带版本0.0.1-SNAPSHOT之
存根和stubs端口上的分类器8080.
4.2.7. 提交拉取请求
到目前为止,你所做的是一个迭代过程。你可以尝试一下contract,在本地安装它,并在消费者端工作,直到合约像你希望的那样工作。
对结果感到满意并且测试通过后,可以将拉取请求发布到 服务器端。目前,消费者方面的工作已经完成。
4.3. 生产者方(欺诈检测服务器)
作为欺诈检测服务器(贷款发放服务的服务器)的开发人员,您 可能想要:
- 
接管拉取请求 
- 
编写缺少的实现 
- 
部署应用程序 
以下 UML 图显示了欺诈检测流:
 
4.3.1. 接管拉取请求
提醒一下,以下列表显示了初始实现:
@RequestMapping(value = "/fraudcheck", method = PUT)
public FraudCheckResult fraudCheck(@RequestBody FraudCheck fraudCheck) {
return new FraudCheckResult(FraudCheckStatus.OK, NO_REASON);
}
然后,您可以运行以下命令:
$ git checkout -b contract-change-pr master
$ git pull https://your-git-server.com/server-side-fork.git contract-change-pr必须添加自动生成的测试所需的依赖项,如下所示:
<dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-contract-verifier</artifactId>
    <scope>test</scope>
</dependency>在 Maven 插件的配置中,必须将packageWithBaseClasses属性,如下所示:
            <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>
                <!-- if additional dependencies are needed e.g. for Pact -->
                <dependencies>
                    <dependency>
                        <groupId>org.springframework.cloud</groupId>
                        <artifactId>spring-cloud-contract-pact</artifactId>
                        <version>${spring-cloud-contract.version}</version>
                    </dependency>
                </dependencies>
            </plugin>| 此示例通过设置 packageWithBaseClasses财产。这样做意味着最后两个包组合为
创建基测试类的名称。在我们的案例中,合同是根据src/test/resources/contracts/fraud.由于您没有两个从
这contracts文件夹,只选择一个,它应该是fraud.添加Basesuffix 和
利用fraud.这给了你FraudBase测试类名称。 | 
所有生成的测试都扩展了该类。在那里,您可以设置 Spring Context
或任何必要的。在这种情况下,您应该使用 Rest Assured MVC 来
启动服务器端FraudDetectionController.以下列表显示了FraudBase类:
/*
 * Copyright 2013-2020 the original author or authors.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      https://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package com.example.fraud;
public class FraudBase {
    @BeforeEach
    public void setup() {
        RestAssuredMockMvc.standaloneSetup(new FraudDetectionController(),
                new FraudStatsController(stubbedStatsProvider()));
    }
    private StatsProvider stubbedStatsProvider() {
        return fraudType -> {
            switch (fraudType) {
            case DRUNKS:
                return 100;
            case ALL:
                return 200;
            }
            return 0;
        };
    }
    public void assertThatRejectionReasonIsNull(Object rejectionReason) {
        assert rejectionReason == null;
    }
}
现在,如果您运行./mvnw clean install,您将得到类似于以下输出的内容:
Results :
Tests in error:
  ContractVerifierTest.validate_shouldMarkClientAsFraud:32 » IllegalState Parsed...发生此错误的原因是,您有一个从中生成测试的新协定,并且它 失败,因为您尚未实现该功能。自动生成的测试将看起来 像下面的测试方法:
@Test
public void validate_shouldMarkClientAsFraud() throws Exception {
    // given:
        MockMvcRequestSpecification request = given()
                .header("Content-Type", "application/vnd.fraud.v1+json")
                .body("{\"client.id\":\"1234567890\",\"loanAmount\":99999}");
    // when:
        ResponseOptions response = given().spec(request)
                .put("/fraudcheck");
    // then:
        assertThat(response.statusCode()).isEqualTo(200);
        assertThat(response.header("Content-Type")).matches("application/vnd.fraud.v1.json.*");
    // and:
        DocumentContext parsedJson = JsonPath.parse(response.getBody().asString());
        assertThatJson(parsedJson).field("['fraudCheckStatus']").matches("[A-Z]{5}");
        assertThatJson(parsedJson).field("['rejection.reason']").isEqualTo("Amount too high");
}
如果您使用 Groovy DSL,您可以看到所有producer()合同中存在的部分value(consumer(…), producer(…))块被注入到测试中。
如果您使用 YAML,则这同样适用于matchers部分response.
请注意,在生产者端,您也在做 TDD。表达了期望
以测试的形式。此测试向我们自己的应用程序发送一个请求,其中包含 URL
headers 和合约中定义的正文。它还需要精确定义的值
在回应中。换句话说,你有red部分red,green和refactor.是时候将red进入green.
4.3.2. 编写缺失的实现
因为你知道预期的输入和预期的输出,所以你可以写缺失的 实现如下:
@RequestMapping(value = "/fraudcheck", method = PUT)
public FraudCheckResult fraudCheck(@RequestBody FraudCheck fraudCheck) {
if (amountGreaterThanThreshold(fraudCheck)) {
    return new FraudCheckResult(FraudCheckStatus.FRAUD, AMOUNT_TOO_HIGH);
}
return new FraudCheckResult(FraudCheckStatus.OK, NO_REASON);
}
当你运行时./mvnw clean install再次,测试通过了。自 Spring Cloud 以来
Contract Verifier 插件将测试添加到generated-test-sources您可以
实际从您的 IDE 运行这些测试。
4.3.3. 部署应用程序
完成工作后,您可以部署更改。为此,您必须首先合并 分支,运行以下命令:
$ git checkout master
$ git merge --no-ff contract-change-pr
$ git push origin master您的 CI 可能会运行一个命令,例如./mvnw clean deploy,这将同时发布
应用程序和存根工件。
4.4. 消费者端(贷款发放),最后一步
作为贷款发放服务的开发人员(欺诈检测服务器的使用者),您需要:
- 
将我们的功能分支合并到 master
- 
切换到在线工作模式 
以下 UML 图显示了进程的最终状态:
 
4.4.1. 将分支合并到 master
以下命令显示了使用 Git 将分支合并到 master 中的一种方法:
$ git checkout master
$ git merge --no-ff contract-change-pr4.4.2. 在线工作
现在您可以禁用 Spring Cloud Contract Stub Runner 的离线工作,并指示
包含存根的存储库所在的位置。此时,服务器的存根
side 会自动从 Nexus/Artifactory 下载。您可以设置stubsMode自REMOTE.以下代码显示了
通过更改属性来实现相同的效果:
stubrunner:
  ids: 'com.example:http-server-dsl:+:stubs:8080'
  repositoryRoot: https://repo.spring.io/libs-snapshot就是这样。 您已完成本教程。
5. 后续步骤
希望本节提供了一些 Spring Cloud Contract 基础知识,并能帮助您上路 编写自己的应用程序。如果您是面向任务的开发人员类型,您可能 想要跳转到 spring.io 并查看一些入门指南,这些指南可以解决特定的“我该怎么做 与Spring?我们还有特定于 Spring Cloud Contract 的 “how-to”参考文档。
否则,下一个合乎逻辑的步骤是阅读使用 Spring Cloud Contract。 如果 你真的很不耐烦,你也可以跳到前面阅读有关 Spring Cloud Contract 功能的信息。
此外,您还可以查看以下视频:
- 
“消费者驱动的契约和微服务架构”,作者:Olga Maciaszek-Sharma 和 Marcin Grzejszczak 
- 
Marcin Grzejszczak 的“企业合同测试” 
- 
“为什么合同测试很重要?” 作者:Marcin Grzejszczak 
您可以在示例中找到默认项目示例。
