Consumer Driven Contracts with Contracts in an External Repository
在此流程中,我们执行消费者驱动的契约测试。契约定义存储在一个单独的存储库中。
In this flow, we perform Consumer Driven Contract testing. The contract definitions are stored in a separate repository.
Prerequisites
若要对外部存储库中保留的契约使用消费者驱动的契约,你需要设置 Git 存储库,此存储库:
To use consumer-driven contracts with the contracts held in an external repository, you need to set up a git repository that:
-
包含每个生产者的所有合同定义。
-
Contains all the contract definitions for each producer.
-
可以将合同定义打包到 JAR 中。
-
Can package the contract definitions in a JAR.
-
对于每个合同生产者,包含一种方法(例如,
pom.xml
)通过 Spring Cloud Contract Plugin (SCC Plugin) 本地安装存根。 -
For each contract producer, contains a way (for example,
pom.xml
) to install stubs locally through the Spring Cloud Contract Plugin (SCC Plugin).
更多信息,请参阅 How To section,我们描述了如何设置这样的存储库。对于此类项目的示例,请参阅 {samples_code}/beer_contracts[此示例]。
For more information, see the How To section, where we describe how to set up such a repository. For an example of such a project, see {samples_code}/beer_contracts[this sample].
您还需要具有已设置 Spring Cloud Contract Stub Runner 的使用者代码。对于此类项目的示例,请参阅 {samples_code}/consumer[此示例]。您还需要具有已设置 Spring Cloud Contract 的生产者代码,以及插件。对于此类项目的示例,请参阅 {samples_code}/producer_with_external_contracts[此示例]。存根存储是 Nexus 或 Artifactory。
You also need consumer code that has Spring Cloud Contract Stub Runner set up. For an example of such a project, see {samples_code}/consumer[this sample]. You also need producer code that has Spring Cloud Contract set up, together with a plugin. For an example of such a project, see {samples_code}/producer_with_external_contracts[this sample]. The stub storage is Nexus or Artifactory.
流程的高度概览如下:
At a high level, the flow is as follows:
-
使用者使用来自独立存储库的合同定义。
-
The consumer works with the contract definitions from the separate repository.
-
一旦使用者的工作完成,就会在使用者端创建包含工作代码的分支,并向包含合同定义的独立存储库发出拉取请求。
-
Once the consumer’s work is done, a branch with working code is created on the consumer side, and a pull request is made to the separate repository that holds the contract definitions.
-
该制作者负责对包含 contractdefinitions 的分离存储库发出拉取请求并本地安装包含所有合同的 JAR。
-
The producer takes over the pull request to the separate repository with contract definitions and installs the JAR with all contracts locally.
-
该制作者根据本地存储的 JAR 生成测试,并编写缺少的实现来使测试通过。
-
The producer generates tests from the locally stored JAR and writes the missing implementation to make the tests pass.
-
一旦该制作者的工作完成,负责存储合同定义的存储库的拉取请求即会合并。
-
Once the producer’s work is done, the pull request to the repository that holds the contract definitions is merged.
-
CI 工具构建了包含合同定义的存储库,并且 JAR 与合同定义上传到 Nexus 或 Artifactory 后,该制作者可以合并其分支。
-
After the CI tool builds the repository with the contract definitions and the JAR with contract definitions gets uploaded to Nexus or Artifactory, the producer can merge its branch.
-
最后,该消费者可以切换到在线工作以从远程位置获取制作者的存根,并且该分支可以合并到 master 中。
-
Finally, the consumer can switch to working online to fetch stubs of the producer from a remote location, and the branch can be merged to master.
Consumer Flow
消费者:
The consumer:
-
编写一个测试以向制作者发送请求。[.iokays-translated-d73cf48455a15e1af4877e31158a7fee] 由于不存在服务器,因此测试失败。
The test fails due to no server being present.
-
Writes a test that would send a request to the producer.[.iokays-translated-d73cf48455a15e1af4877e31158a7fee] 由于不存在服务器,因此测试失败。
The test fails due to no server being present.
-
克隆包含合同定义的存储库。
-
Clones the repository that holds the contract definitions.
-
在文件夹下将需求设定为合同,其中消费者名称为制作者的子文件夹。[.iokays-translated-87b5ad595377bfffeedec62e245c8b8b] 例如,对于名为
producer
的生产者和名为consumer
的消费者,契约存储在src/main/resources/contracts/producer/consumer/
) 下
For example, for a producer named producer
and a consumer named consumer
, the contracts would be stored under src/main/resources/contracts/producer/consumer/
)
-
Sets up the requirements as contracts under the folder, with the consumer name as a subfolder of the producer.[.iokays-translated-87b5ad595377bfffeedec62e245c8b8b] 例如,对于名为
producer
的生产者和名为consumer
的消费者,契约存储在src/main/resources/contracts/producer/consumer/
) 下
For example, for a producer named producer
and a consumer named consumer
, the contracts would be stored under src/main/resources/contracts/producer/consumer/
)
-
定义合同后,将制作者存根安装到本地存储,如下例所示:[source, bash]
$ cd src/main/resource/contracts/producer $ ./mvnw clean install
-
Once the contracts are defined, installs the producer stubs to local storage, as the following example shows:[source, bash]
$ cd src/main/resource/contracts/producer $ ./mvnw clean install
-
在消费者测试中设置 Spring Cloud Contract (SCC) Stub Runner,以:
-
从本地存储获取制作者存根。
-
Fetch the producer stubs from local storage.
-
在存根到消费者模式下工作(启用消费者驱动合同模式)。[.iokays-translated-071d063289660d5f9cc5fc7d41d430fa] SCC Stub Runner:
-
The SCC Stub Runner:
-
Work in the stubs-per-consumer mode (this enables consumer driven contracts mode).[.iokays-translated-071d063289660d5f9cc5fc7d41d430fa] SCC Stub Runner:
The SCC Stub Runner:
-
Fetches the producer stubs.
-
使用制作者存根运行内存中 HTTP 服务器存根。现在,您的测试会与 HTTP 服务器存根进行通信,并通过测试。
-
Runs an in-memory HTTP server stub with the producer stubs. Now your test communicates with the HTTP server stub, and your tests pass.
-
对包含新合同的制作者的存储库创建拉取请求。
-
Creates a pull request to the repository with contract definitions, with the new contracts for the producer.
-
为您的消费者代码添加分支,直至制作者团队合并其代码。
-
Branches your consumer code, until the producer team has merged their code.
-
Sets up Spring Cloud Contract (SCC) Stub Runner in the consumer tests, to:
-
从本地存储获取制作者存根。
-
Fetch the producer stubs from local storage.
-
在存根到消费者模式下工作(启用消费者驱动合同模式)。[.iokays-translated-071d063289660d5f9cc5fc7d41d430fa] SCC Stub Runner:
-
The SCC Stub Runner:
-
Work in the stubs-per-consumer mode (this enables consumer driven contracts mode).[.iokays-translated-071d063289660d5f9cc5fc7d41d430fa] SCC Stub Runner:
The SCC Stub Runner:
-
Fetches the producer stubs.
-
使用制作者存根运行内存中 HTTP 服务器存根。现在,您的测试会与 HTTP 服务器存根进行通信,并通过测试。
-
Runs an in-memory HTTP server stub with the producer stubs. Now your test communicates with the HTTP server stub, and your tests pass.
-
对包含新合同的制作者的存储库创建拉取请求。
-
Creates a pull request to the repository with contract definitions, with the new contracts for the producer.
-
为您的消费者代码添加分支,直至制作者团队合并其代码。
-
Branches your consumer code, until the producer team has merged their code.
以下 UML 图显示了消费者流程:
The following UML diagram shows the consumer flow:
"Consumer"->"Repo\nwith\ncontracts": clone "Repo\nwith\ncontracts"->"Repo\nwith\ncontracts\nclone": cloned "Consumer"->"Repo\nwith\ncontracts\nclone": create contract\ndefinitions of\nthe [Producer] "Repo\nwith\ncontracts\nclone"->"Local storage": install [Producer]\nstubs locally "Consumer"->"Consumer\nBuild": run tests "Consumer\nBuild"->"SCC\nStub Runner": Run [Producer] stubs "SCC\nStub Runner"->"Local storage": fetch [Producer] stubs "SCC\nStub Runner"->"Producer stub": stub is running "Consumer\nBuild"->"Producer stub": send a request\nin the tests "Producer stub"->"Consumer\nBuild": send a response "Consumer\nBuild"->"Consumer": the tests are passing "Consumer"->"Repo\nwith\ncontracts\nclone": send a pull request "Repo\nwith\ncontracts\nclone"->"Repo\nwith\ncontracts": pull request sent "Consumer"->"Consumer": branch the code
Producer Flow
生产者:
The producer:
-
负责对包含 contractdefinitions 的存储库发出拉取请求。您可以从命令行执行此操作,如下所示:[source, bash]
$ git checkout -b the_branch_with_pull_request master git pull https://github.com/user_id/project_name.git the_branch_with_pull_request
-
Takes over the pull request to the repository with contract definitions. You can do it from the command line, as follows[source, bash]
$ git checkout -b the_branch_with_pull_request master git pull https://github.com/user_id/project_name.git the_branch_with_pull_request
-
安装合同定义,如下所示:[source, bash]
$ ./mvnw clean install
-
Installs the contract definitions, as follows[source, bash]
$ ./mvnw clean install
-
设置插件从 JAR(而不是
src/test/resources/contracts
)获取合约定义,如下所示:- ===Maven
===
- Maven
-
<plugin> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-contract-maven-plugin</artifactId> <version>${spring-cloud-contract.version}</version> <extensions>true</extensions> <configuration> <!-- We want to use the JAR with contracts with the following coordinates --> <contractDependency> <groupId>com.example</groupId> <artifactId>beer-contracts</artifactId> </contractDependency> <!-- The JAR with contracts should be taken from Maven local --> <contractsMode>LOCAL</contractsMode> <!-- ... additional configuration --> </configuration> </plugin>
- Gradle
-
contracts { // We want to use the JAR with contracts with the following coordinates // group id `com.example`, artifact id `beer-contracts`, LATEST version and NO classifier contractDependency { stringNotation = 'com.example:beer-contracts:+:' } // The JAR with contracts should be taken from Maven local contractsMode = "LOCAL" // Additional configuration }
===
-
Sets up the plugin to fetch the contract definitions from a JAR instead of from
src/test/resources/contracts
, as follows:- ===Maven
===
- Maven
-
<plugin> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-contract-maven-plugin</artifactId> <version>${spring-cloud-contract.version}</version> <extensions>true</extensions> <configuration> <!-- We want to use the JAR with contracts with the following coordinates --> <contractDependency> <groupId>com.example</groupId> <artifactId>beer-contracts</artifactId> </contractDependency> <!-- The JAR with contracts should be taken from Maven local --> <contractsMode>LOCAL</contractsMode> <!-- ... additional configuration --> </configuration> </plugin>
- Gradle
-
contracts { // We want to use the JAR with contracts with the following coordinates // group id `com.example`, artifact id `beer-contracts`, LATEST version and NO classifier contractDependency { stringNotation = 'com.example:beer-contracts:+:' } // The JAR with contracts should be taken from Maven local contractsMode = "LOCAL" // Additional configuration }
===
-
运行构建以生成测试和存根,如下所示:
- ===Maven
===
- Maven
-
./mvnw clean install
- Gradle
-
./gradlew clean build
===
-
Runs the build to generate tests and stubs, as follows:
- ===Maven
===
- Maven
-
./mvnw clean install
- Gradle
-
./gradlew clean build
===
-
编写缺少的实现,使测试通过。
-
Writes the missing implementation, to make the tests pass.
-
将包含合约定义的拉取请求合并到代码库中:[source, bash]
$ git commit -am "Finished the implementation to make the contract tests pass" $ git checkout master $ git merge --no-ff the_branch_with_pull_request $ git push origin master
CI 系统使用契约定义构建项目,并将具有契约定义的 JAR 上传到 Nexus 或 Artifactory。
The CI system builds the project with the contract definitions and uploads the JAR with the contract definitions to Nexus or Artifactory.
-
Merges the pull request to the repository with contract definitions, as follows:[source, bash]
$ git commit -am "Finished the implementation to make the contract tests pass" $ git checkout master $ git merge --no-ff the_branch_with_pull_request $ git push origin master
CI 系统使用契约定义构建项目,并将具有契约定义的 JAR 上传到 Nexus 或 Artifactory。
The CI system builds the project with the contract definitions and uploads the JAR with the contract definitions to Nexus or Artifactory.
-
Switches to working remotely.
-
设置插件,使其不再从本地存储获取合约定义,而从远程位置获取:
- ===Maven
===
- Maven
-
<plugin> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-contract-maven-plugin</artifactId> <version>${spring-cloud-contract.version}</version> <extensions>true</extensions> <configuration> <!-- We want to use the JAR with contracts with the following coordinates --> <contractDependency> <groupId>com.example</groupId> <artifactId>beer-contracts</artifactId> </contractDependency> <!-- The JAR with contracts should be taken from a remote location --> <contractsMode>REMOTE</contractsMode> <!-- ... additional configuration --> </configuration> </plugin>
- Gradle
-
contracts { // We want to use the JAR with contracts with the following coordinates // group id `com.example`, artifact id `beer-contracts`, LATEST version and NO classifier contractDependency { stringNotation = 'com.example:beer-contracts:+:' } // The JAR with contracts should be taken from a remote location contractsMode = "REMOTE" // Additional configuration }
===
-
Sets up the plugin so that the contract definitions are no longer taken from the local storage but from a remote location, as follows:
- ===Maven
===
- Maven
-
<plugin> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-contract-maven-plugin</artifactId> <version>${spring-cloud-contract.version}</version> <extensions>true</extensions> <configuration> <!-- We want to use the JAR with contracts with the following coordinates --> <contractDependency> <groupId>com.example</groupId> <artifactId>beer-contracts</artifactId> </contractDependency> <!-- The JAR with contracts should be taken from a remote location --> <contractsMode>REMOTE</contractsMode> <!-- ... additional configuration --> </configuration> </plugin>
- Gradle
-
contracts { // We want to use the JAR with contracts with the following coordinates // group id `com.example`, artifact id `beer-contracts`, LATEST version and NO classifier contractDependency { stringNotation = 'com.example:beer-contracts:+:' } // The JAR with contracts should be taken from a remote location contractsMode = "REMOTE" // Additional configuration }
===
-
将生产者代码与新的实现合并。
-
Merges the producer code with the new implementation.
-
The CI system:
-
Builds the project.
-
生成测试、存根和存根 JAR。
-
Generates tests, stubs, and the stub JAR.
-
将包含应用程序和存根的人工制品上传到Nexus或Artifactory。
-
Uploads the artifact with the application and the stubs to Nexus or Artifactory.
-
以下 UML 图显示了生产者流程:
The following UML diagram shows the producer process:
"Producer"->"Repo\nwith\ncontracts": take over the pull request "Producer"->"Repo\nwith\ncontracts": install the contract\ndefinitions JAR "Repo\nwith\ncontracts"->"Local storage": install the\ncontract definitions\nJAR locally "Local storage"->"Repo\nwith\ncontracts": contract definitions\nJAR installed "Producer"->"Producer\nBuild": run build "Producer\nBuild"->"SCC\nPlugin": generate tests,\nstubs\nand stub jar "SCC\nPlugin"->"Local storage": fetch the contract definitions "Local storage"->"SCC\nPlugin": contract definitions found "SCC\nPlugin"->"SCC\nPlugin": generate tests "Producer\nBuild"->"Producer\nBuild": run the\ngenerated tests "Producer\nBuild"->"Producer": the tests failed to pass "Producer"->"Producer": write the missing implementation "Producer"->"Producer\nBuild": run the build again "Producer\nBuild"->"Producer\nBuild": fetch the contract definitions\nrun the generated tests "Producer\nBuild"->"Producer": the tests passed "Producer"->"Repo\nwith\ncontracts": merge the pull request "Repo\nwith\ncontracts"->"CI": build and upload the\ncontract definitions artifact "CI"->"Stub Storage": upload the\ncontract definitions "Producer"->"Producer": setup the SCC Plugin\nto work remotely "Producer"->"Producer": merge the code\nwith the implementation "Producer"->"CI": build and upload\nthe artifacts "CI"->"Producer\nBuild\non CI": generate tests,\nstubs\nand stub jar "Producer\nBuild\non CI"->"SCC\nPlugin": generate tests,\nstubs\nand stub jar "SCC\nPlugin"->"Stub Storage": fetch the contract definitions "Stub Storage"->"SCC\nPlugin": contract definitions found "SCC\nPlugin"->"SCC\nPlugin": generate tests "Producer\nBuild\non CI"->"CI": the build passed "Producer\nBuild\non CI"->"Stub Storage": upload the application JAR\nand the stubs jar