“作方法”指南

1. 为什么要使用 Spring Cloud Contract?

Spring Cloud Contract 在多语言环境中运行良好。这个项目有很多 非常有趣的功能。其中相当多的功能肯定使 Spring Cloud Contract Verifier 在消费者驱动合约市场脱颖而出 (CDC) 工具。最有趣的功能包括:spring-doc.cadn.net.cn

2. 如何用 Groovy 以外的语言编写合约?

你可以用 YAML 编写合约。有关详细信息,请参阅此部分spring-doc.cadn.net.cn

我们正在努力允许更多描述合同的方式。您可以查看 github-issues 了解更多信息。spring-doc.cadn.net.cn

3. 如何为合约提供动态值?

与存根相关的最大挑战之一是它们的可重用性。只有能够广泛使用,才能达到其目的。 请求和响应元素的硬编码值(例如日期和 ID)通常使这变得困难。 考虑以下 JSON 请求:spring-doc.cadn.net.cn

{
    "time" : "2016-10-10 20:10:15",
    "id" : "9febab1c-6f36-4a0b-88d6-3b6a6d81cd4a",
    "body" : "foo"
}

现在考虑以下 JSON 响应:spring-doc.cadn.net.cn

{
    "time" : "2016-10-10 21:10:15",
    "id" : "c4231e1f-3ca9-48d3-b7e7-567d55f0d051",
    "body" : "bar"
}

想象一下设置适当的值所需的痛苦time字段(假设此内容是由 数据库)通过更改系统中的时钟或提供数据提供者的存根实现。同样是相关的 到id田。您可以创建 UUID 生成器的存根实现,但这样做没有什么意义。spring-doc.cadn.net.cn

因此,作为消费者,您希望发送与任何形式的时间或任何 UUID 匹配的请求。这样,您的系统 照常工作,无需存根即可生成数据。假设,在上述情况下 JSON,最重要的部分是body田。您可以专注于此并为其他领域提供匹配。换句话说, 您希望存根按如下方式工作:spring-doc.cadn.net.cn

{
    "time" : "SOMETHING THAT MATCHES TIME",
    "id" : "SOMETHING THAT MATCHES UUID",
    "body" : "foo"
}

就响应而言,作为消费者,您需要一个可以作的具体值。 因此,以下 JSON 有效:spring-doc.cadn.net.cn

{
    "time" : "2016-10-10 21:10:15",
    "id" : "c4231e1f-3ca9-48d3-b7e7-567d55f0d051",
    "body" : "bar"
}

在前面的部分中,我们从合约生成了测试。所以,从制片方的角度来看,情况看起来 大不相同。我们解析提供的合约,并且在测试中,我们希望向您的端点发送一个真实的请求。 因此,对于请求的生产者,我们不能进行任何形式的匹配。我们需要具体的值,其中 producer 的后端可以工作。因此,以下 JSON 将有效:spring-doc.cadn.net.cn

{
    "time" : "2016-10-10 20:10:15",
    "id" : "9febab1c-6f36-4a0b-88d6-3b6a6d81cd4a",
    "body" : "foo"
}

另一方面,从合同效力的角度来看,响应不一定非要 包含具体值timeid.假设您在生产者端生成这些。再说一遍,你 必须进行大量存根处理,以确保始终返回相同的值。这就是为什么从制作人方面来看, 您可能需要以下响应:spring-doc.cadn.net.cn

{
    "time" : "SOMETHING THAT MATCHES TIME",
    "id" : "SOMETHING THAT MATCHES UUID",
    "body" : "bar"
}

然后,您如何为消费者提供匹配器,为生产者提供具体值(在其他时间则相反)? Spring Cloud Contract 允许您提供动态值。这意味着两者的情况可能不同 沟通的双方。spring-doc.cadn.net.cn

您可以在合约 DSL 部分阅读有关此内容的更多信息。spring-doc.cadn.net.cn

阅读与 JSON 相关的 Groovy 文档,了解如何 正确构建请求和响应正文。

4. 如何进行存根版本控制?

本节介绍存根的版本控制,您可以通过多种不同的方式处理这些版本控制:spring-doc.cadn.net.cn

4.1. API 版本控制

版本控制的真正含义是什么?如果参考 API 版本,有 不同的方法:spring-doc.cadn.net.cn

我们不试图回答哪种方法更好的问题。你应该选择任何 满足您的需求,让您创造商业价值。spring-doc.cadn.net.cn

假设您确实对 API 进行了版本控制。在这种情况下,您应该提供尽可能多的协定和您支持的版本。 您可以为每个版本创建一个子文件夹,也可以将其附加到合同名称中 - 任何最适合您的版本。spring-doc.cadn.net.cn

4.2. JAR版本控制

如果版本控制是指包含存根的 JAR 版本,那么基本上有两种主要方法。spring-doc.cadn.net.cn

假设您执行持续交付和部署,这意味着您生成一个新版本的 每次通过管道时的 jar,并且 jar 可以随时投入生产。例如,您的 jar 版本 如下所示(因为它是在 2016 年 10 月 20 日 20:15:21 上构建的):spring-doc.cadn.net.cn

1.0.0.20161020-201521-RELEASE

在这种情况下,您生成的存根 jar 应如下所示:spring-doc.cadn.net.cn

1.0.0.20161020-201521-RELEASE-stubs.jar

在这种情况下,您应该在application.yml@AutoConfigureStubRunner什么时候 引用存根时,提供存根的最新版本。您可以通过传递标志来做到这一点。以下示例显示了如何执行此作:+spring-doc.cadn.net.cn

@AutoConfigureStubRunner(ids = {"com.example:http-server-dsl:+:stubs:8080"})

但是,如果版本控制是固定的(例如,1.0.4.RELEASE2.1.1),您必须设置 jar 的具体值 版本。以下示例显示了如何为版本 2.1.1 执行此作:spring-doc.cadn.net.cn

@AutoConfigureStubRunner(ids = {"com.example:http-server-dsl:2.1.1:stubs:8080"})

4.3. 开发或生产存根

您可以作分类器以针对当前开发版本运行测试 其他服务的存根或部署到生产环境的存根。如果您更改 您的构建来部署存根,并使用prod-stubs生产后的分类器 部署时,您可以在一种情况下使用开发存根运行测试,在另一种情况下使用生产存根运行测试。spring-doc.cadn.net.cn

以下示例适用于使用存根开发版本的测试:spring-doc.cadn.net.cn

@AutoConfigureStubRunner(ids = {"com.example:http-server-dsl:+:stubs:8080"})

以下示例适用于使用存根生产版本的测试:spring-doc.cadn.net.cn

@AutoConfigureStubRunner(ids = {"com.example:http-server-dsl:+:prod-stubs:8080"})

还可以在部署管道的属性中传递这些值。spring-doc.cadn.net.cn

5. 如何将通用存储库与合约一起使用,而不是将它们存储在生产者那里?

存储合同的另一种方式,而不是将它们交给生产者,是保留 它们在一个共同的地方。这种情况可能与安全问题有关(其中 消费者无法克隆生产者的代码)。另外,如果您将合同保存在一个地方, 然后,作为生产者,你知道你有多少消费者,你可能会破坏哪个消费者 与您本地的更改。spring-doc.cadn.net.cn

5.1. Repo 结构

假设我们有一个坐标为com.example:server和三个 消费者:client1,client2client3.然后,在存储库中,使用 common 合约,您可以进行以下设置(您可以在此处查看)。 以下列表显示了这样的结构:spring-doc.cadn.net.cn

├── com
│   └── example
│       └── server
│           ├── client1
│           │   └── expectation.groovy
│           ├── client2
│           │   └── expectation.groovy
│           ├── client3
│           │   └── expectation.groovy
│           └── pom.xml
├── mvnw
├── mvnw.cmd
├── pom.xml
└── src
    └── assembly
        └── contracts.xml

在斜杠分隔下groupid/artifact id文件夹 (com/example/server),你有 三大消费者的期望(client1,client2client3).期望是标准的 Groovy DSL contract 文件,如本文档中所述。此存储库必须生成一个 JAR 文件,该文件映射 一对一访问存储库的内容。spring-doc.cadn.net.cn

以下示例显示了pom.xml文件server文件夹:spring-doc.cadn.net.cn

<?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.15</version>
        <relativePath/>
    </parent>

    <properties>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
        <java.version>1.8</java.version>
        <spring-cloud-contract.version>3.1.10-SNAPSHOT</spring-cloud-contract.version>
        <spring-cloud-release.version>2021.0.10-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在本地安装 生产者项目的存根。spring-doc.cadn.net.cn

pom.xml根文件夹中的文件可能如下所示:spring-doc.cadn.net.cn

<?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。以下示例 显示这样的设置:spring-doc.cadn.net.cn

<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. 工作流程

该工作流假定 Spring Cloud Contract 是在消费者和 制片人方面。在公共存储库中也有适当的插件设置,其中 合同。CI 作业设置为公共存储库,以构建所有 合同并将其上传到 Nexus 或 Artifactory。下图显示了此 UML 工作流程:spring-doc.cadn.net.cn

如何共同存储

5.3. 消费者

当消费者想要离线处理合约而不是克隆生产者时 代码,消费者团队克隆公共存储库,转到所需生产者的 文件夹(例如,com/example/server) 并运行mvn clean install -DskipTests自 在本地安装从合约转换的存根。spring-doc.cadn.net.cn

您需要在本地安装 Maven

5.4. 生产者

作为生产者,您可以更改 Spring Cloud Contract Verifier 以提供 URL 和 包含合约的 JAR 的依赖关系,如下所示:spring-doc.cadn.net.cn

<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>

通过此设置,具有groupidcom.example.standaloneartifactidcontracts下载自link/to/your/nexus/or/artifactory/or/sth.是的 然后在本地临时文件夹中解压,并且com/example/server被选为用于生成测试和存根的存根。由于 对于这个约定,生产者团队可以知道哪些消费者团队在什么时候被破坏了 进行了一些不兼容的更改。spring-doc.cadn.net.cn

其余的流程看起来是一样的。spring-doc.cadn.net.cn

5.5. 如何定义每个主题而不是每个生产者的消息传递契约?

为了避免在公共仓库中重复消息传递契约,当几个生产者将消息写入一个主题时, 我们可以创建一个结构,其中 REST 契约被放置在每个生产者和消息传递的文件夹中 合同放置在每个主题的文件夹中。spring-doc.cadn.net.cn

5.5.1. 对于 Maven 项目

为了能够在生产者端工作,我们应该为 按我们感兴趣的消息传递主题过滤常见的存储库 jar 文件。这includedFilesMaven Spring Cloud Contract 插件的属性 让我们这样做。也contractsPath需要指定,因为默认路径为 通用存储库groupid/artifactid.以下示例显示了 Maven Spring Cloud Contract 插件:spring-doc.cadn.net.cn

<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 项目,请执行以下作:spring-doc.cadn.net.cn

  1. 为公共存储库依赖项添加自定义配置,如下所示:spring-doc.cadn.net.cn

    ext {
        contractsGroupId = "com.example"
        contractsArtifactId = "common-repo"
        contractsVersion = "1.2.3"
    }
    
    configurations {
        contracts {
            transitive = false
        }
    }
    
  2. 将公共存储库依赖项添加到类路径,如下所示:spring-doc.cadn.net.cn

    dependencies {
        contracts "${contractsGroupId}:${contractsArtifactId}:${contractsVersion}"
        testCompile "${contractsGroupId}:${contractsArtifactId}:${contractsVersion}"
    }
    
  3. 将依赖项下载到适当的文件夹,如下所示:spring-doc.cadn.net.cn

    task getContracts(type: Copy) {
        from configurations.contracts
        into new File(project.buildDir, "downloadedContracts")
    }
    
  4. 解压缩 JAR,如下所示:spring-doc.cadn.net.cn

    task unzipContracts(type: Copy) {
        def zipFile = new File(project.buildDir, "downloadedContracts/${contractsArtifactId}-${contractsVersion}.jar")
        def outputDir = file("${buildDir}/unpackedContracts")
    
        from zipTree(zipFile)
        into outputDir
    }
    
  5. 清理未使用的合约,如下所示:spring-doc.cadn.net.cn

    task deleteUnwantedContracts(type: Delete) {
        delete fileTree(dir: "${buildDir}/unpackedContracts",
            include: "**/*",
            excludes: [
                "**/${project.name}/**"",
                "**/${first-topic}/**",
                "**/${second-topic}/**"])
    }
    
  6. 创建任务依赖项,如下所示:spring-doc.cadn.net.cn

    unzipContracts.dependsOn("getContracts")
    deleteUnwantedContracts.dependsOn("unzipContracts")
    build.dependsOn("deleteUnwantedContracts")
    
  7. 通过指定包含合约的目录来配置插件,方法是将 这contractsDslDir属性,如下所示:spring-doc.cadn.net.cn

    contracts {
        contractsDslDir = new File("${buildDir}/unpackedContracts")
    }
    

6. 如何使用 Git 作为合约和存根的存储?

在多语言世界中,有些语言不使用二进制存储,因为 Artifactory 和 Nexus 可以。从 Spring Cloud Contract 2.0.0 版本开始,我们提供了 将合约和存根存储在 SCM(源代码控制管理)存储库中的机制。目前, 唯一支持的 SCM 是 Git。spring-doc.cadn.net.cn

存储库必须具有以下设置 (您可以从这里结账):spring-doc.cadn.net.cn

.
└── 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文件夹:spring-doc.cadn.net.cn

  • 我们按以下方式对应用程序进行分组groupId(例如com.example).spring-doc.cadn.net.cn

  • 每个应用程序都由其artifactId(例如,beer-api-producer-git).spring-doc.cadn.net.cn

  • 接下来,每个应用程序按其版本组织(例如0.0.1-SNAPSHOT).开始 从 Spring Cloud Contract 版本2.1.0,您可以按如下方式指定版本 (假设您的版本遵循语义版本控制):spring-doc.cadn.net.cn

最后,有两个文件夹:spring-doc.cadn.net.cn

  • contracts:好的做法是存储每个人所需的合同 consumer 在具有消费者名称的文件夹中(例如beer-api-consumer).这样,您 可以使用stubs-per-consumer特征。进一步的目录结构是任意的。spring-doc.cadn.net.cn

  • mappings:Maven 或 Gradle Spring Cloud Contract 插件推送 此文件夹中的存根服务器映射。在消费者端,Stub Runner 扫描此文件夹 以启动具有存根定义的存根服务器。文件夹结构是副本 在contracts子文件夹。spring-doc.cadn.net.cn

6.1. 协议公约

控制合同来源的类型和位置(是否 二进制存储或 SCM 存储库),您可以在 存储库。Spring Cloud Contract 迭代已注册的协议解析器 并尝试获取合约(通过使用插件)或存根(从 Stub Runner)。spring-doc.cadn.net.cn

对于 SCM 功能,目前我们支持 Git 存储库。要使用它, 在需要放置存储库 URL 的属性中,您必须添加前缀 连接 URL 与git://.以下列表显示了一些示例:spring-doc.cadn.net.cn

git://file:///foo/bar
git://https://github.com/spring-cloud-samples/spring-cloud-contract-nodejs-contracts-git.git
git://[email protected]:spring-cloud-samples/spring-cloud-contract-nodejs-contracts-git.git

6.2. 生产者

对于生产者来说,要使用 SCM(源代码控制管理)方法,我们可以重用 与我们用于外部合同的机制相同。我们路由 Spring Cloud Contract 从以 这git://协议。spring-doc.cadn.net.cn

您必须手动添加pushStubsToScm目标或使用(绑定)Maven 中的pushStubsToScmtask 中的 Gradle。我们不会将存根推送到origin你的 git 存储 库。

以下列表包括 Maven 和 Gradle 构建文件的相关部分:spring-doc.cadn.net.cn

专家
<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
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://https://github.com/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 invoked
*/
publish.dependsOn("publishStubsToScm")

您还可以进一步自定义publishStubsToScmgradle 任务。在以下示例中, 该任务是自定义的,以从本地 Git 存储库中选取合约:spring-doc.cadn.net.cn

Gradle
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.RELEASEcustomize{}闭包之前用于publishStubsToScm不再提供定制。应直接应用设置 在publishStubsToScm闭包,如前面的示例所示。spring-doc.cadn.net.cn

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

6.3. 合约存储在本地的生产者

使用 SCM 作为存根和合同目标的另一种选择是将 在本地与生产者签订合同,并且仅将合同和存根推送到 SCM。 以下列表显示了使用 Maven 和 Gradle 实现此目的所需的设置:spring-doc.cadn.net.cn

专家
<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://[email protected]: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>
Gradle
contracts {
    testFramework = "JUNIT5"
        // Base package for generated tests
    basePackageForTests = "com.example"
    baseClassMappings {
        baseClassMapping(".*messaging.*", "com.example.BeerMessagingBase")
        baseClassMapping(".*rest.*", "com.example.BeerRestBase")
    }

    /*
    In this scenario we want to publish stubs to SCM whenever
    the `publish` task is executed
    */
    publishStubsToScm {

        // We want to pick contracts from a Git repository
        contractDependency {
            stringNotation = "${project.group}:${project.name}:${project.version}"
        }

        contractRepository {
            /*
            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
             */
            repositoryUrl = "git://file://${new File(project.rootDir, "../target")}/contract_empty_git/"
        }
    }
}

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

6.4. 将与生产者的契约和存根保存在外部存储库中

您也可以将合约保留在生产者存储库中,但将存根保留在外部 git 存储库中。 当您想要使用基本使用者-生产者协作流但不能使用时,这最有用 使用工件存储库来存储存根。spring-doc.cadn.net.cn

为此,请使用通常的生产者设置,然后将pushStubsToScm目标和设定contractsRepositoryUrl到要保存存根的存储库。spring-doc.cadn.net.cn

6.5. 消费者

在消费者端,当传递repositoryRoot参数 从@AutoConfigureStubRunner注释,则 JUnit 4 规则、JUnit 5 扩展或属性,您可以传递 SCM 存储库,前缀为git://协议。以下示例显示了如何执行此作:spring-doc.cadn.net.cn

@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"
)

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

7. 如何使用 Pact 经纪人?

使用 Pact 时,您可以使用 Pact Broker 来存储和共享 Pact 定义。从 Spring Cloud Contract 开始 2.0.0,您可以从 Pact Broker 中获取 Pact 文件以生成 测试和存根。spring-doc.cadn.net.cn

Pact 遵循消费者合同惯例。这意味着 消费者首先创建契约定义,然后再创建 与制作人共享文件。这些期望是产生的 如果期望 没有得到满足。

7.1. 如何使用 Pact

Spring Cloud Contract 包括对 Pact 表示的支持 合同直到版本 4。您可以使用 Pact 文件来代替 DSL。在本节中,我们将 展示如何为您的项目添加 Pact 支持。但请注意,并非所有功能都受支持。 从版本 3 开始,您可以为同一元素组合多个匹配器: 您可以对正文、标头、请求和路径使用匹配器,也可以使用值生成器。 Spring Cloud Contract 目前仅支持使用AND规则逻辑。 除此之外,在转换过程中将跳过请求和路径匹配器。 使用具有给定格式的日期、时间或日期时间值生成器时, 跳过给定的格式并使用 ISO 格式。spring-doc.cadn.net.cn

7.2. 契约转换器

为了正确支持 Spring Cloud Contract 的消息传递方式 使用 Pact,您必须提供一些额外的元数据条目。spring-doc.cadn.net.cn

要定义消息发送到的目标,您必须 设置一个metaData条目与sentTo键等于目标 要发送的消息(例如,"metaData": { "sentTo": "activemq:output" }).spring-doc.cadn.net.cn

7.3. 契约合同

Spring Cloud Contract 可以读取 Pact JSON 定义。您可以将文件放在src/test/resources/contracts文件夹。记得把spring-cloud-contract-pact依赖于你的类路径。spring-doc.cadn.net.cn

7.4. 生产者契约

在生产者端,您必须向插件添加两个额外的依赖项 配置。一个是 Spring Cloud Contract Pact 支持,另一个代表 您使用的当前 Pact 版本。以下列表显示了如何对两者执行此作 Maven 和 Gradle:spring-doc.cadn.net.cn

专家
<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>
Gradle
    testImplementation("org.springframework.cloud:spring-cloud-contract-pact")
contracts {
    testFramework = "JUNIT5"
    contractDependency {
        stringNotation = "${project.group}:${project.name}:+"
    }
    contractsMode = "REMOTE"
    // Base package for generated tests
    baseClassForTests = "com.example.BeerRestBase"
    contractRepository {
        repositoryUrl = "pact://http://localhost:8085"
    }
}

构建应用程序时,会生成一个测试和存根。以下内容 示例显示了来自此过程的测试和存根:spring-doc.cadn.net.cn

测试
@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");
    }
存根
{
  "id" : "996ae5ae-6834-4db6-8fac-358ca187ab62",
  "uuid" : "996ae5ae-6834-4db6-8fac-358ca187ab62",
  "request" : {
    "url" : "/fraudcheck",
    "method" : "PUT",
    "headers" : {
      "Content-Type" : {
        "matches" : "application/vnd\\.fraud\\.v1\\+json.*"
      }
    },
    "bodyPatterns" : [ {
      "matchesJsonPath" : "$[?(@.['loanAmount'] = 99999)]"
    }, {
      "matchesJsonPath" : "$[?(@.clientId =~ /([0-9]{10})/)]"
    } ]
  },
  "response" : {
    "status" : 200,
    "body" : "{\"fraudCheckStatus\":\"FRAUD\",\"rejectionReason\":\"Amount too high\"}",
    "headers" : {
      "Content-Type" : "application/vnd.fraud.v1+json;charset=UTF-8"
    },
    "transformers" : [ "response-template" ]
  },
}

7.5. 消费者契约

在使用者端,您必须向项目添加两个额外的依赖项 依赖。一个是 Spring Cloud Contract Pact 支持,另一个代表 您使用的当前 Pact 版本。以下列表显示了如何对两者执行此作 Maven 和 Gradle:spring-doc.cadn.net.cn

专家
<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>
Gradle
testImplementation("org.springframework.cloud:spring-cloud-starter-contract-stub-runner")
testImplementation("org.springframework.cloud:spring-cloud-contract-pact")

7.6. 与 Pact Broker 沟通

每当repositoryRoot属性以 Pact 协议开头 (即以pact://),存根下载器尝试 从 Pact Broker 获取 Pact 合约定义。 无论设置什么pact://被解析为 Pact 代理 URL。spring-doc.cadn.net.cn

通过设置环境变量、系统属性或属性集 在插件或合约存储库配置中,您可以 调整下载器的行为。下表描述了 性能:spring-doc.cadn.net.cn

表 1.Pact 存根下载器属性

属性名称spring-doc.cadn.net.cn

默认值spring-doc.cadn.net.cn

描述spring-doc.cadn.net.cn

* pactbroker.host(插件道具)spring-doc.cadn.net.cn

* stubrunner.properties.pactbroker.host(系统道具)spring-doc.cadn.net.cn

* STUBRUNNER_PROPERTIES_PACTBROKER_HOST(环境道具)spring-doc.cadn.net.cn

将 URL 中的主机传递给repositoryRootspring-doc.cadn.net.cn

协议代理的 URL。spring-doc.cadn.net.cn

* pactbroker.port(插件道具)spring-doc.cadn.net.cn

* stubrunner.properties.pactbroker.port(系统道具)spring-doc.cadn.net.cn

* STUBRUNNER_PROPERTIES_PACTBROKER_PORT(环境道具)spring-doc.cadn.net.cn

从 URL 传递给repositoryRootspring-doc.cadn.net.cn

Pact Broker 的端口。spring-doc.cadn.net.cn

* pactbroker.protocol(插件道具)spring-doc.cadn.net.cn

* stubrunner.properties.pactbroker.protocol(系统道具)spring-doc.cadn.net.cn

* STUBRUNNER_PROPERTIES_PACTBROKER_PROTOCOL(环境道具)spring-doc.cadn.net.cn

从 URL 传递到repositoryRootspring-doc.cadn.net.cn

Pact Broker 的协议。spring-doc.cadn.net.cn

* pactbroker.tags(插件道具)spring-doc.cadn.net.cn

* stubrunner.properties.pactbroker.tags(系统道具)spring-doc.cadn.net.cn

* STUBRUNNER_PROPERTIES_PACTBROKER_TAGS(环境道具)spring-doc.cadn.net.cn

存根的版本,或latest如果 version 为+spring-doc.cadn.net.cn

应用于获取存根的标签。spring-doc.cadn.net.cn

* pactbroker.auth.scheme(插件道具)spring-doc.cadn.net.cn

* stubrunner.properties.pactbroker.auth.scheme(系统道具)spring-doc.cadn.net.cn

* STUBRUNNER_PROPERTIES_PACTBROKER_AUTH_SCHEME(环境道具)spring-doc.cadn.net.cn

Basicspring-doc.cadn.net.cn

应用于连接到 Pact 代理的身份验证类型。spring-doc.cadn.net.cn

* pactbroker.auth.username(插件道具)spring-doc.cadn.net.cn

* stubrunner.properties.pactbroker.auth.username(系统道具)spring-doc.cadn.net.cn

* STUBRUNNER_PROPERTIES_PACTBROKER_AUTH_USERNAME(环境道具)spring-doc.cadn.net.cn

传递给的用户名contractsRepositoryUsername(Maven) 或contractRepository.username(Gradle)spring-doc.cadn.net.cn

连接到 Pact 代理时要使用的用户名。spring-doc.cadn.net.cn

* pactbroker.auth.password(插件道具)spring-doc.cadn.net.cn

* stubrunner.properties.pactbroker.auth.password(系统道具)spring-doc.cadn.net.cn

* STUBRUNNER_PROPERTIES_PACTBROKER_AUTH_PASSWORD(环境道具)spring-doc.cadn.net.cn

传递给contractsRepositoryPassword(Maven) 或contractRepository.password(Gradle)spring-doc.cadn.net.cn

连接到 Pact 代理时使用的密码。spring-doc.cadn.net.cn

* pactbroker.provider-name-with-group-id(插件道具)spring-doc.cadn.net.cn

* stubrunner.properties.pactbroker.provider-name-with-group-id(系统道具)spring-doc.cadn.net.cn

* STUBRUNNER_PROPERTIES_PACTBROKER_PROVIDER_NAME_WITH_GROUP_ID(环境道具)spring-doc.cadn.net.cn

falsespring-doc.cadn.net.cn

什么时候true,则提供者名称是groupId:artifactId.如果falseartifactId被使用。spring-doc.cadn.net.cn

7.7. 流程:消费者端与 Pact Broker 的消费者合同方法

使用者使用 Pact 框架生成 Pact 文件。 这 Pact 文件被发送到 Pact Broker。您可以在此处找到此类设置的示例。spring-doc.cadn.net.cn

7.8. 流程:生产者端与 Pact Broker 的消费者合同方法

为了让生产者使用 Pact Broker 中的 Pact 文件,我们可以重用与我们用于外部合约的机制相同。我们将 Spring Cloud Contract与包含 URL 的 URL 一起使用 Pact 实现 这pact://协议。 您可以将 URL 传递给Pact 代理。您可以在此处找到此类设置的示例。以下列表显示了 Maven 和 Gradle 的配置详细信息:spring-doc.cadn.net.cn

专家
<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>
Gradle
buildscript {
    repositories {
        //...
    }

    dependencies {
        // ...
        // Don't forget to add spring-cloud-contract-pact to the classpath!
        classpath "org.springframework.cloud:spring-cloud-contract-pact:${contractVersion}"
    }
}

contracts {
    // When + is passed, a latest tag will be applied when fetching pacts
    contractDependency {
        stringNotation = "${project.group}:${project.name}:+"
    }
    contractRepository {
        repositoryUrl = "pact://http://localhost:8085"
    }
    // The mode can't be classpath
    contractsMode = "REMOTE"
    // Base class mappings etc.
}

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

7.9. 流程:消费者方面与 Pact 的生产者合同方法

在不想执行消费者契约方法的场景中 (对于每个消费者,定义期望)但您更喜欢 执行生产者合同(生产者提供合同和 publishes 存根),您可以将 Spring Cloud Contract 与 存根流道选项。您可以在此处找到此类设置的示例。spring-doc.cadn.net.cn

记得添加 Stub Runner 和 Spring Cloud Contract Pact 模块 作为测试依赖项。spring-doc.cadn.net.cn

以下列表显示了 Maven 和 Gradle 的配置详细信息:spring-doc.cadn.net.cn

专家
<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>
Gradle
dependencyManagement {
    imports {
        mavenBom "org.springframework.cloud:spring-cloud-dependencies:${springCloudVersion}"
    }
}

dependencies {
    //...
    testCompile("org.springframework.cloud:spring-cloud-starter-contract-stub-runner")
    // Don't forget to add spring-cloud-contract-pact to the classpath!
    testCompile("org.springframework.cloud:spring-cloud-contract-pact")
}

接下来,您可以将 Pact Broker 的 URL 传递给repositoryRoot前缀 使用pact://协议(例如,pact://http://localhost:8085),如下所示 示例显示:spring-doc.cadn.net.cn

@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-doc.cadn.net.cn

8. 如何调试生成的测试客户端发送的请求/响应?

生成的测试都以某种形式或方式归结为 RestAssured。放心 依赖于 Apache HttpClient。 HttpClient 有一个称为 wire logging 的工具, 它将整个请求和响应记录到 HttpClient。Spring Boot 有一个日志记录通用应用程序属性来执行此类作。要使用它,请将其添加到应用程序属性中,如下所示:spring-doc.cadn.net.cn

logging.level.org.apache.http.wire=DEBUG

9. 如何调试 WireMock 发送的映射、请求或响应?

从版本开始1.2.0,我们将 WireMock 日志记录设置为info并将 WireMock 通知程序设置为详细。现在你可以 确切地知道 WireMock 服务器收到了什么请求以及哪些请求 选择匹配的响应定义。spring-doc.cadn.net.cn

要关闭此功能,请将 WireMock 日志记录设置为ERROR如下:spring-doc.cadn.net.cn

logging.level.com.github.tomakehurst.wiremock=ERROR

10. 如何查看 HTTP 服务器存根中注册的内容?

您可以使用mappingsOutputFolder属性@AutoConfigureStubRunner,StubRunnerRuleStubRunnerExtension转储每个工件 ID 的所有映射。此外,给定存根服务器所在的端口 已启动并附上。spring-doc.cadn.net.cn

11. 如何从文件中引用文本?

在 1.2.0 版本中,我们添加了此功能。您可以调用file(…​)方法 DSL 并提供相对于合约所在位置的路径。 如果您使用 YAML,则可以使用bodyFromFile财产。spring-doc.cadn.net.cn

12. 如何从 Spring Cloud 合约合约生成 Pact、YAML 或 X 文件?

Spring Cloud Contract 附带了一个ToFileContractsTransformer允许您转储的类 合同作为给定文件ContractConverter.它包含一个static void main方法,允许您将 Transformer 作为可执行文件运行。它需要以下内容 参数:spring-doc.cadn.net.cn

  • 论点 1 :FQN:完全限定的名称ContractConverter(例如,PactContractConverter).必需spring-doc.cadn.net.cn

  • 论点 2 :path:应存储转储文件的路径。OPTIONAL — 默认为target/converted-contracts.spring-doc.cadn.net.cn

  • 论点 3 :path:应搜索合同的路径。OPTIONAL — 默认为src/test/resources/contracts.spring-doc.cadn.net.cn

调用 transformer 后,处理 Spring Cloud Contract 文件,并且,根据提供的 FQNContractTransformer,则将合约转换为为所需的格式并转储到提供的文件夹中。spring-doc.cadn.net.cn

以下示例展示了如何为 Maven 和 Gradle 配置 Pact 集成:spring-doc.cadn.net.cn

专家
<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>
Gradle
task convertContracts(type: JavaExec) {
    main = "org.springframework.cloud.contract.verifier.util.ToFileContractsTransformer"
    classpath = sourceSets.test.compileClasspath
    args("org.springframework.cloud.contract.verifier.spec.pact.PactContractConverter",
            "${project.rootDir}/build/pacts", "${project.rootDir}/src/test/resources/contracts")
}

test.dependsOn("convertContracts")

13. 如何使用传递依赖项?

Spring Cloud Contract 插件添加了为您创建存根 jar 的任务。 一 出现的问题是,在重用存根时,您可能会错误地导入所有该存根的依赖项。在构建 Maven 工件时,即使您有几个不同的罐子,它们都共享一个pom.xml文件,如以下列表所示:spring-doc.cadn.net.cn

├── 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
├── ...
└── ...

使用这些依赖项有三种可能性,以免出现任何传递依赖项的问题:spring-doc.cadn.net.cn

13.1. 如何将所有应用程序依赖项标记为可选?

如果,在producer应用程序时,您将所有依赖项标记为可选,当您包含producer存根(或者当该依赖项被存根运行器下载时),那么,由于所有依赖项都是可选的,因此它们不会被下载。spring-doc.cadn.net.cn

13.2. 如何创建单独的artifactid对于存根?

如果您创建单独的artifactid,您可以按照任何您想要的方式进行设置。例如,您可能决定根本没有依赖项。spring-doc.cadn.net.cn

13.3. 如何排除消费者端的依赖关系?

作为消费者,如果您将存根依赖项添加到类路径中,则可以显式排除不需要的依赖项。spring-doc.cadn.net.cn

14. 如何从合约中生成 Spring REST 文档片段?

当你想使用 Spring REST Docs 包含 API 的请求和响应时, 如果您使用的是 MockMvc 和 RestAssuredMockMvc,则只需对设置进行一些细微的更改。 为此,请包含以下依赖项(如果您尚未这样做):spring-doc.cadn.net.cn

专家
<dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-contract-verifier</artifactId>
    <scope>test</scope>
</dependency>
<dependency>
    <groupId>org.springframework.restdocs</groupId>
    <artifactId>spring-restdocs-mockmvc</artifactId>
    <optional>true</optional>
</dependency>
Gradle
testImplementation 'org.springframework.cloud:spring-cloud-starter-contract-verifier'
testImplementation 'org.springframework.restdocs:spring-restdocs-mockmvc'

接下来,您需要对基类进行一些更改。以下示例使用WebAppContext以及带有 RestAssured 的独立选项:spring-doc.cadn.net.cn

Web应用上下文
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;
    }

}
独立
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.restdocs.RestDocumentationContextProvider;
import org.springframework.restdocs.RestDocumentationExtension;
import org.springframework.test.web.servlet.setup.MockMvcBuilders;

import static org.springframework.restdocs.mockmvc.MockMvcRestDocumentation.document;
import static org.springframework.restdocs.mockmvc.MockMvcRestDocumentation.documentationConfiguration;

@ExtendWith(RestDocumentationExtension.class)
public abstract class FraudBaseWithStandaloneSetup {

    @BeforeEach
    public void setup(TestInfo info, RestDocumentationContextProvider restDocumentation) {
        RestAssuredMockMvc.standaloneSetup(MockMvcBuilders
                .standaloneSetup(new FraudDetectionController())
                .apply(documentationConfiguration(restDocumentation))
                .alwaysDo(document(
                        getClass().getSimpleName() + "_" + info.getDisplayName())));
    }

}
您无需为生成的代码片段指定输出目录(自 Spring REST Docs 版本 1.2.0.RELEASE 开始)。

15. 如何使用某个位置的存根

如果您想从给定位置获取合约或存根而不克隆存储库或获取 JAR,请使用stubs://协议,为存根运行器或 Spring Cloud Contract 插件提供存储库根参数时。您可以在文档的这一部分中阅读有关此内容的更多信息。spring-doc.cadn.net.cn

16. 如何在运行时生成存根

如果要在运行时为合约生成存根,请将generateStubs属性中的@AutoConfigureStubRunner注释,或调用withGenerateStubs(true)方法。您可以在文档的这一部分中阅读有关此内容的更多信息。spring-doc.cadn.net.cn

17. 如果没有合同或存根,如何让构建通过

如果希望 Stub Runner 在未找到存根时不失败,请将generateStubs属性中的@AutoConfigureStubRunner注释或调用withFailOnNoStubs(false)方法。您可以在文档的这一部分中阅读有关此内容的更多信息。spring-doc.cadn.net.cn

如果您希望插件在未找到合约时不会使构建失败,您可以将failOnNoStubs标记或调用contractRepository { failOnNoStubs(false) }关闭。spring-doc.cadn.net.cn

18. 如何标记合同正在进行中

如果合约正在进行中,则意味着在生产者端不会生成测试,但会生成存根。您可以在文档的这一部分中阅读有关此内容的更多信息。spring-doc.cadn.net.cn

在 CI 构建中,在投入生产之前,您希望确保类路径上没有正在进行的协定,因为它们可能会导致误报。为此,默认情况下,在 Spring Cloud Contract 插件中,我们将failOnInProgresstrue.如果要在生成测试时允许此类协定,请将标志设置为false.spring-doc.cadn.net.cn