契约测试概念以及契约测试框架SCC VS PACT对比

契约测试

基于契约,对消费者与生产者间的协作的验证, 本质上就是验证生产者所提供的内容是否满足消费者的期望。

契约测试在行业内,主要分为两种类型,消费者驱动的契约测试和生产者驱动的契约测试,最常见的就是消费者驱动的契约测试,简称 CDC(Consumer Driven Contract Test);根据消费者驱动契约 ,我们可以将服务分为消费者端和生产者端,而消费者驱动的契约测试的核心思想在于是从消费者业务实现的角度出发,由消费者自己会定义需要的数据格式以及交互细节,并驱动生成一份契约文件。然后生产者根据契约文件来实现自己的逻辑,并在持续集成环境中持续验证。

契约测试帮我们解决的问题:

  • 可以使得消费端和提供端之间测试解耦,不再需要客户端和服务端联调才能发现问题
  • 测试前移,越早的发现问题,保证后续测试的完整性
  • 通过契约测试,团队能以一种离线的方式(不需要消费者、提供者同时在线),通过契约作为中间的标准,验证提供者提供的内容是否满足消费者的期望。

TDD

TDD是测试驱动开发(Test-Driven Development)的英文简称,是敏捷开发中的一项核心实践和技术,也是一种设计方法论。TDD的原理是在开发功能代码之前,先编写单元测试用例代码,测试代码确定需要编写什么产品代码。TDD虽是敏捷方法的核心实践,但不只适用于XP(Extreme Programming),同样可以适用于其他开发方法和过程。

CDC

消费者驱动契约

cdc核心原则:

  • cdc是以消费者提出接口契约,交由服务提供方实现,并以测试用例对契约进行产生约束,所以服务提供方在满足测试用例的情况下可以自行更改接口或架构实现而不影响消费者。
  • cdc是一种针对外部服务的接口进行的测试,它能够验证服务是否满足消费方期待的契约。 它的本质是从利益相关者的目标和动机出发,最大限度地满足需求方的业务价值实现。

契约测试角色定义

  • 消费者

​ 使用来自另一个应用程序的功能或数据来完成其工作的流程.对于使用HTTP的应用程序,消费者始终是发起HTTP请求的应用程序(例如Web前端),而已数据流的方向无关,对于使用队列的应用程序,消费者是从队列中读取消息的应用程序

  • 提供者

​ 通常通过 API 提供功能或数据供其他应用程序使用的应用程序(通常称为服务) 。对于使用 HTTP 的应用程序,提供者是返回响应的应用程序。对于使用队列的应用程序,提供者(也称为生产者)是将消息写入队列的应用程序。

  • 契约

契约是一份协议,它规定了API 或消息通信应该是怎么样的,每一份契约是一份交互的集合,每个交互描述:

  • 对于 HTTP:
    • 一个预期的请求——描述消费者期望发送给提供者的内容
    • 最小预期响应 - 描述消费者希望提供者返回的响应部分
  • 对于消息:
    • 最小预期消息-描述消费者想要使用的消息部分

Pact interaction

Spring Cloud Contract

Spring Cloud Contract是一个包含解决方案的总括项目,可帮助用户实现不同类型的契约测试。它带有两个主要模块:Spring Cloud Contract Verifier主要由生产者方使用,以及Spring Cloud Contract Stub Runner由消费者方使用。

工作原理

Spring Cloud Contract 中各部分的关系:

在这里插入图片描述

  • Spring Cloud Contract的目的
  1. 确保 HTTP 和 Messaging 存根(在开发客户端时使用)与实际的服务器端实现完全相同;
  2. 推广ATDD(验收测试驱动开发)方法和微服务架构风格;
  3. 提供一种发布双方立即可见的契约更改的方法;
  4. 生成要在服务器端使用的样板测试代码.

使用 Spring Cloud Contract 和 契约测试 可以提供:

  • 存根可靠性:它们仅在测试通过后生成。
  • 存根可重用性:它们可以被多个消费者下载和重用

默认情况下,Spring Cloud Contract 与Wiremock集成为 HTTP 服务器存根。

注意: Spring Cloud Contract 的目的不是开始在契约中编写业务功能, 契约测试用于测试应用程序之间的契约,而不是模拟完整的行为。 假设我们有一个欺诈检查的业务用例,如果一个用户可能因为 100 个不同的原因而成为欺诈,你可以去将创建两份契约,一份契约的内容定义为欺诈,另一份契约定义为未欺诈,不需要定义100多种契约.

设计者Marcin Grzejszczak也在stack flow中提到:

在这里插入图片描述

契约DSL(领域特定语言)

Spring Cloud Contract 支持使用以下语言编写的 DSL:

  • Groovy
  • YAML
  • Java(基于Groovy)

要在 Java 中编写合约定义,您需要创建一个实现Supplier<Contract>接口(对于单个合约)或Supplier<Collection<Contract>>(对于多个合约)的类

  • Kotlin(基于Groovy)

支持动态配置属性,支持正则表达式,

以下是Groovy格式的sample:

/**
 * @author shil
 * @date 2022/5/25
 */
Contract.make {

    description("should return all customers")
    request {
        url("/customers")
        method GET()
    }
    response {
        status 200
        headers {
            header(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON_VALUE)
        }
        body([[id: 1L, name: "shiliang"], [id: 2L, name: "Tom"]])
    }


}

服务提供者(Producer)

  1. 添加 Spring Cloud Contract Verifier依赖和插件并配置stub存储仓库
testImplementation("org.springframework.cloud:spring-cloud-starter-contract-verifier")
  1. 编写测试基类(自动生成的测试类都会继承这个类,同时可以起到设置测试上下文的作用并将基类配置到gradle中
/**
 * @author shil
 * @date 2022/5/25
 */
@SpringBootTest(classes = ProviderServiceDemoApplication.class)
public class BaseTestClass {

    @Autowired
    private CustomerController webApplicationContext;

    @BeforeEach
    public void setup() {
        RestAssuredMockMvc.standaloneSetup(webApplicationContext);
    }

}
contracts {
	testFramework = "JUNIT5"
	packageWithBaseClasses = 'kl.v2x.providerservicedemo'
	baseClassMappings {
		baseClassMapping(".*.*", "kl.v2x.providerservicedemo.BaseTestClass")
	}
	contractsDslDir = new File(project.rootDir, "src/test/java/contracts")
}
  1. 编写定义契约文件,契约文件放置在$rootDir/src/test/resources/contracts,编写完成后运行构建,将会生成测试类、stub,生成的测试类可以验证服务本身是否符合契约,默认情况下,测试类在 org.springframework.cloud.contract.verifier.tests 下生成;

下面是生成的测试类sample(默认使用的是MockMvc测试框架):

public class ContractVerifierTest extends BaseTestClass {

	@Test
	public void validate_shouldReturnAllCustomers() throws Exception {
		// given:
			MockMvcRequestSpecification request = given();


		// when:
			ResponseOptions response = given().spec(request)
					.get("/customers");

		// then:
			assertThat(response.statusCode()).isEqualTo(200);
			assertThat(response.header("Content-Type")).isEqualTo("application/json");

		// and:
			DocumentContext parsedJson = JsonPath.parse(response.getBody().asString());
			assertThatJson(parsedJson).array().contains("['id']").isEqualTo(1L);
			assertThatJson(parsedJson).array().contains("['name']").isEqualTo("shiliang");
			assertThatJson(parsedJson).array().contains("['id']").isEqualTo(2L);
			assertThatJson(parsedJson).array().contains("['name']").isEqualTo("Tom");
	}

}
  1. 上传stub到仓库供服务消费者获取并使用

服务消费者(Consumer)

消费者端主要是通过使用Spring Cloud Contract Stub Runner去获取一个运行的WireMock实例,这个实例是模拟被消费的服务

使用 Stub Runner 获取生产者的存根,Stub Runner 在内存 中启动一个HTTP 服务器(默认情况下,都是WireMock 服务器),消费者针对存根进行测试

  1. 添加Spring Cloud Contract Stub Runner依赖
	testImplementation 'org.springframework.cloud:spring-cloud-starter-contract-stub-runner'
  1. 测试类通过添加@AutoConfigureStubRunner注解,并提供group-idartifact-id,将其配置到注解属性中,配置的属性主要是从什么地方fetch stub.

    @RunWith(SpringRunner.class)
    @SpringBootTest(webEnvironment=WebEnvironment.NONE)
    @AutoConfigureStubRunner(ids = {"com.example:http-server-dsl:+:stubs:6565"},
            stubsMode = StubRunnerProperties.StubsMode.LOCAL)
    public class LoanApplicationServiceTests {
        
         . . .// test
    }
    
  2. 开始测试,测试过程会去拉取stub,并在本地启动,充当服务提供者供消费者消费,就可以发起请求,获取相应,判断相应是否与预期的契约一致

测试方法

  • 生产者契约测试(Producer Contract testing)

生产者契约测试方法中,,生产者去定义契约然后编写契约测试,描述API并发布存根,无需与其客户端合作.通常这种情况发生在当API是公开的并且API的所有者甚至不知道到底是谁在使用它.

  • 消费者驱动契约测试(Consumer-Driven Contract testing)

消费者驱动的契约测试方法中,契约由消费者建议,与生产者密切合作。生产者确切地知道哪个消费者定义了哪个契约以及当契约兼容性被破坏时哪个契约被破坏。这种方法在使用内部 API 时更为常见。

在这两种类型情况下,契约都可以在生产者的存储库中定义(使用 DSL 或通过编写契约测试来定义)或存储所有契约的外部存储库。

PACT

契约测试的精髓就是消费者驱动,而PACT的设计理念就是遵循和实现消费者驱动测试.Pact是一款编码优先的工具,使用契约测试来测试HTTP和消息集成,契约测试断言应用程序之间是否符合共同的约定(记录在契约中),如果没有契约测试,唯一的方法去确保应用程序间能够正确交互的方法是通过昂贵的且脆弱的集成测试

Pact 术语

Mock Service Provider
  • 对于 HTTP

    消费者项目中的 Pact 测试使用 Pact mock provider来模拟实际的服务提供者,这意味着可以运行类似集成的测试,而无需实际的服务提供者可用;

  • 对于消息

    消费者和提供者通常使用诸如队列、主题或事件总线之类的中介来交换消息,但是,Pact message是故意与这些技术无关,它只关注有效负载,因此没有 Pact 模拟队列/主题/总线之类的东西。

交互

请求和响应相对。每个交互都有一个描述和一个或多个提供者状态。HTTP 协议由一系列交互组成。

契约文件

包含在消费者测试中定义的 JSON 序列化交互(请求和响应)或消息的文件。这是契约。一个契约定义:

  • 消费者名称
  • 提供者名称
  • 交互或消息的集合
  • Pact规范版本
Pact 规范

Pact 规范是一个管理实际生成的 Pact 文件的结构的文档,以允许语言之间的互操作性,使用语义版本控制来指示重大更改。

Pact Broker

Pact Broker 是用于共享 Pact 合约和验证结果的应用程序,它也是一个永久运行的外部托管服务,具有 API 和 UI,允许将契约测试集成到 CI/CD 管道中。与 Pact 客户端一样,Pact Broker 是一个开源项目。

Pact 验证
  • 对于 HTTP

    为了验证Pact 契约,包含在一个pact 文件中的请求将针对提供程序代码进行重放,并检查返回的响应以确保它们与 pact 文件中的预期相匹配.

  • 对于消息

    执行提供程序上的一段代码以生成给定描述的消息,并检查生成的消息以确保它与 pact 文件中的预期匹配。

提供者状态

在消费者方面,提供者状态是一个名称,描述了提供者在针对它重放给定请求时应该处于的“状态” ——例如“当用户 John Doe 存在时”或“当用户 John Doe 拥有银行账户”。这些允许在不同的场景下测试相同的端点。

在提供者端,当执行协议验证时,提供者状态名称将用于标识在请求执行之前应该运行的设置代码块。提供者状态设置代码由提供者团队编写。

工作原理

  • 消费者测试

每一个交互都通过Pact框架进行测试,由消费者代码库中的单元测试框架进行驱动:

如下图:

Pact interaction

  1. 使用Pcat DSL语言,将预期的请求和响应注册到模拟服务;
  2. 消费者测试代码向模拟提供者(由Pact框架创建)发出真实请求;
  3. 模拟提供者将实际请求与预期请求进行对比,如果比较成功,则返回预期的响应;
  4. 消费者测试代码确认响应被正确理解

在消费者测试期间,对 Pact 模拟提供者的每个请求都记录到契约文件中,以及它的预期响应。

只有在每个步骤都完成且没有错误的情况下,Pact测试才会成功,通常,交互定义和消费者测试是一起编写的

在Pact中每个交互被认为是独立的,这意味着每个测试只测试一个交互.如果你需要描述依赖于预先存在状态的交互,可以使用provider states去完成. Provider states允许你描述生成预期响应所需的提供者的先决条件.

协议与提供者状态的交互

举例:与其编写一个测试用例含义为“创建一个用户123,然后登陆”,不如编写两个单独的交互,一个是“创建用户 123”,另一个是定义提供者状态为“用户 123 存在”,即“以用户身份登录” 123”。

  • 提供者验证

与消费者测试相比,提供者验证是完全通过Pact框架来驱动进行的

Provider verification

在提供者验证中,每个请求都发送给提供者,并将其生成的实际响应与消费者测试中描述的最小预期响应进行比较。

如果每个请求生成的响应至少包含最小预期响应中描述的数据,则提供者验证通过。

在很多用例中,提供者需要处于特定的状态(例如“用户123已经登陆”或“客户456拥有一个发票”等待),Pact框架支持你在请求交互之前设置由Provider State描述的数据来支持这一点:

Provider verification with state

  • 放在一起

Pact test and verify

如果我们将每次交互的消费者测试和提供者验证过程配对,则消费者和提供者之间的契约将得到全面测试,而无需一起启动服务。

  • 总的流程

在这里插入图片描述

​ 基于契约进行合作

完成契约测试后,您需要一个流程来管理契约测试流程。这就是Pact Broker的用武之地。Pact Broker 使您能够:

​ ✅跨团队共享和协作契约

​ ✅跨代码分支和环境去管理契约

​ ✅编排构建以了解何时可以安全部署

​ ✅集成到您的流程和工具中

SCC vs PACT

相同点

  • 基于一份契约测试

  • 测试范围相同,它们都只专注于确保请求创建和响应处理是正确的,而不是用于测试特定的、完整的业务逻辑

  • 消费者的契约测试都是通过启动一个HTTP mock server进行测试

不同点

  • 契约存储位置

SCC: Artifactory/Nexus、classpath、Git 代码库或 Pact broker

PACT: classpath、Pact broker(推荐, 需要额外搭建和维护Pact Broker)

  • 契约文件格式

SCC: Groogy、Yaml

Pact: JSON

  • 契约的掌控权

SCC: 消费者生产者一起定义一份契约,消费者提供建议,契约在生产方编写

Pact: 消费者去定义契约,主导方在消费者,生产者负责实现

  • 生产者的契约测试

SCC: 自己需要先编写一个测试基类,SCC帮你生成测试类,测试类去校验服务自身的响应是否与预期响应一致

PACT: 通过一个Mock Provider Server(HTTP服务)模拟消费者,往生产者自身发请求,校验响应是否与预期响应一致

  • 工作方式

Pact: 在consumer端生成契约文件,发布到Pact Broker,而后,provider从Pact Broker获取契约文件,触发provider端执行契约测试。

SCC: 实际生成契约文件的工作是发生在provider端的,基于这份契约文件,在provider端,生成了Java的测试案例,这些测试案例用于provider的功能测试;而在Consumer端,使用同一份契约文件作为Stub,生成了基于WireMock的mock service,consumer可以使用该mock service来做集成测试。

  • 多语言支持

SCC: 支持JVM和非JVM语言,但是非JVM语言是通过Docker镜像实现,并没有原生支持

Spring Cloud Contract支持多语言(非JVM语言)是通过Docker镜像来帮助完成的,为了隐藏实现细节(例如生成 java 测试、插件设置或 Java 安装),SCC引入一个抽象层。通过使用Docker镜像来隐藏它们。SCC将所有项目设置、所需的包和文件夹结构封装在一个 docker 映像中,这样除了所需的环境变量之外,用户不需要任何知识。

PACT: 原生支持多种语言:java、C++、JS、Go、Python、Swift、PHP等(基于PACT规范实现)

  • 是否需要手动编写契约

SCC: 需要手动编写契约,也可以通过集成Spring Rest Doc框架,通过springmvc的单元测试可直接生成契约

PATC: 基于消费者代码编写生成契约

  • 驱动理念

Pact作为消费者驱动契约测试的倡导者,真正地实践了消费者驱动的契约测试。相对的,SCC,既没有实际的将契约作为被测对象来进行测试,更没有确实地实现”消费者驱动”。SCC的做法,实际上是基于同一份契约,分别驱动了consumer端的集成测试和provider端的功能测试。所以,Pact和SCC的区别,就在于,前者做的是”契约测试”,后者做的是”基于契约的测试(契约驱动的测试)”。

  • Github Stars人气活跃度对比

Spring Cloud Contract: Contributors: 138、starts: 641、forks: 395

PACT-JVM: Contributors: 138、starts: 924、 forks: 438

来自SCC 社区:

Marcin Grzejszczak解答:

在这里插入图片描述
在这里插入图片描述

两个框架之间的主要区别在于测试开发过程的起点。SCC 先在生产者端定义契约,然后将其传递给消费者进行验证。相比之下,PACT 是消费者驱动的,这意味着消费者向提供者提供他们对合同的期望,以验证它是否符合预期的合同定义。

控制权更多的是在提供者, 提供者契约是一种方法,生产者声明如何使用 API,而不关心哪个消费者以何种方式使用它

来自Pact官网

相同点: 本质上都是在解决同一个问题

不同点:

  1. Pact生成语言中立的验收契约,它是以JSON协议文件的形式存在的,这些Pact文件可以由任何实现Pact规范的东西创建和测试,无论代码是 Ruby、Javascript、JVM 还是任何其他语言;尽管SCC可以已经兼容非JVM语言,但它没有对它们的原生支持,并且需要使用YAML手动编写契约并使用外部存根运行程序来运行测试;
  2. 另一个关键区别是,在 Pact 中,消费者代码实际上生成了契约,在 Spring Cloud Contract 中,契约是在代码库之外手动编写的。这会产生漂移的可能性;
  3. Pact一直是一个消费者驱动的契约测试框架,而SCC是从提供者驱动开始的,尽管可以使用 SCC 进行消费者驱动的契约测试,但是来自一个提供者的所有消费者的所有契约都需要存储在一个集中共享的 git 存储库中,而在 Pact 中,每个消费者都从自己的存储库生成其契约;
  4. Pact 具有一系列的提供者状态(Provider state)相关的功能,这是 Pact 应对在专用测试环境中管理测试数据和排序测试的传统挑战的解决方案。

总的来说:

  1. 如果你依赖于 JVM,尤其是 Spring,那么 Spring Cloud Contract 可能更容易集成到您的测试中;
  2. 如果你的工作流本质上更多是由提供者驱动的,那么SCC更适合,如果是消费者驱动的,则PACT更适合;
  3. 如果你想增加语言选择的灵活性,并且不依赖于特定的实现,Pact 可能更适合;
  4. 如果你想使用Pact Broker,Pact更是自然的选择
来自社区使用者的经验之谈
  • 方法——SCC的契约测试过程从提供方开始,而PACT则相反,它从消费者方开始
  • 编程语言——SCC 仅支持 Java,因为它基于 Spring 框架。另一方面,PACT 支持广泛的编程语言,如 Java、Javascript、C++、Python、Ruby、Rust 等
  • 契约定义——SCC 允许使用 groovy、yaml 和编程 DSL 来定义契约,但是,PACT 仅支持编程 DSL。
  • 契约存储——SCC 使用artifactr存储库进行合同存储,而 PACT 拥有自己的 PACT 代理,具有附加功能。
  • 消费者测试——SCC 和 PACT 都会自动启动模拟服务器以进行消费者测试。可以从远程存储库或本地机器检索契约定义。
  • 提供者测试 ——SCC 不太灵活,因为它不支持每个接口交互的模拟设置
我的观点
  • 多语言支持和扩展,PACT
  • 契约的管理和监控平台,推荐使用Pact Broker

特征:

用于发布和检索协议的 RESTful API。
用于导航 API 的嵌入式 API 浏览器。
每个协议的自动生成的文档。
动态生成的网络图,因此您可以可视化您的微服务网络。
显示提供者验证结果,以便您了解是否可以安全部署。
提供兼容的消费者和提供者版本的“矩阵”,以便您知道哪些版本可以安全地部署在一起。
提供徽章以在您的自述文件中显示协议验证状态。
允许标记应用程序版本(即“prod”、“feat/customer-preferences” )以允许类似存储库的工作流。
提供 webhook 以在协议更改时触发操作,例如。运行提供程序构建,通知 Slack 频道。
查看 Pact 版本之间的差异,以便您了解预期发生了哪些变化。
Docker 契约代理
用于将 Pact 工作流整合到您的持续集成过程中的CLI 。
  • 契约编写更灵活、优雅、更友好(接口的mock)、更容易理解

结论: 推荐PACT

学习参考

  1. Spring Cloud Contract 官方文档

  2. PACT官方文档

  3. 契约测试之核心解惑

  4. Contract Test — Spring Cloud Contract vs PACT

  5. Spring Cloud Contract 社区

  • 0
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值