微服务架构是最流行的开发方法之一。 微服务架构允许将大型应用程序拆分为更小的应用程序,其中每个应用程序都经过设计、开发、测试和部署,我们通常将它们称为服务。 服务是为业务能力而构建的,每个服务执行一个功能。 因为它们是独立运行的,所以每个服务都可以更新、部署和扩展以满足应用程序特定功能的需求。 为了满足单个用户请求,基于微服务的应用程序可以调用许多内部微服务来组成其响应。 这种开发方式的好处之一是可以更快地投放市场。
大多数时候,一个微服务可以调用另一个微服务来满足用户请求。 发送请求的微服务通常称为“消费者”,而响应请求的微服务通常称为“服务提供者”。 生产者和消费者都有一个共同的契约来相互通信。 最大的挑战之一是将契约变更从服务提供者“传达”给消费者。 如果服务提供者在未与消费者沟通和部署的情况下更改契约,则可能会中断消费者服务。 这是一个非常非常严重的问题,如果受影响的服务是关键服务之一,它可能会导致停机并经常导致大量收入损失。
想象一个场景,用户想通过信用卡、银行转账、其他支付方式(Google pay 或 Paypal 或其他)等各种支付方式在电子商务网站上付款。 假设,为了通过信用卡支付,用户必须在继续支付之前保存信用卡详细信息。 假设此“保存信用卡服务”将调用第三方服务来验证用户信用卡上的欺诈活动。
1.前端服务将请求发送到保存信用卡服务。
2.在将信用卡详细信息保存到数据库之前,保存信用卡服务向外部服务欺诈分数服务提供者服务发送请求。
现在 Save Credit Card Service 和 Fraud Score Provider Service 之间存在依赖关系,想象一下,如果 Fraud Score Provider Service 更改其契约并部署这些更改而不与 Save Credit Card Service 团队沟通怎么办??? 因此,在服务提供者将其服务部署到集成环境之前,我们需要找到一种方法来验证服务提供者更改不会在消费者服务中引入任何重大更改,我们需要在隔离环境 (CI) 或开发人员本地机器中验证这一点 . 这显然是一种左移测试方法,测试将在开发生命周期中尽早进行,这将帮助我们更快地获得反馈。
为了解决这个问题,可用的测试过程很少。 其中之一是契约测试。
契约测试:契约测试是一种确保两个独立系统(例如两个微服务)兼容并且可以相互通信的方法。 它捕获每个服务之间交换的交互,将它们存储在契约中,然后可用于验证双方是否遵守契约。 有两种类型的契约测试方法可用。
1.消费者驱动的契约测试
2.双向契约测试
消费者驱动的契约测试:顾名思义,消费者驱动契约测试。 消费者根据它的期望(来自服务提供者)生成契约,这些期望将针对服务提供者进行验证。
双向契约测试:消费者和服务提供者都将他们的契约发布到一个工具,该工具根据服务提供者契约验证消费者契约并告诉我们它们是否兼容。
如何生成消费者合约? : 作为消费者,可以简单的把消费者契约写在一个文件里,然后发布。 但是,你认为这样做是正确的吗? 如果在编写它们时犯了任何错误怎么办?因此,可以使用一些工具或库来帮助我们生成消费者合约,而不是手动编写消费者合约。 市场上有一些这样的工具,如 Pact DSL 和 Wiremock。
将使用 Pact DSL 或 Wiremock 编写测试,在其中验证服务提供者的行为,一旦执行所有这些测试,这些工具将生成消费者契约文件。 因此,在这里模拟预期服务提供者的行为,对其进行测试并根据这些测试生成契约。
在本文中,将重点关注 Pact DSL 来生成消费者合约,并且将遵循 BDCT 方法来验证 Provider 合约和 Consumer 合约之间的兼容性。 使用 SmartBear 的 Pactflow 来执行 BDCT。
双向契约测试应遵循的步骤:
服务提供者的步骤:
1.将手动编写提供者合约(Open Api Specification),或者可以通过 swagger-generator 等代码生成。
2.准备好供应商契约后,需要确保该契约在发布到 Pactflow 之前按预期工作。 为了验证这一点,使用服务提供者的集成测试,并且有一些工具可用于验证服务提供者的契约。 其中一些工具与 Atlassian 的 OpenApiValidator、Postman、Dredd 等一起使用 RestAssured。
3.Provider 的契约在本地被验证后,将把它连同验证结果一起发布到 Pactflow。
4.运行“can_i_deploy”命令来检查是否可以部署提供者服务。
5.部署了服务提供者服务后,在 Pactflow 中记录部署。
消费步骤:
1.将使用 Pact DSL 模拟提供者的行为,并将编写测试来验证此行为。 一旦执行这些测试,将在执行结束时生成一个 Pact 文件(Consumer Contract)。
2.向 Pactflow 发布消费者合约
3.运行“can_i_deploy”命令来检查是否可以部署消费者服务。
4.部署了消费者服务后,在 Pactflow 中记录部署。
开始写代码:
先决条件:
1.在 https://pactflow.io/ 中创建一个帐户并从 Pactflow 设置页面复制读/写令牌。
2.将这些令牌设置为机器中的环境变量
export PACT_BROKER_BASE_URL="https://<your>.pactflow.io"
export PACT_BROKER_TOKEN="Token"
注意:以上信息有一些敏感数据,一般做法是将此类敏感信息存储在安全的地方,如 Vault,并在 CI 中检索它们,或者也可以将它们作为受保护的变量保存在 CI 配置中。
3.复制 https://github.com/AnjiB/SimpleApiTest ,并构建它
4.复制提供者服务:https://github.com/AnjiB/ccfc 并构建它。
5.复制消费者服务:https://github.com/AnjiB/save-cc 并构建它。
6.在本地启动 Docker。
7.cd到ccfc,执行provider验证测试。
8.让我们测试一下如果对 Provider 合约进行更改 OpenApiValidator 是否会抛出错误。 为了测试这种情况,打开 ccfc_provider_contract.json 并将 CCFraudInfo 架构字段“错误”类型从字符串更新为整数。
"components": {
"schemas": {
"CCFraudInfo": {
"type": "object",
"properties": {
"timeStamp": {
"type": "string"
},
"status": {
"type": "integer",
"format": "int32"
},
"error": {
"type": "integer"
},
"path": {
"type": "string"
},
"ccNumber": {
"type": "string"
},
"issueDate": {
"type": "string",
"format": "date"
},
"expDate": {
"type": "string",
"format": "date"
},
"fraudProps": {
"$ref": "#/components/schemas/FraudProps"
}
}
},
9.保存更改并再次执行服务提供者验证测试并观察失败情况。 这些失败告诉我们,在服务提供者契约中,将“错误”字段类型定义为 Integer,但应用程序以 String 类型响应。 这就是确保应用程序按照定义的规范运行的方式。
10.现在恢复 spec 文件中的更改,再次运行测试,可以将服务提供者契约发布到 Pactflow。
11.执行下面的 shell 脚本来发布服务提供者合约。 或者,可以运行脚本文件夹中提供的这个 shell 脚本。
docker run --rm -v /${PWD}:/${PWD} -w ${PWD} \
-e PACT_BROKER_BASE_URL \
-e PACT_BROKER_TOKEN \
pactfoundation/pact-cli:latest \
pactflow publish-provider-contract docs/oas_contract/ccfc_provider_contract.json \
--provider fraud-service-provider \
--provider-app-version 1.0.0\
--branch main \
--content-type application/json \
--verification-exit-code=0 \
--verification-results target/surefire-reports/TEST-com.anji.finance.ccfc.CcfcProviderVerificationTest.xml \
--verification-results-content-type text/xml \
--verifier restassured
12.登录 Pactflow 帐户,观察是否成功发布了服务提供者契约。
13.运行 can-i-deploy shell 脚本,它说是,因为此时没有消费者契约。
14.假设将此服务部署到生产环境,现在需要在 Pactflow 中记录此部署。
15.现在是发布消费者合约的时候了。 Cd 到“save-cc”文件夹并运行命令“mvn test”,它将在“target/pacts/”文件夹中创建协议文件。
{
"provider": {
"name": "fraud-service-provider"
},
"consumer": {
"name": "save-credit-card-consumer"
},
"interactions": [
{
"description": "Invalid Auth Error message from ccfc service",
"request": {
"method": "GET",
"path": "/v1/fraudcheck/score",
"headers": {
"Authorization": "Basic afaaf="
},
"query": {
"cc": [
"4532788397355156"
],
"exp": [
"2023-03-31"
]
}
},
"response": {
"status": 401,
"body": {
"error": "Bad credentials"
}
},
"providerStates": [
{
"name": "Invalid Auth Token is Provided"
}
]
},
{
"description": "Error message from ccfc service",
"request": {
"method": "GET",
"path": "/v1/fraudcheck/score",
"headers": {
"Authorization": "Basic afafaf="
},
"query": {
"cc": [
"test"
],
"exp": [
"2023-03-31"
]
}
},
"response": {
"status": 500,
"body": {
"error": "There is no credit with given card number. Please enter valid credit card"
}
},
"providerStates": [
{
"name": "Invalid credit card is provided"
}
]
},
{
"description": "Invalid CC Error message from ccfc service",
"request": {
"method": "GET",
"path": "/v1/fraudcheck/score",
"headers": {
"Authorization": "Basic afaffa="
},
"query": {
"cc": [
"test"
],
"exp": [
"2023-03-31"
]
}
},
"response": {
"status": 500,
"body": {
"error": "There is no credit with given card number. Please enter valid credit card"
}
},
"providerStates": [
{
"name": "Invalid credit card is provided"
}
]
},
{
"description": "Fraud score for a valid credit card",
"request": {
"method": "GET",
"path": "/v1/fraudcheck/score",
"headers": {
"Authorization": "Basic afaff="
},
"query": {
"cc": [
"4532788397355156"
],
"exp": [
"2023-03-31"
]
}
},
"response": {
"status": 200,
"body": {
"ccNumber": "4532788397355156",
"issueDate": "2019-03-31",
"expDate": "2023-03-31",
"fraudProps": {
"issuer": "VISA",
"fraudScore": 700
}
}
},
"providerStates": [
{
"name": "Valid credit card is provided"
}
]
}
],
"metadata": {
"pactSpecification": {
"version": "3.0.0"
},
"pact-jvm": {
"version": "4.1.7"
}
}
}
16.运行 shell 脚本 — “publish_consumer_contract”发布消费者合约。
17.登录Pactflow账号查看状态
18.运行 can-i-deploy 脚本来检查是否可以部署消费者应用程序
19.部署消费者应用程序并在 Pactflow 中记录此部署。
现在有版本为 1.0.0 的消费者,它与版本为 1.0.0 的提供者兼容
20.让我们从服务提供者那里引入一个突破性的变化,看看是否可以部署它。 为了进行此测试,将规范文件中的字段“错误”类型从“字符串”更新为“整数”并将其发布为版本 2.0.0
注意:服务提供者验证测试将因上述更改而失败,需要通过更改应用程序行为来修复这些错误。
docker run --rm -v /${PWD}:/${PWD} -w ${PWD} \
-e PACT_BROKER_BASE_URL \
-e PACT_BROKER_TOKEN \
pactfoundation/pact-cli:latest \
pactflow publish-provider-contract docs/oas_contract/ccfc_provider_contract.json \
--provider fraud-service-provider \
--provider-app-version 2.0.0\
--branch main \
--content-type application/json \
--verification-exit-code=0 \
--verification-results target/surefire-reports/TEST-com.anji.finance.ccfc.CcfcProviderVerificationTest.xml \
--verification-results-content-type text/xml \
--verifier restassured
21.登录Pactflow并检查兼容性
22.由于正在引入重大变更,可以询问 Pactflow 是否可以将此版本部署到生产环境中?
让我们看看 Pactflow 怎么说..
docker run --rm -v /${PWD}:/${PWD} -w ${PWD} \
-e PACT_BROKER_BASE_URL \
-e PACT_BROKER_TOKEN \
pactfoundation/pact-cli:latest \
pact-broker can-i-deploy \
--pacticipant fraud-service-provider \
--version 2.0.0 \
--to-environment production \
--retry-while-unknown 6 \
--retry-interval 10
结论:
双向契约测试是实施契约测试的最简单方法之一(从提供者的角度来看),可以利用现有服务提供者的集成测试来验证其开放 API 规范文档,然后再共享给所有消费者。