“作方法”指南

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

在前面的章节中,我们从合约中生成了测试。因此,从生产者方面来看,情况看起来大不相同。我们解析提供的合约,在测试中,我们希望向您的端点发送一个真实的请求。因此,对于请求的生产者,我们不能进行任何形式的匹配。我们需要具体的值,以便生产者的后端可以工作。因此,以下 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.xmlserver文件夹: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.3.10.RELEASE</version>
        <relativePath/>
    </parent>

    <properties>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
        <java.version>1.8</java.version>
        <spring-cloud-contract.version>2.2.8.BUILD-SNAPSHOT</spring-cloud-contract.version>
        <spring-cloud-release.version>Hoxton.BUILD-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 文件是消费者端运行所必需的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

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>

通过此设置,groupid 为com.example.standalone和 artifactidcontracts下载自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. 以下示例显示了 MavenSpring 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 executed
*/
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 {
        // 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 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/"
    }
    // The mode can't be classpath
    contractsMode = "LOCAL"
    }
}

publish.dependsOn("publishStubsToScm")
publishToMavenLocal.dependsOn("publishStubsToScm")

使用这样的设置: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 规则、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 Contract2.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依赖于你的类路径。以下示例显示了这样的 Pact 契约:spring-doc.cadn.net.cn

{
  "provider": {
    "name": "Provider"
  },
  "consumer": {
    "name": "Consumer"
  },
  "interactions": [
    {
      "description": "",
      "request": {
        "method": "PUT",
        "path": "/pactfraudcheck",
        "headers": {
          "Content-Type": "application/json"
        },
        "body": {
          "clientId": "1234567890",
          "loanAmount": 99999
        },
        "generators": {
          "body": {
            "$.clientId": {
              "type": "Regex",
              "regex": "[0-9]{10}"
            }
          }
        },
        "matchingRules": {
          "header": {
            "Content-Type": {
              "matchers": [
                {
                  "match": "regex",
                  "regex": "application/json.*"
                }
              ],
              "combine": "AND"
            }
          },
          "body": {
            "$.clientId": {
              "matchers": [
                {
                  "match": "regex",
                  "regex": "[0-9]{10}"
                }
              ],
              "combine": "AND"
            }
          }
        }
      },
      "response": {
        "status": 200,
        "headers": {
          "Content-Type": "application/json"
        },
        "body": {
          "fraudCheckStatus": "FRAUD",
          "rejection.reason": "Amount too high"
        },
        "matchingRules": {
          "header": {
            "Content-Type": {
              "matchers": [
                {
                  "match": "regex",
                  "regex": "application/json.*"
                }
              ],
              "combine": "AND"
            }
          },
          "body": {
            "$.fraudCheckStatus": {
              "matchers": [
                {
                  "match": "regex",
                  "regex": "FRAUD"
                }
              ],
              "combine": "AND"
            }
          }
        }
      }
    }
  ],
  "metadata": {
    "pact-specification": {
      "version": "3.0.0"
    },
    "pact-jvm": {
      "version": "3.5.13"
    }
  }
}

7.4. 生产者契约

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

专家
Gradle
// if additional dependencies are needed e.g. for Pact
classpath "org.springframework.cloud:spring-cloud-contract-pact:${findProperty('verifierVersion') ?: verifierVersion}"

执行应用程序的构建时,将生成一个测试和存根。以下内容 示例显示了来自此过程的测试和存根: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

专家
Gradle

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. Flow:使用 Pact Broker 的消费者合约方法 |消费者方面

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

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

为了让生产者使用 Pact Broker 中的 Pact 文件,我们可以重用 与我们用于外部合同的机制相同。我们路由 Spring Cloud Contract 将 Pact 实现与包含 这pact://协议。您可以将 URL 传递给 契约经纪人。您可以在此处找到此类设置的示例。 以下列表显示了 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. Flow:在消费者端与 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,StubRunnerRule或 'StubRunnerExtension'转储每个项目 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方法,允许您将转换器作为可执行文件执行。它需要以下内容 参数: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,如以下列表所示: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. 如何将所有应用程序依赖项标记为可选?

如果,在producerapplication,则将所有依赖项标记为可选, 当您将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.Before;
import org.junit.Rule;
import org.junit.rules.TestName;
import org.junit.runner.RunWith;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.restdocs.JUnitRestDocumentation;
import org.springframework.test.context.junit4.SpringRunner;
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;

@RunWith(SpringRunner.class)
@SpringBootTest(classes = Application.class)
public abstract class FraudBaseWithWebAppSetup {

    private static final String OUTPUT = "target/generated-snippets";

    @Rule
    public JUnitRestDocumentation restDocumentation = new JUnitRestDocumentation(OUTPUT);

    @Rule
    public TestName testName = new TestName();

    @Autowired
    private WebApplicationContext context;

    @Before
    public void setup() {
        RestAssuredMockMvc.mockMvc(MockMvcBuilders.webAppContextSetup(this.context)
                .apply(documentationConfiguration(this.restDocumentation))
                .alwaysDo(document(
                        getClass().getSimpleName() + "_" + testName.getMethodName()))
                .build());
    }

    protected void assertThatRejectionReasonIsNull(Object rejectionReason) {
        assert rejectionReason == null;
    }

}
独立
package com.example.fraud;

import io.restassured.module.mockmvc.RestAssuredMockMvc;
import org.junit.Before;
import org.junit.Rule;
import org.junit.rules.TestName;

import org.springframework.restdocs.JUnitRestDocumentation;
import org.springframework.test.web.servlet.setup.MockMvcBuilders;

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

public abstract class FraudBaseWithStandaloneSetup {

    private static final String OUTPUT = "target/generated-snippets";

    @Rule
    public JUnitRestDocumentation restDocumentation = new JUnitRestDocumentation(OUTPUT);

    @Rule
    public TestName testName = new TestName();

    @Before
    public void setup() {
        RestAssuredMockMvc.standaloneSetup(MockMvcBuilders
                .standaloneSetup(new FraudDetectionController())
                .apply(documentationConfiguration(this.restDocumentation))
                .alwaysDo(document(
                        getClass().getSimpleName() + "_" + testName.getMethodName())));
    }

}
您无需为生成的代码片段指定输出目录(自 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) }Gradle 关闭。spring-doc.cadn.net.cn

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

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

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