使用 Spring Cloud Contract

1. 在 Nexus 或 Artifactory 中使用 Stub 进行提供商合同测试

您可以查看开发第一个基于 Spring Cloud Contract 的应用程序链接,以查看在 Nexus 或 Artifactory 流程中使用存根进行提供商 Contract 测试。spring-doc.cn

您还可以查看研讨会页面,获取有关如何执行此流程的分步说明。spring-doc.cn

2. 在 Git 中使用存根进行提供商合同测试

在这个流程中,我们执行提供者契约测试(生产者不知道消费者如何使用他们的 API)。存根将上传到单独的存储库(它们不会上传到 Artifactory 或 Nexus)。spring-doc.cn

2.1. 先决条件

在 git 中使用存根测试提供商合同之前,您必须提供 git 存储库 ,其中包含每个生产者的所有存根。有关此类项目的示例,请参阅此示例此示例。 将存根推送到那里的结果,存储库具有以下结构:spring-doc.cn

$ tree .
└── META-INF
   └── folder.with.group.id.as.its.name
       └── folder-with-artifact-id
           └── folder-with-version
               ├── contractA.groovy
               ├── contractB.yml
               └── contractC.groovy

您还必须提供设置了 Spring Cloud Contract Stub Runner 的使用者代码。为 此类项目的示例,请参阅此示例并搜索测试。您还必须提供具有 Spring Cloud 的生产者代码 合约设置,以及插件。有关此类项目的示例,请参阅此示例BeerControllerGitTestspring-doc.cn

2.2. 流程

该流程看起来与开发第一个基于 Spring Cloud Contract 的应用程序 中提供的流程完全相同。 但实现是一个 Git 存储库。Stub Storagespring-doc.cn

您可以阅读有关设置 git 存储库和设置 consumer 和 producer 端的更多信息 在文档的 How To 页面中。spring-doc.cn

2.3. 消费者设置

为了从 git 存储库而不是 Nexus 或 Artifactory 获取存根,您需要 需要在 Stub Runner 中的属性 URL 中使用协议。 以下示例显示了如何设置它:gitrepositoryRootspring-doc.cn

注解
@AutoConfigureStubRunner(
stubsMode = StubRunnerProperties.StubsMode.REMOTE,
        repositoryRoot = "git://[email protected]:spring-cloud-samples/spring-cloud-contract-nodejs-contracts-git.git",
        ids = "com.example:artifact-id:0.0.1")
JUnit 4 规则
@Rule
    public StubRunnerRule rule = new StubRunnerRule()
            .downloadStub("com.example","artifact-id", "0.0.1")
            .repoRoot("git://[email protected]:spring-cloud-samples/spring-cloud-contract-nodejs-contracts-git.git")
            .stubsMode(StubRunnerProperties.StubsMode.REMOTE);
JUnit 5 扩展
@RegisterExtension
    public StubRunnerExtension stubRunnerExtension = new StubRunnerExtension()
            .downloadStub("com.example","artifact-id", "0.0.1")
            .repoRoot("git://[email protected]:spring-cloud-samples/spring-cloud-contract-nodejs-contracts-git.git")
            .stubsMode(StubRunnerProperties.StubsMode.REMOTE);

2.4. 设置 Producer

为了将存根推送到 git 存储库而不是 Nexus 或 Artifactory,您需要 以在插件设置的 URL 中使用协议。此外,您需要明确告知 用于在构建过程结束时推送存根的插件。以下示例显示了 如何操作:gitspring-doc.cn

maven
<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://git://[email protected]: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
contracts {
    // 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://git://[email protected]:spring-cloud-samples/spring-cloud-contract-nodejs-contracts-git.git"
    }
    // The mode can't be classpath
    contractsMode = "REMOTE"
    // Base class mappings etc.
}

/*
In this scenario we want to publish stubs to SCM whenever
the `publish` task is executed
*/
publish.dependsOn("publishStubsToScm")

您可以在文档的 How To 页面中阅读有关设置 git 存储库的更多信息。spring-doc.cn

3. 消费者驱动的合约,生产者端的合约

查看 Consumer Driven 分步指南 Contracts (CDC) 与 Contracts 在 Producer 端查看 Consumer Driven Contracts with Contract on the producer side 流。spring-doc.cn

4. 具有外部存储库中的合同的消费者驱动合同

在这个流程中,我们执行 Consumer Driven Contract 测试。合同定义包括 存储在单独的存储库中。spring-doc.cn

有关如何执行此流程的分步说明,请参阅研讨会页面spring-doc.cn

4.1. 先决条件

要将消费者驱动的合同与外部存储库中保存的合同一起使用,您需要设置一个 git 存储库,该存储库:spring-doc.cn

  • 包含每个生产者的所有合同定义。spring-doc.cn

  • 可以将 Contract 定义打包到 JAR 中。spring-doc.cn

  • 对于每个合同创建者,包含一种安装存根的方法(例如) 通过 Spring Cloud Contract Plugin(SCC Plugin)在本地pom.xmlspring-doc.cn

有关更多信息,请参阅“操作方法”部分。 其中我们描述了如何设置这样的存储库 有关此类项目的示例,请参阅此示例spring-doc.cn

您还需要设置了 Spring Cloud Contract Stub Runner 的使用者代码。 有关此类项目的示例,请参阅此示例。 您还需要设置了 Spring Cloud Contract 的生产者代码以及插件。 有关此类项目的示例,请参阅此示例。 存根存储是 Nexus 或 Artifactoryspring-doc.cn

概括地说,流程如下所示:spring-doc.cn

  1. 使用者使用来自单独存储库的合同定义spring-doc.cn

  2. 一旦消费者的工作完成,一个带有工作代码的分支就会在消费者身上完成 side 的 PULL MANAGER,并向包含合同定义的单独存储库发出拉取请求。spring-doc.cn

  3. 创建者通过 Contract 接管对单独存储库的拉取请求 定义并在本地安装包含所有 Contract 的 JAR。spring-doc.cn

  4. 创建者从本地存储的 JAR 生成测试,并写入缺失的 implementation 使测试通过。spring-doc.cn

  5. 创建者的工作完成后,对包含该 Contract definitions 被合并。spring-doc.cn

  6. 在 CI 工具使用合约定义构建存储库并使用 JAR 合约定义上传到 Nexus 或 Artifactory,生产者可以合并其分支。spring-doc.cn

  7. 最后,消费者可以切换到在线工作,从 remote 位置,并且该分支可以合并到 master 中。spring-doc.cn

4.2. 消费者流程

消费者:spring-doc.cn

  1. 编写一个测试,该测试将向创建者发送请求。spring-doc.cn

    由于没有服务器存在,测试失败。spring-doc.cn

  2. 克隆包含合同定义的存储库。spring-doc.cn

  3. 将要求设置为文件夹下的合同,并将使用者名称作为生产者的子文件夹。spring-doc.cn

    例如,对于名为 的 producer 和 名为 的 Consumer ,Contract 将存储在producerconsumersrc/main/resources/contracts/producer/consumer/)spring-doc.cn

  4. 定义 Contract 后,将 producer 存根安装到本地存储,如下例所示:spring-doc.cn

    $ cd src/main/resource/contracts/producer
    $ ./mvnw clean install
  5. 在使用者测试中设置 Spring Cloud Contract (SCC) Stub Runner,以:spring-doc.cn

    • 从本地存储获取生产者存根。spring-doc.cn

    • 在 stubs-per-consumer 模式下工作(这将启用消费者驱动的 Contract 模式)。spring-doc.cn

      SCC 存根运行程序:spring-doc.cn

    • 获取生产者存根。spring-doc.cn

    • 使用创建器存根运行内存中 HTTP 服务器存根。spring-doc.cn

    • 现在,您的测试与 HTTP 服务器存根通信,并且您的测试通过spring-doc.cn

    • 使用合同定义创建者的新合同创建对存储库的拉取请求spring-doc.cn

    • 对使用者代码进行分支,直到生产者团队合并其代码spring-doc.cn

下面的 UML 图显示了使用者流:spring-doc.cn

流程概述 使用者 CDC 外部使用者

4.3. 生产者流程

制片人:spring-doc.cn

  1. 使用合同定义接管对存储库的拉取请求。你可以的 从命令行,如下所示spring-doc.cn

    $ git checkout -b the_branch_with_pull_request master
    git pull https://github.com/user_id/project_name.git the_branch_with_pull_request
  2. 安装合同定义,如下所示spring-doc.cn

    $ ./mvnw clean install
  3. 将插件设置为从 JAR 而不是 from 获取 Contract 定义,如下所示:src/test/resources/contractsspring-doc.cn

    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
    }
  4. 运行生成以生成测试和存根,如下所示:spring-doc.cn

    Maven 系列
    ./mvnw clean install
    Gradle
    ./gradlew clean build
  5. 写入缺少的实现,以使测试通过。spring-doc.cn

  6. 将拉取请求合并到带有合同定义的存储库,如下所示:spring-doc.cn

    $ 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
  7. CI 系统使用合约定义构建项目,并使用 将合同定义设置为 Nexus 或 Artifactory。spring-doc.cn

  8. 切换到远程工作。spring-doc.cn

  9. 设置插件,以便不再从本地获取 Contract 定义 存储,但从远程位置存储,如下所示:spring-doc.cn

    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
    }
  10. 将创建者代码与新实现合并。spring-doc.cn

  11. CI 系统:spring-doc.cn

下面的 UML 图显示了生产者进程:spring-doc.cn

流程概述 使用者 CDC 外部生产者

5. 消费者驱动的合约,生产者端的合约,推送到 Git

您可以查看位于生产者端的合同的消费者驱动型合同分步指南 (CDC),以查看在生产者端使用合同的消费者驱动型合同流程。spring-doc.cn

存根存储实现是一个 git 存储库。我们在 在 Git 中使用存根进行提供商合同测试 部分介绍了它的设置。spring-doc.cn

您可以在 文档的 How To 页面spring-doc.cn

6. 在 Artifactory 中使用非 Spring 应用程序的存根进行提供者合同测试

6.1. 流程

您可以检查开发第一个基于 Spring Cloud Contract 的应用程序,以查看在 Nexus 或 Artifactory 中使用存根进行提供程序 Contract 测试的流程。spring-doc.cn

6.2. 设置 Consumer

对于使用者端,您可以使用 JUnit 规则。这样,你就不需要启动 Spring 上下文。下面的清单显示了这样的规则(在 JUnit4 和 JUnit 5 中);spring-doc.cn

JUnit 4 规则
@Rule
    public StubRunnerRule rule = new StubRunnerRule()
            .downloadStub("com.example","artifact-id", "0.0.1")
            .repoRoot("git://[email protected]:spring-cloud-samples/spring-cloud-contract-nodejs-contracts-git.git")
            .stubsMode(StubRunnerProperties.StubsMode.REMOTE);
JUnit 5 扩展
@Rule
    public StubRunnerExtension stubRunnerExtension = new StubRunnerExtension()
            .downloadStub("com.example","artifact-id", "0.0.1")
            .repoRoot("git://[email protected]:spring-cloud-samples/spring-cloud-contract-nodejs-contracts-git.git")
            .stubsMode(StubRunnerProperties.StubsMode.REMOTE);

6.3. 设置 Producer

默认情况下,Spring Cloud Contract 插件使用 Rest Assure 的 生成的测试。由于非 Spring 应用程序不使用,因此您可以更改 to 以将实际请求发送到绑定到特定端口的应用程序。MockMvcMockMvctestModeEXPLICITspring-doc.cn

在此示例中,我们使用一个名为 Javalin 的框架来启动 非 Spring HTTP 服务器。spring-doc.cn

假设我们有以下应用程序:spring-doc.cn

package com.example.demo;

import io.javalin.Javalin;

public class DemoApplication {

    public static void main(String[] args) {
        new DemoApplication().run(7000);
    }

    public Javalin start(int port) {
        return Javalin.create().start(port);
    }

    public Javalin registerGet(Javalin app) {
        return app.get("/", ctx -> ctx.result("Hello World"));
    }

    public Javalin run(int port) {
        return registerGet(start(port));
    }

}

给定该应用程序,我们可以将插件设置为使用模式(即 向真实端口发送请求),如下所示:EXPLICITspring-doc.cn

maven
<plugin>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-contract-maven-plugin</artifactId>
    <version>${spring-cloud-contract.version}</version>
    <extensions>true</extensions>
    <configuration>
        <baseClassForTests>com.example.demo.BaseClass</baseClassForTests>
        <!-- This will setup the EXPLICIT mode for the tests -->
        <testMode>EXPLICIT</testMode>
    </configuration>
</plugin>
Gradle
contracts {
    // This will setup the EXPLICIT mode for the tests
    testMode = "EXPLICIT"
    baseClassForTests = "com.example.demo.BaseClass"
}

基类可能类似于以下内容:spring-doc.cn

import io.javalin.Javalin;
import io.restassured.RestAssured;
import org.junit.After;
import org.junit.Before;
import org.springframework.util.SocketUtils;

public class BaseClass {

    Javalin app;

    @Before
    public void setup() {
        // pick a random port
        int port = SocketUtils.findAvailableTcpPort();
        // start the application at a random port
        this.app = start(port);
        // tell Rest Assured where the started application is
        RestAssured.baseURI = "http://localhost:" + port;
    }

    @After
    public void close() {
        // stop the server after each test
        this.app.stop();
    }

    private Javalin start(int port) {
        // reuse the production logic to start a server
        return new DemoApplication().run(port);
    }
}

使用这样的设置:spring-doc.cn

  • 我们已经搭建了 Spring Cloud Contract 插件,使用该模式发送真实 requests 而不是 mock 的。EXPLICITspring-doc.cn

  • 我们定义了一个基类:spring-doc.cn

    • 在每个测试的随机端口上启动 HTTP 服务器。spring-doc.cn

    • 将 Rest Assured 设置为向该端口发送请求。spring-doc.cn

    • 在每次测试后关闭 HTTP 服务器。spring-doc.cn

7. 在非 JVM 环境中使用 Artifactory 中的存根进行提供者合同测试

在此流程中,我们假设:spring-doc.cn

  • API 创建者和 API 使用者是非 JVM 应用程序。spring-doc.cn

  • 协定定义是用 YAML 编写的。spring-doc.cn

  • 存根存储是 Artifactory 或 Nexus。spring-doc.cn

  • Spring Cloud Contract Docker (SCC Docker) 和 Spring Cloud Contract Stub Runner Docker (SCC Stub Runner Docker) 映像。spring-doc.cn

您可以在此页面中阅读有关如何将 Spring Cloud Contract 与 Docker 结合使用的更多信息。spring-doc.cn

在这里,您可以 阅读有关如何在多语言世界中使用 Spring Cloud Contract 的博客文章。spring-doc.cn

在这里,您可以找到 一个 NodeJS 应用程序示例,该应用程序同时使用 Spring Cloud Contract 作为生产者和 消费者。spring-doc.cn

7.1. 生产者流程

在高级别上,生产商:spring-doc.cn

  1. 写入协定定义(例如,在 YAML 中)。spring-doc.cn

  2. 将生成工具设置为:spring-doc.cn

    1. 在给定端口上使用模拟服务启动应用程序。spring-doc.cn

      如果无法进行模拟,您可以设置基础设施并以有状态的方式定义测试。spring-doc.cn

    2. 运行 Spring Cloud Contract Docker 镜像,并将正在运行的应用程序的端口作为环境变量传递。spring-doc.cn

SCC Docker 镜像: * 从附加的卷生成测试。 * 针对正在运行的应用程序运行测试。spring-doc.cn

测试完成后,存根将上传到存根存储站点(例如 Artifactory 或 Git)。spring-doc.cn

以下 UML 图显示了生产者流程:spring-doc.cn

流提供程序 non JVM producer

7.2. 消费者流程

在高级别上,消费者:spring-doc.cn

  1. 将生成工具设置为:spring-doc.cn

  2. 针对正在运行的存根运行应用程序测试。spring-doc.cn

下面的 UML 图显示了使用者流:spring-doc.cn

流提供程序非 JVM 使用者

8. 在 Nexus 或 Artifactory 中使用 REST 文档和存根进行提供商合同测试

在此流程中,我们不使用 Spring Cloud Contract 插件来生成测试和存根。我们编写 Spring RESTDoc,并从中自动生成存根。最后,我们设置构建来打包存根并将它们上传到存根存储站点 — 在我们的例子中,是 Nexus 或 Artifactory。spring-doc.cn

有关如何使用此流程的分步说明,请参阅研讨会页面spring-doc.cn

8.1. 生产者流程

作为生产商,我们:spring-doc.cn

  1. 我们编写 API 的 RESTDocs 测试。spring-doc.cn

  2. 我们将 Spring Cloud Contract Stub Runner starter 添加到我们的构建 () 中,如下所示spring-cloud-starter-contract-stub-runnerspring-doc.cn

    maven
    <dependencies>
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-contract-stub-runner</artifactId>
            <scope>test</scope>
        </dependency>
    </dependencies>
    
    <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>
    Gradle
    dependencies {
        testImplementation 'org.springframework.cloud:spring-cloud-starter-contract-stub-runner'
    }
    
    dependencyManagement {
        imports {
            mavenBom "org.springframework.cloud:spring-cloud-dependencies:${springCloudVersion}"
        }
    }
  3. 我们设置构建工具来打包我们的存根,如下所示:spring-doc.cn

    maven
    <!-- pom.xml -->
    <plugins>
        <plugin>
            <groupId>org.apache.maven.plugins</groupId>
            <artifactId>maven-assembly-plugin</artifactId>
            <executions>
                <execution>
                    <id>stub</id>
                    <phase>prepare-package</phase>
                    <goals>
                        <goal>single</goal>
                    </goals>
                    <inherited>false</inherited>
                    <configuration>
                        <attach>true</attach>
                        <descriptors>
                            ${basedir}/src/assembly/stub.xml
                        </descriptors>
                    </configuration>
                </execution>
            </executions>
        </plugin>
    </plugins>
    
    <!-- src/assembly/stub.xml -->
    <assembly
        xmlns="http://maven.apache.org/plugins/maven-assembly-plugin/assembly/1.1.3"
        xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
        xsi:schemaLocation="http://maven.apache.org/plugins/maven-assembly-plugin/assembly/1.1.3 http://maven.apache.org/xsd/assembly-1.1.3.xsd">
        <id>stubs</id>
        <formats>
            <format>jar</format>
        </formats>
        <includeBaseDirectory>false</includeBaseDirectory>
        <fileSets>
            <fileSet>
                <directory>${project.build.directory}/generated-snippets/stubs</directory>
                <outputDirectory>META-INF/${project.groupId}/${project.artifactId}/${project.version}/mappings</outputDirectory>
                <includes>
                    <include>**/*</include>
                </includes>
            </fileSet>
        </fileSets>
    </assembly>
    Gradle
    task stubsJar(type: Jar) {
        classifier = "stubs"
        into("META-INF/${project.group}/${project.name}/${project.version}/mappings") {
            include('**/*.*')
            from("${project.buildDir}/generated-snippets/stubs")
        }
    }
    // we need the tests to pass to build the stub jar
    stubsJar.dependsOn(test)
    bootJar.dependsOn(stubsJar)

现在,当我们运行测试时,存根会自动发布和打包。spring-doc.cn

以下 UML 图显示了生产者流程:spring-doc.cn

流提供程序 REST 文档创建者

8.2. 消费者流程

由于使用者流不受用于生成存根的工具的影响,因此您可以检查开发第一个基于 Spring Cloud Contract 的应用程序,以查看使用 Nexus 或 Artifactor 中的存根进行提供者 Contract 测试的使用者端的流程。spring-doc.cn

9. 接下来要读什么

现在,您应该了解如何使用 Spring Cloud Contract 以及 应该紧随其后。您现在可以继续了解特定的 Spring Cloud Contract 功能,或者您可以 跳到前面并阅读 Spring Cloud Contract 的高级功能spring-doc.cn