开始
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.0
2016 年 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 和
它想从我们这里借来的金额。您还想将其发送到/fraudcheck
URL 通过使用
这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 {
. . .
}
使用REMOTE stubsMode 从在线存储库下载存根时,以及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");
}
@SuppressWarnings("rawtypes")
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; import org.junit.Before; import io.restassured.module.mockmvc.RestAssuredMockMvc; public class BaseTestClass { @Before public void setup() { RestAssuredMockMvc.standaloneSetup(new FraudController()); } }
这个最小的类确实是让您的测试正常工作所需的全部。它充当 自动生成的测试附加到的起始位置。
现在我们可以继续实施。为此,我们首先需要一个数据类,我们 然后在我们的控制器中使用。以下列表显示了数据类:
package com.example.Test; import com.fasterxml.jackson.annotation.JsonProperty; 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; import org.springframework.web.bind.annotation.PutMapping; import org.springframework.web.bind.annotation.RequestBody; import org.springframework.web.bind.annotation.RestController; @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 {
. . .
}
使用REMOTE stubsMode 从在线存储库下载存根时,以及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 是关于沟通的。
在这种情况下,生产者拥有合约。从物理上讲,所有合约都在在生产者的存储库中。 |
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 和客户端想要借款的金额的请求。您想将其发送到/fraudcheck
URL 的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-repo
4.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 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>
<!-- <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 Contract
BOM,如下所示:<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}]
此输出表示 Stub Runner 已找到您的存根并为您的应用程序启动了服务器组 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 .添加Base suffix 和
利用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;
import io.restassured.module.mockmvc.RestAssuredMockMvc;
import org.junit.jupiter.api.BeforeEach;
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-pr
4.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
您可以在示例中找到默认项目示例。
你可以在这里找到 Spring Cloud Contract 研讨会。