
本节提供一些常见“我该怎么做...”问题的答案。 使用春云合约时经常出现这种情况。它的覆盖范围并不详尽,但它 确实涵盖了很多内容。
如果您有我们此处未涵盖的特定问题,您可能需要查看stackoverflow.com以查看是否有人有 已经提供了答案。堆栈溢出也是提出新问题的好地方(请使用 标签)。spring-cloud-contract
我们也非常乐意扩展此部分。如果要添加“操作方法”, 向我们发送拉取请求。
1. 为什么要使用春云合约?
春季云合约在多语言环境中运行良好。这个项目有很多 非常有趣的功能。其中相当多的功能肯定会使 春云合约验证器在消费者驱动合约市场上脱颖而出 (疾控中心)工具。最有趣的功能包括:
- 能够通过消息传递进行 CDC。
- 清晰易用的静态类型DSL。
- 能够将当前 JSON 文件复制粘贴到合约中并仅编辑其元素。
- 从定义的合约自动生成测试。
- 存根运行器功能:存根在运行时自动从 Nexus 或 Artifactory 下载。
- Spring 云集成:集成测试不需要发现服务。
- Spring Cloud Contract 与 Pact 集成,并提供简单的钩子来扩展其功能。
- 能够通过Docker添加对任何语言和框架的支持。
2. 我怎样才能用Groovy以外的语言写合同?
您可以在 YAML 中编写协定。有关详细信息,请参阅此部分。
我们正在努力允许更多描述合同的方式。您可以查看github-issue以获取更多信息。
3. 如何为合约提供动态值?
与存根相关的最大挑战之一是它们的可重用性。只有当它们能够被广泛使用时,它们才能达到其目的。 请求和响应元素的硬编码值(如日期和 ID)通常使这变得困难。 请考虑以下 JSON 请求:
{
"time" : "2016-10-10 20:10:15",
"id" : "9febab1c-6f36-4a0b-88d6-3b6a6d81cd4a",
"body" : "foo"
}
现在考虑以下 JSON 响应:
{
"time" : "2016-10-10 21:10:15",
"id" : "c4231e1f-3ca9-48d3-b7e7-567d55f0d051",
"body" : "bar"
}
想象一下设置字段的正确值所需的痛苦(假设此内容由 数据库),通过更改系统中的时钟或提供数据提供程序的存根实现。同样是相关的 到田野。您可以创建 UUID 生成器的存根实现,但这样做没有意义。time
id
因此,作为使用者,您希望发送与任何形式的时间或任何 UUID 匹配的请求。这样,您的系统就会 像往常一样工作,无需存根即可生成数据。假设,在上述情况下 JSON,最重要的部分是字段。您可以专注于此并为其他字段提供匹配。换句话说, 您希望存根按如下方式工作:body
{
"time" : "SOMETHING THAT MATCHES TIME",
"id" : "SOMETHING THAT MATCHES UUID",
"body" : "foo"
}
就回应而言,作为消费者,你需要一个可以操作的具体价值。 因此,以下 JSON 有效:
{
"time" : "2016-10-10 21:10:15",
"id" : "c4231e1f-3ca9-48d3-b7e7-567d55f0d051",
"body" : "bar"
}
在前面的部分中,我们从合约生成了测试。所以,从制片人的角度来看,情况看起来 大不相同。我们解析提供的协定,并且在测试中,我们希望向终结点发送真实请求。 因此,对于请求的生产者的情况,我们不能进行任何形式的匹配。我们需要具体的价值观,在这些价值观上 生产者的后端可以工作。因此,以下 JSON 将有效:
{
"time" : "2016-10-10 20:10:15",
"id" : "9febab1c-6f36-4a0b-88d6-3b6a6d81cd4a",
"body" : "foo"
}
另一方面,从合同有效性的角度来看,响应不一定非要 包含 foror 的具体值。假设您在生产者端生成这些。再一次,你 必须做大量的存根以确保您始终返回相同的值。这就是为什么,从制片人的角度来看, 您可能需要以下响应:time
id
{
"time" : "SOMETHING THAT MATCHES TIME",
"id" : "SOMETHING THAT MATCHES UUID",
"body" : "bar"
}
那么,如何为消费者提供匹配器,为生产者提供具体价值(在其他时间则相反)? Spring 云合约允许您提供动态值。这意味着两者可能不同 沟通的双方。
您可以在合同 DSL部分阅读有关此内容的更多信息。
阅读与 JSON 相关的 Groovy 文档,了解如何 正确构建请求和响应正文。
|
4. 如何进行存根版本控制?
本节介绍存根的版本控制,您可以通过多种不同的方式处理这些版本控制:
- API 版本控制
- JAR 版本控制
- 开发或生产存根
4.1. API 版本控制
版本控制的真正含义是什么?如果您参考 API 版本,则有 不同的方法:
- 使用超媒体链接,不要以任何方式对您的 API 进行版本控制
- 通过标头和 URL 传递版本
我们不试图回答哪种方法更好的问题。你应该选择任何 满足您的需求,让您创造商业价值。
假设你对 API 进行了版本控制。在这种情况下,应提供尽可能多的合同,其中包含尽可能多的版本。 您可以为每个版本创建一个子文件夹,也可以将其附加到合约名称中 - 最适合您的名称。
4.2.JAR 版本控制
如果通过版本控制,您指的是包含存根的 JAR 版本,那么基本上有两种主要方法。
假设您执行持续交付和部署,这意味着您生成新版本的 每次通过管道时,罐子都可以随时投入生产。例如,您的 jar 版本 看起来像以下内容(因为它是在 20.10.2016 at 20:15:21 上构建的):
1.0.0.20161020-201521-RELEASE
在这种情况下,生成的存根 jar 应如下所示:
1.0.0.20161020-201521-RELEASE-stubs.jar
在这种情况下,你应该,在你的 引用存根,提供最新版本的存根。您可以通过传递标志来做到这一点。以下示例演示如何执行此操作:application.yml
@AutoConfigureStubRunner
+
@AutoConfigureStubRunner(ids = {"com.example:http-server-dsl:+:stubs:8080"})
但是,如果版本控制是固定的(例如,或),则必须设置 jar 的具体值 版本。以下示例显示了如何在版本 2.1.1 中执行此操作:1.0.4.RELEASE
2.1.1
@AutoConfigureStubRunner(ids = {"com.example:http-server-dsl:2.1.1:stubs:8080"})
4.3. 开发或生产存根
您可以操作分类器以针对当前开发版本运行测试 其他服务的存根或部署到生产的存根。如果您更改 您的构建,以便在进入生产环境后使用分类器部署存根 部署,您可以在一种情况下使用开发存根运行测试,在另一种情况下使用生产存根运行测试。prod-stubs
以下示例适用于使用存根开发版本的测试:
@AutoConfigureStubRunner(ids = {"com.example:http-server-dsl:+:stubs:8080"})
以下示例适用于使用存根生产版本的测试:
@AutoConfigureStubRunner(ids = {"com.example:http-server-dsl:+:prod-stubs:8080"})
还可以在部署管道的属性中传递这些值。
5. 如何使用带有合约的通用存储库,而不是将它们存储在生产者那里?
存储合同的另一种方式,而不是与制片人签订合同,是保留 他们在一个共同的地方。这种情况可能与安全问题有关(其中 使用者不能克隆生产者的代码)。此外,如果您将合同保存在一个地方, 那么你,作为一个生产者,知道你有多少消费者,你可能会打破哪个消费者 与您的本地更改。
5.1. 存储库结构
假设我们有一个坐标为 of 和 3 的生产者 消费者:,,和。然后,在存储库中具有共同的 合同,您可以进行以下设置(您可以在此处查看)。 下面的清单显示了这样的结构:com.example:server
client1
client2
client3
├── com
│ └── example
│ └── server
│ ├── client1
│ │ └── expectation.groovy
│ ├── client2
│ │ └── expectation.groovy
│ ├── client3
│ │ └── expectation.groovy
│ └── pom.xml
├── mvnw
├── mvnw.cmd
├── pom.xml
└── src
└── assembly
└── contracts.xml
在斜杠分隔文件夹 () 下,您有 三个消费者的期望(,和)。期望是标准的Groovy DSL 合同文件,如本文档中所述。此存储库必须生成一个映射的 JAR 文件 一对一地访问存储库的内容。groupid/artifact id
com/example/server
client1
client2
client3
以下示例显示文件夹中的文件:pom.xml
server
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns="http://maven.apache.org/POM/4.0.0"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>com.example</groupId>
<artifactId>server</artifactId>
<version>0.0.1</version>
<name>Server Stubs</name>
<description>POM used to install locally stubs for consumer side</description>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.6.11</version>
<relativePath/>
</parent>
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<java.version>1.8</java.version>
<spring-cloud-contract.version>3.1.5-SNAPSHOT</spring-cloud-contract.version>
<spring-cloud-release.version>2021.0.5-SNAPSHOT</spring-cloud-release.version>
<excludeBuildFolders>true</excludeBuildFolders>
</properties>
<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>
<build>
<plugins>
<plugin>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-contract-maven-plugin</artifactId>
<version>${spring-cloud-contract.version}</version>
<extensions>true</extensions>
<configuration>
<!-- By default it would search under src/test/resources/ -->
<contractsDirectory>${project.basedir}</contractsDirectory>
</configuration>
</plugin>
</plugins>
</build>
<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>
</project>
除了 Spring Cloud Contract Maven 插件之外,没有其他依赖项。 这些文件对于消费者端运行到本地安装是必需的 生产者项目的存根。pom.xml
mvn clean install -DskipTests
根文件夹中的文件可能如下所示:pom.xml
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://maven.apache.org/POM/4.0.0" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd"> <modelVersion>4.0.0</modelVersion> <groupId>com.example.standalone</groupId> <artifactId>contracts</artifactId> <version>0.0.1</version> <name>Contracts</name> <description>Contains all the Spring Cloud Contracts, well, contracts. JAR used by the producers to generate tests and stubs </description> <properties> <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding> </properties> <build> <plugins> <plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-assembly-plugin</artifactId> <executions> <execution> <id>contracts</id> <phase>prepare-package</phase> <goals> <goal>single</goal> </goals> <configuration> <attach>true</attach> <descriptor>${basedir}/src/assembly/contracts.xml</descriptor> <!-- If you want an explicit classifier remove the following line --> <appendAssemblyId>false</appendAssemblyId> </configuration> </execution> </executions> </plugin> </plugins> </build></project>
它使用汇编插件来构建包含所有合约的 JAR。以下示例 显示了这样的设置:
<assembly xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns="http://maven.apache.org/plugins/maven-assembly-plugin/assembly/1.1.3" xsi:schemaLocation="http://maven.apache.org/plugins/maven-assembly-plugin/assembly/1.1.3 https://maven.apache.org/xsd/assembly-1.1.3.xsd"> <id>project</id> <formats> <format>jar</format> </formats> <includeBaseDirectory>false</includeBaseDirectory> <fileSets> <fileSet> <directory>${project.basedir}</directory> <outputDirectory>/</outputDirectory> <useDefaultExcludes>true</useDefaultExcludes> <excludes> <exclude>**/${project.build.directory}/**</exclude> <exclude>mvnw</exclude> <exclude>mvnw.cmd</exclude> <exclude>.mvn/**</exclude> <exclude>src/**</exclude> </excludes> </fileSet> </fileSets></assembly>
5.2. 工作流程
工作流假定在使用者和 制片方。在公共存储库中也有正确的插件设置 合同。CI 作业是为通用存储库设置的,以构建所有工件 合同并将其上传到Nexus或Artifactory。下图显示了此 UML 工作流程:

5.3. 消费者
当使用者想要离线处理合约,而不是克隆生产者时 代码,消费者团队克隆公共存储库,转到所需生产者的 文件夹(例如,)并运行到 本地安装从合同转换的存根。com/example/server
mvn clean install -DskipTests
5.4. 生产者
作为生产者,您可以更改 Spring 云合同验证程序以提供 URL 和 包含协定的 JAR 的依赖项,如下所示:
<plugin>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-contract-maven-plugin</artifactId>
<configuration>
<contractsMode>REMOTE</contractsMode>
<contractsRepositoryUrl>
https://link/to/your/nexus/or/artifactory/or/sth
</contractsRepositoryUrl>
<contractDependency>
<groupId>com.example.standalone</groupId>
<artifactId>contracts</artifactId>
</contractDependency>
</configuration>
</plugin>
通过此设置,从中下载带有 aofand anofis 的 JAR。是的 然后在本地临时文件夹中解压缩,并且存在的合约被选取为用于生成测试和存根的合约。由于 对于此约定,制作人团队可以知道哪些使用者团队在以下情况下被破坏 进行了一些不兼容的更改。groupid
com.example.standalone
artifactid
contracts
link/to/your/nexus/or/artifactory/or/sth
com/example/server
流的其余部分看起来是一样的。
5.5. 如何定义每个主题而不是每个生产者的消息传递合约?
为了避免公共存储库中的消息传递协定重复,当几个生产者将消息写入一个主题时, 我们可以创建一个结构,其中 REST 合约放置在每个生产者和消息传递的文件夹中 合同放置在每个主题的文件夹中。
5.5.1. 对于 Maven 项目
为了能够在生产者端工作,我们应该指定一个包含模式 按我们感兴趣的消息传递主题过滤常见的存储库 JAR 文件。Maven Spring Cloud 合约插件的属性 让我们这样做。另外,需要指定,因为默认路径将是 公共存储库。以下示例显示了一个 Maven 春云合约插件:includedFiles
contractsPath
groupid/artifactid
<plugin>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-contract-maven-plugin</artifactId>
<version>${spring-cloud-contract.version}</version>
<configuration>
<contractsMode>REMOTE</contractsMode>
<contractsRepositoryUrl>https://link/to/your/nexus/or/artifactory/or/sth</contractsRepositoryUrl>
<contractDependency>
<groupId>com.example</groupId>
<artifactId>common-repo-with-contracts</artifactId>
<version>+</version>
</contractDependency>
<contractsPath>/</contractsPath>
<baseClassMappings>
<baseClassMapping>
<contractPackageRegex>.*messaging.*</contractPackageRegex>
<baseClassFQN>com.example.services.MessagingBase</baseClassFQN>
</baseClassMapping>
<baseClassMapping>
<contractPackageRegex>.*rest.*</contractPackageRegex>
<baseClassFQN>com.example.services.TestBase</baseClassFQN>
</baseClassMapping>
</baseClassMappings>
<includedFiles>
<includedFile>**/${project.artifactId}/**</includedFile>
<includedFile>**/${first-topic}/**</includedFile>
<includedFile>**/${second-topic}/**</includedFile>
</includedFiles>
</configuration>
</plugin>
前面的 Maven 插件中的许多值都可以更改。我们将其包含在 说明目的,而不是试图提供一个“典型”的例子。
|
5.5.2. 对于 Gradle 项目
要使用 Gradle 项目,请执行以下操作:
- 为通用存储库依赖项添加自定义配置,如下所示:
ext {
contractsGroupId = "com.example"
contractsArtifactId = "common-repo"
contractsVersion = "1.2.3"
}
configurations {
contracts {
transitive = false
}
}
- 将公共资源库依赖项添加到类路径中,如下所示:
dependencies {
contracts "${contractsGroupId}:${contractsArtifactId}:${contractsVersion}"
testCompile "${contractsGroupId}:${contractsArtifactId}:${contractsVersion}"
}
- 将依赖项下载到相应的文件夹,如下所示
task getContracts(type: Copy) {
from configurations.contracts
into new File(project.buildDir, "downloadedContracts")
}
- 解压缩 JAR,如下所示:
task unzipContracts(type: Copy) {
def zipFile = new File(project.buildDir, "downloadedContracts/${contractsArtifactId}-${contractsVersion}.jar")
def outputDir = file("${buildDir}/unpackedContracts")
from zipTree(zipFile)
into outputDir
}
- 清理未使用的合同,如下所示:
task deleteUnwantedContracts(type: Delete) {
delete fileTree(dir: "${buildDir}/unpackedContracts",
include: "**/*",
excludes: [
"**/${project.name}/**"",
"**/${first-topic}/**",
"**/${second-topic}/**"])
}
- 创建任务依赖项,如下所示:
unzipContracts.dependsOn("getContracts")
deleteUnwantedContracts.dependsOn("unzipContracts")
build.dependsOn("deleteUnwantedContracts")
- 通过指定包含合约的目录来配置插件,通过设置 属性,如下所示:
contractsDslDir
contracts {
contractsDslDir = new File("${buildDir}/unpackedContracts")
}
6. 如何使用 Git 作为合约和存根的存储?
在多语言世界中,有些语言不使用二进制存储,如 Artifactory和Nexus做到了。从春云合约2.0.0版本开始,我们提供 在 SCM(源代码管理)存储库中存储协定和存根的机制。目前, 只有支持的 SCM 是 Git。
存储库必须具有以下设置 (您可以从此处结帐):
.
└── META-INF
└── com.example
└── beer-api-producer-git
└── 0.0.1-SNAPSHOT
├── contracts
│ └── beer-api-consumer
│ ├── messaging
│ │ ├── shouldSendAcceptedVerification.groovy
│ │ └── shouldSendRejectedVerification.groovy
│ └── rest
│ ├── shouldGrantABeerIfOldEnough.groovy
│ └── shouldRejectABeerIfTooYoung.groovy
└── mappings
└── beer-api-consumer
└── rest
├── shouldGrantABeerIfOldEnough.json
└── shouldRejectABeerIfTooYoung.json
在文件夹下:META-INF
- 我们按(例如)对应用程序进行分组。
groupId
com.example
- 每个应用程序都由其(例如,)表示。
artifactId
beer-api-producer-git
- 接下来,每个应用程序按其版本(例如)进行组织。开始 从春云合约版本,可以指定版本如下 (假设您的版本遵循语义版本控制):
0.0.1-SNAPSHOT
2.1.0
+
或者:查找存根的最新版本(假设快照 始终是给定修订号的最新工件)。这意味着:latest
- 如果您有,,并且,我们假设 最新的是。
1.0.0.RELEASE
2.0.0.BUILD-SNAPSHOT
2.0.0.RELEASE
2.0.0.BUILD-SNAPSHOT
- 如果您有,我们假设最新的是。
1.0.0.RELEASE
2.0.0.RELEASE
2.0.0.RELEASE
- 如果您有名为or的版本,我们将选择该文件夹。
latest
+
release
:查找存根的最新版本。这意味着:
- 如果您有,,我们假设 最新的是。
1.0.0.RELEASE
2.0.0.BUILD-SNAPSHOT
2.0.0.RELEASE
2.0.0.RELEASE
- 如果您有调用的版本,我们会选择该文件夹。
release
最后,有两个文件夹:
-
contracts
:好的做法是存储每个所需的合同 具有使用者名称(如)的文件夹中的使用者。这样,你 可以使用该功能。进一步的目录结构是任意的。beer-api-consumer
stubs-per-consumer
-
mappings
:Maven 或 Gradle Spring 云合约插件推送 此文件夹中的存根服务器映射。在使用者端,存根运行程序扫描此文件夹 使用存根定义启动存根服务器。文件夹结构是副本 在子文件夹中创建的那个。contracts
6.1. 协议公约
控制合同源的类型和位置(是否 二进制存储或 SCM 存储库),您可以在 URL 中使用协议 存储库。春云合约迭代已注册的协议解析器 并尝试获取合约(通过使用插件)或存根(从存根运行器)。
对于 SCM 功能,目前,我们支持 Git 存储库。要使用它, 在需要放置存储库 URL 的属性中,您必须添加前缀 连接网址。下面的清单显示了一些示例:git://
git://file:///foo/bar
git://https://github.com/spring-cloud-samples/spring-cloud-contract-nodejs-contracts-git.git
git://git@github.com:spring-cloud-samples/spring-cloud-contract-nodejs-contracts-git.git
6.2. 生产者
对于生产者,要使用SCM(源代码管理)方法,我们可以重用 我们与外部合同使用的机制相同。我们路由春云合约 从以 协议。git://
您必须在Maven中手动添加目标或在Maven中使用(绑定)任务 格拉德尔。我们不会将存根推送到您的 git 存储 库。pushStubsToScm pushStubsToScm origin
|
以下清单包括 Maven 和 Gradle 构建文件的相关部分:
<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>
您还可以进一步自定义 gradle 任务。在以下示例中, 该任务被自定义为从本地 Git 存储库中选取合约:publishStubsToScm
格拉德尔
publishStubsToScm {
// We want to modify the default set up of the plugin when publish stubs to scm is called
// We want to pick contracts from a Git repository
contractDependency {
stringNotation = "${project.group}:${project.name}:${project.version}"
}
/*
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
*/
contractRepository {
repositoryUrl = "git://file://${new File(project.rootDir, "../target")}/contract_empty_git/"
}
// We set the contracts mode to `LOCAL`
contractsMode = "LOCAL"
}
重要
从 开始,以前用于自定义的闭包不再可用。应直接应用设置。 在闭包中,如前面的示例所示。2.3.0.RELEASE
customize{}
publishStubsToScm
publishStubsToScm
通过这样的设置:
- 将 git 项目克隆到临时目录
- SCM 存根下载器转到文件夹 查找合同。例如,对于,路径将是。
META-INF/groupId/artifactId/version/contracts
com.example:foo:1.0.0
META-INF/com.example/foo/1.0.0/contracts
- 测试是从合同生成的。
- 存根是从合同创建的。
- 测试通过后,将在克隆的存储库中提交存根。
- 最后,推送将发送到该存储库。
origin
6.3. 合同存储在本地的生产者
使用 SCM 作为存根和合约目标的另一种选择是存储 与生产者在本地签订合同,并且仅将合同和存根推送到 SCM。 以下清单显示了使用 Maven 和 Gradle 实现此目的所需的设置:
<plugin>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-contract-maven-plugin</artifactId>
<version>${spring-cloud-contract.version}</version>
<extensions>true</extensions>
<!-- In the default configuration, we want to use the contracts stored locally -->
<configuration>
<baseClassMappings>
<baseClassMapping>
<contractPackageRegex>.*messaging.*</contractPackageRegex>
<baseClassFQN>com.example.BeerMessagingBase</baseClassFQN>
</baseClassMapping>
<baseClassMapping>
<contractPackageRegex>.*rest.*</contractPackageRegex>
<baseClassFQN>com.example.BeerRestBase</baseClassFQN>
</baseClassMapping>
</baseClassMappings>
<basePackageForTests>com.example</basePackageForTests>
</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>
<configuration>
<!-- We want to pick contracts from a Git repository -->
<contractsRepositoryUrl>git://file://${env.ROOT}/target/contract_empty_git/
</contractsRepositoryUrl>
<!-- Example of URL via git protocol -->
<!--<contractsRepositoryUrl>git://git@github.com:spring-cloud-samples/spring-cloud-contract-samples.git</contractsRepositoryUrl>-->
<!-- Example of URL via http protocol -->
<!--<contractsRepositoryUrl>git://https://github.com/spring-cloud-samples/spring-cloud-contract-samples.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 mode can't be classpath -->
<contractsMode>LOCAL</contractsMode>
</configuration>
</execution>
</executions>
</plugin>
通过这样的设置:
- 从默认目录中选取合约。
src/test/resources/contracts
- 测试是从合同生成的。
- 存根是从合同创建的。
- 测试通过后:
- git 项目被克隆到一个临时目录。
- 存根和协定在克隆的存储库中提交。
6.4. 将与生产者的合同和存根保存在外部存储库中
您还可以将合约保留在创建者存储库中,但将存根保留在外部 git 存储库中。 当您想要使用基本的使用者-生产者协作流但不能使用时,这最有用 使用项目存储库存储存根。
为此,请使用通常的创建者设置,然后将目标和设置添加到要保存存根的存储库。pushStubsToScm
contractsRepositoryUrl
6.5. 消费者
在消费者端,当传递参数时, 要么来自注释, JUnit 4 规则、JUnit 5 扩展或属性,可以传递 SCM 存储库,以协议为前缀。以下示例演示如何执行此操作:repositoryRoot
@AutoConfigureStubRunner
git://
@AutoConfigureStubRunner(
stubsMode="REMOTE",
repositoryRoot="git://https://github.com/spring-cloud-samples/spring-cloud-contract-nodejs-contracts-git.git",
ids="com.example:bookstore:0.0.1.RELEASE"
)
通过这样的设置:
- git 项目被克隆到一个临时目录。
- SCM 存根下载器转到文件夹 以查找存根定义和协定。例如,对于,路径将是。
META-INF/groupId/artifactId/version/
com.example:foo:1.0.0
META-INF/com.example/foo/1.0.0/
- 存根服务器启动并馈送映射。
- 消息传递定义在消息传递测试中读取和使用。
7. 如何使用契约经纪人?
使用 Pact 时,您可以使用Pact 代理来存储和共享Pact 定义。从春云合约开始 2.0.0,你可以从契约代理获取契约文件来生成 测试和存根。
契约遵循消费者合同公约。这意味着 消费者先创建契约定义,然后再创建 与生产者共享文件。产生这些期望 从消费者的代码,如果期望,可以打破生产者 不满足。
|
7.1. 如何使用契约
Spring Cloud 合约包括对Pact表示的支持 合同直到版本 4。您可以使用 Pact 文件,而不是使用 DSL。在本节中,我们 演示如何为您的项目添加 Pact 支持。但请注意,并非所有功能都受支持。 从版本 3 开始,您可以为同一元素组合多个匹配器: 您可以对正文、标头、请求和路径使用匹配器,也可以使用值生成器。 春云合约目前仅支持使用规则逻辑组合的多个匹配器。 接下来,在转换过程中会跳过请求和路径匹配器。 使用给定格式的日期、时间或日期时间值生成器时, 跳过给定的格式并使用 ISO 格式。AND
7.2. 契约转换器
为了正确支持春云合约的消息传递方式 使用 Pact,您必须提供一些额外的元数据条目。
要定义消息发送到的目标,您必须 在 Pact 文件中将 aentry 设置为与目标相等的键 要发送的消息(例如,)。metaData
sentTo
"metaData": { "sentTo": "activemq:output" }
7.3. 契约合同
Spring Cloud Contract 可以读取 Pact JSON 定义。您可以将文件放在文件夹中。请记住将依赖关系放在类路径中。src/test/resources/contracts
spring-cloud-contract-pact
7.4. 生产者公约
在生产者端,您必须向插件添加两个额外的依赖项 配置。一个是春云合约契约支持,另一个代表 您使用的当前协议版本。以下清单显示了如何对两者执行此操作 Maven 和 Gradle:
<plugin>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-contract-maven-plugin</artifactId>
<version>${spring-cloud-contract.version}</version>
<extensions>true</extensions>
<configuration>
<testFramework>JUNIT5</testFramework>
<contractsRepositoryUrl>pact://http://localhost:8085</contractsRepositoryUrl>
<contractDependency>
<groupId>${project.groupId}</groupId>
<artifactId>${project.artifactId}</artifactId>
<version>+</version>
</contractDependency>
<contractsMode>REMOTE</contractsMode>
<baseClassForTests>com.example.BeerRestBase</baseClassForTests>
</configuration>
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-contract-pact</artifactId>
<version>${spring-cloud-contract.version}</version>
</dependency>
</dependencies>
</plugin>
生成应用程序时,将生成测试和存根。以下 示例显示了来自此过程的测试和存根:
@Test
public void validate_shouldMarkClientAsFraud() throws Exception {
// given:
MockMvcRequestSpecification request = given()
.header("Content-Type", "application/vnd.fraud.v1+json")
.body("{\"clientId\":\"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("['rejectionReason']").isEqualTo("Amount too high");
// and:
assertThat(parsedJson.read("$.fraudCheckStatus", String.class)).matches("FRAUD");
}
7.5. 消费者公约
在使用者端,必须向项目添加两个额外的依赖项 依赖。一个是春云合约契约支持,一个代表 您使用的当前协议版本。以下清单显示了如何对两者执行此操作 Maven 和 Gradle:
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-contract-stub-runner</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-contract-pact</artifactId>
<scope>test</scope>
</dependency>
7.6. 与协议经纪人沟通
每当该属性以协议协议开始时 (即开头为),存根下载器尝试 从契约经纪人处获取契约合约定义。 之后设置的任何内容都被解析为 Pact 代理 URL。repositoryRoot
pact://
pact://
通过设置环境变量、系统属性或设置的属性 在插件或合约存储库配置中,您可以 调整下载器的行为。下表描述了 性能:
Table 1. Pact Stub Downloader properties
属性名称
|
违约
|
描述
|
* pactbroker.host (插件道具)
* stubrunner.properties.pactbroker.host (系统道具)
* STUBRUNNER_PROPERTIES_PACTBROKER_HOST (环境道具)
|
从 URL 传递到 的主机repositoryRoot
|
协议代理的 URL。
|
* pactbroker.port (插件道具)
* stubrunner.properties.pactbroker.port (系统道具)
* STUBRUNNER_PROPERTIES_PACTBROKER_PORT (环境道具)
|
从 URL 传递到 的端口repositoryRoot
|
契约经纪人的港口。
|
* pactbroker.protocol (插件道具)
* stubrunner.properties.pactbroker.protocol (系统道具)
* STUBRUNNER_PROPERTIES_PACTBROKER_PROTOCOL (环境道具)
|
从 URL 传递到 的协议repositoryRoot
|
契约经纪人的协议。
|
* pactbroker.tags (插件道具)
* stubrunner.properties.pactbroker.tags (系统道具)
* STUBRUNNER_PROPERTIES_PACTBROKER_TAGS (环境道具)
|
存根的版本,或者 if 版本是latest +
|
应该用于获取存根的标记。
|
* pactbroker.auth.scheme (插件道具)
* stubrunner.properties.pactbroker.auth.scheme (系统道具)
* STUBRUNNER_PROPERTIES_PACTBROKER_AUTH_SCHEME (环境道具)
|
Basic
|
应该用于连接到 Pact 代理的身份验证类型。
|
* pactbroker.auth.username (插件道具)
* stubrunner.properties.pactbroker.auth.username (系统道具)
* STUBRUNNER_PROPERTIES_PACTBROKER_AUTH_USERNAME (环境道具)
|
传递给(Maven)或(Gradle)的用户名contractsRepositoryUsername contractRepository.username
|
连接到契约代理时要使用的用户名。
|
* pactbroker.auth.password (插件道具)
* stubrunner.properties.pactbroker.auth.password (系统道具)
* STUBRUNNER_PROPERTIES_PACTBROKER_AUTH_PASSWORD (环境道具)
|
传递给(Maven)或(Gradle)的密码contractsRepositoryPassword contractRepository.password
|
连接到协议代理时要使用的密码。
|
* pactbroker.provider-name-with-group-id (插件道具)
* stubrunner.properties.pactbroker.provider-name-with-group-id (系统道具)
* STUBRUNNER_PROPERTIES_PACTBROKER_PROVIDER_NAME_WITH_GROUP_ID (环境道具)
|
假
|
当,提供程序名称是的组合。如果,仅使用。true groupId:artifactId false artifactId
|
7.7. 流程:消费者方与契约经纪人的消费者合同方法
消费者使用 Pact 框架生成 Pact 文件。这 契约文件被发送到契约经纪人。您可以在此处找到此类设置的示例。
7.8. 流程:生产者方与契约经纪人的消费者合同方法
为了让生产者使用来自 Pact 代理的 Pact 文件,我们可以重用 我们与外部合同使用的机制相同。我们路由春云合约 将 Pact 实现与包含以下内容的 URL 一起使用 协议。您可以将 URL 传递给 契约经纪人。您可以在此处找到此类设置的示例。 以下清单显示了 Maven 和 Gradle 的配置详细信息:pact://
<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>pact://http://localhost:8085</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>
<!-- When + is passed, a latest tag will be applied when fetching pacts -->
<version>+</version>
</contractDependency>
<!-- The contracts mode can't be classpath -->
<contractsMode>REMOTE</contractsMode>
</configuration>
<!-- Don't forget to add spring-cloud-contract-pact to the classpath! -->
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-contract-pact</artifactId>
<version>${spring-cloud-contract.version}</version>
</dependency>
</dependencies>
</plugin>
通过这样的设置:
- 协议文件从协议代理下载。
- Spring Cloud 合约将 Pact 文件转换为测试和存根。
- 像往常一样,自动创建带有存根的 JAR。
7.9. 流程:生产者合同方法与消费者方的契约
在不想执行消费者合约方法的情况下 (对于每个消费者,定义期望)但你更喜欢 签订生产者合同(生产者提供合同和 发布存根),您可以将春云合约与 存根运行器选项。您可以在此处找到此类设置的示例。
记得添加存根运行器和春云合约契约模块 作为测试依赖项。
以下清单显示了 Maven 和 Gradle 的配置详细信息:
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-dependencies</artifactId>
<version>${spring-cloud.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
<!-- Don't forget to add spring-cloud-contract-pact to the classpath! -->
<dependencies>
<!-- ... -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-contract-stub-runner</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-contract-pact</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
接下来,您可以将协议代理的 URL 传递给,前缀 使用协议(例如,),如下所示 示例显示:repositoryRoot
pact://
pact://http://localhost:8085
@RunWith(SpringRunner.class)
@SpringBootTest
@AutoConfigureStubRunner(stubsMode = StubRunnerProperties.StubsMode.REMOTE,
ids = "com.example:beer-api-producer-pact",
repositoryRoot = "pact://http://localhost:8085")
public class BeerControllerTest {
//Inject the port of the running stub
@StubRunnerPort("beer-api-producer-pact") int producerPort;
//...
}
通过这样的设置:
- 协议文件从协议代理下载。
- Spring Cloud 合约将 Pact 文件转换为存根定义。
- 存根服务器已启动并馈送存根。
8. 如何调试生成的测试客户端发送的请求/响应?
生成的测试都以某种形式或方式归结为RestAssure。放心 依赖于Apache HttpClient。 HttpClient有一个名为线路日志记录的工具, 记录对 HttpClient 的整个请求和响应。Spring Boot 有一个日志记录公共应用程序属性,用于执行此类操作。若要使用它,请将其添加到应用程序属性中,如下所示:
logging.level.org.apache.http.wire=DEBUG
9. 如何调试 WireMock 发送的映射、请求或响应?
从版本开始,我们将 WireMock 日志记录设置为 并将 WireMock 通知程序设置为详细。现在你可以 确切地知道 WireMock 服务器收到了什么请求以及哪个请求 选择了匹配的响应定义。1.2.0
info
若要关闭此功能,请将 WireMock 日志记录设置为,如下所示:ERROR
logging.level.com.github.tomakehurst.wiremock=ERROR
10. 如何查看 HTTP 服务器存根中注册的内容?
可以使用属性 on,, 或转储每个项目 ID 的所有映射。此外,给定存根服务器的端口 已启动 已连接。mappingsOutputFolder
@AutoConfigureStubRunner
StubRunnerRule
StubRunnerExtension
11. 如何引用文件中的文本?
在版本 1.2.0 中,我们添加了此功能。您可以在 DSL 并提供相对于合约所在位置的路径。 如果使用 YAML,则可以使用该属性。file(…)
bodyFromFile
12. 如何从春云合约中生成契约、YAML 或 X 文件?
春云合约附带一个类,可以让你转储 合同作为给定的文件。它包含一个方法,允许您将转换器作为可执行文件运行。它需要以下几点 参数:ToFileContractsTransformer
ContractConverter
static void main
- 参数 1 ::(例如,)的完全限定名称。必需的。
FQN
ContractConverter
PactContractConverter
- 参数 2 ::应存储转储文件的路径。可选 — 默认为。
path
target/converted-contracts
- 参数 3 :: 应搜索合同的路径。可选 — 默认为。
path
src/test/resources/contracts
调用转换器后,将处理春云合约文件,并, 根据提供的 FQN,合同被转换 到所需的格式并转储到提供的文件夹。ContractTransformer
以下示例显示了如何为 Maven 和 Gradle 配置 Pact 集成:
<plugin>
<groupId>org.codehaus.mojo</groupId>
<artifactId>exec-maven-plugin</artifactId>
<version>1.6.0</version>
<executions>
<execution>
<id>convert-dsl-to-pact</id>
<phase>process-test-classes</phase>
<configuration>
<classpathScope>test</classpathScope>
<mainClass>
org.springframework.cloud.contract.verifier.util.ToFileContractsTransformer
</mainClass>
<arguments>
<argument>
org.springframework.cloud.contract.verifier.spec.pact.PactContractConverter
</argument>
<argument>${project.basedir}/target/pacts</argument>
<argument>
${project.basedir}/src/test/resources/contracts
</argument>
</arguments>
</configuration>
<goals>
<goal>java</goal>
</goals>
</execution>
</executions>
</plugin>
13. 如何使用传递依赖项?
Spring 云合约插件添加了为您创建存根 jar 的任务。一 出现的问题是,在重用存根时,您可能会错误地导入所有 该存根的依赖项。在构建 Maven 工件时,即使您有几个 在不同的 jar 中,它们都共享一个文件,如以下列表所示:pom.xml
├── producer-0.0.1.BUILD-20160903.075506-1-stubs.jar
├── producer-0.0.1.BUILD-20160903.075506-1-stubs.jar.sha1
├── producer-0.0.1.BUILD-20160903.075655-2-stubs.jar
├── producer-0.0.1.BUILD-20160903.075655-2-stubs.jar.sha1
├── producer-0.0.1.BUILD-SNAPSHOT.jar
├── producer-0.0.1.BUILD-SNAPSHOT.pom
├── producer-0.0.1.BUILD-SNAPSHOT-stubs.jar
├── ...
└── ...
使用这些依赖项有三种可能性,以免有任何依赖关系 传递依赖项的问题:
- 将所有应用程序依赖项标记为可选
- 为存根创建单独的
artifactid
- 排除使用者端的依赖关系
13.1. 如何将所有应用程序依赖项标记为可选?
如果在应用程序中将所有依赖项标记为可选, 当您在另一个应用程序中包含存根时(或者当 依赖项由存根运行器下载),然后,因为所有依赖项都是 可选,它们不会被下载。producer
producer
13.2. 如何为存根创建单独的工件 ID?
如果创建单独的,则可以根据需要进行设置。 例如,您可能决定完全没有依赖项。artifactid
13.3. 如何排除消费者端的依赖关系?
作为使用者,如果将存根依赖项添加到类路径中,则可以显式排除不需要的依赖项。
14. 如何从合约中生成 Spring REST 文档片段?
当您想通过使用 Spring REST 文档包含 API 的请求和响应时, 如果您使用的是MockMvc和RestAssuredMockMvc,则只需要对设置进行一些小的更改。 为此,请包含以下依赖项(如果尚未这样做):
org.springframework.cloud
spring-cloud-starter-contract-verifier
test
org.springframework.restdocs
spring-restdocs-mockmvc
true
接下来,您需要对基类进行一些更改。以下示例使用和独立选项与 RestAsassured 一起使用:WebAppContext
WebAppContext
package com.example.fraud;
import io.restassured.module.mockmvc.RestAssuredMockMvc;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.TestInfo;
import org.junit.jupiter.api.extension.ExtendWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.restdocs.RestDocumentationContextProvider;
import org.springframework.restdocs.RestDocumentationExtension;
import org.springframework.test.web.servlet.setup.MockMvcBuilders;
import org.springframework.web.context.WebApplicationContext;
import static org.springframework.restdocs.mockmvc.MockMvcRestDocumentation.document;
import static org.springframework.restdocs.mockmvc.MockMvcRestDocumentation.documentationConfiguration;
@ExtendWith(RestDocumentationExtension.class)
@SpringBootTest(classes = Application.class)
public abstract class FraudBaseWithWebAppSetup {
@Autowired
private WebApplicationContext context;
@BeforeEach
public void setup(TestInfo info, RestDocumentationContextProvider restDocumentation) {
RestAssuredMockMvc.mockMvc(MockMvcBuilders.webAppContextSetup(this.context)
.apply(documentationConfiguration(restDocumentation))
.alwaysDo(document(
getClass().getSimpleName() + "_" + info.getDisplayName()))
.build());
}
protected void assertThatRejectionReasonIsNull(Object rejectionReason) {
assert rejectionReason == null;
}
}
您无需为生成的代码段指定输出目录(从 Spring REST 文档版本 1.2.0.RELEASE 开始)。
|
15. 如何使用某个位置的存根
如果要从给定位置获取合约或存根而不克隆存储库或获取 JAR,请在为 Stub Runner 或 Spring Cloud Contract 插件提供存储库根参数时使用协议。您可以在文档的这一部分中阅读有关此内容的更多信息。stubs://
16. 如何在运行时生成存根
如果要在运行时为合约生成存根,请在注释中切换属性,或在 JUnit 规则或扩展上调用该方法。您可以在文档的这一部分中阅读有关此内容的更多信息。generateStubs
@AutoConfigureStubRunner
withGenerateStubs(true)
17. 如果没有合同或存根,我如何才能完成构建传递
如果希望存根运行程序在未找到存根时不会失败,请在注释中切换属性或在 JUnit 规则或扩展上调用该方法。您可以在文档的这一部分中阅读有关此内容的更多信息。generateStubs
@AutoConfigureStubRunner
withFailOnNoStubs(false)
如果您希望插件在找不到合约时不会失败构建,您可以在 Maven 中设置 theflag 或在 Gradle 中调用闭包。failOnNoStubs
contractRepository { failOnNoStubs(false) }
18. 如何标记合同正在进行中
如果合同正在进行中,则意味着在生产者端不会生成测试,而是生成存根。您可以在文档的这一部分中阅读有关此内容的更多信息。
在 CI 构建中,在投入生产之前,您希望确保类路径上没有正在进行的协定,因为它们可能会导致误报。出于这个原因,默认情况下,在 Spring 云合约插件中,我们将值设置为 ofto。如果要在生成测试时允许此类协定,请将标志设置为 。failOnInProgress
true
false