在我们之前的博客文章中,我们已经介绍了微服务消费者驱动契约(CDC)测试的主题及其背后的动机。我们还通过提供基于Spring Boot的示例来看看 PACT 作为CDC测试框架。在当前的博客文章中,我们将重点关注CDC测试的Spring Cloud Contract框架。Spring Cloud合约项目本身包含三个主要组件 - Spring Cloud Contract Verifier,Spring Cloud Contract WireMock和Spring Cloud Contract RestDocs。由于此博客文章无法涵盖Spring Cloud Contract提供的所有可能的技术和选项组合,因此我们主要关注 Spring Cloud Contract Verifier。但是,为了好奇,我们添加了两个测试样本 - 一个用于Spring WireMock,另一个用于演示项目中的Spring RestDocs,以获得一个想法。更多细节,你可以在官方文档中找到。
让我们从我们的Github项目开始: Spring Clould合同演示Github Repo
首先介绍一下我们的案例。假设我们有两个服务(参见图1) - Consumer和Provider通过REST彼此进行通信。我们案例中的提供商或产品服务通过给定的产品识别号提供所有产品相关信息,例如 名称, 类型 和 说明。另一方面,消费者获取数据并消费相关信息。
谈到合同测试时,我们首先定义包含我们正在使用的API的期望的第一个合同。作为一个经验法则,合同必须由消费者服务来定义,但是在Spring Cloud Contract中,它实际上位于提供者服务代码中。正式文档中建议的一种方法是克隆Provider服务项目,并在本地写入合同。该指南由两个包含较小步骤的主要步骤组成。第一步着重于提供方应该做什么,第二步是将消费者方面的活动分组。
步骤1操作概述
1.1。编写合同规范(Groovy DSL)
合同用Groovy DSL编写,由两个主要部分组成 - 定义请求和预期响应。根据规范,至少有必须定义的元素。对于Request部分,它们是:method和urlPath,对于响应部分:状态代码。有关定义方式的详细信息以及Groovy DSL语法细节可以在这里找到 - https://cloud.spring.io/spring-cloud-contract/spring-cloud-contract.html#_contract_dsl。在以下代码块中,您可以看到我们的产品演示案例定义的合约。指定合同的默认位置在/ test / resources / contracts中。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
|
import
org
.springframework
.cloud
.contract
.spec
.Contract
Contract
.make
{
//ignored
(
)
request
{
method
'GET'
urlPath
('/product'
)
{
queryParameters
{
parameter
('id'
,
537
)
}
}
}
response
{
status
200
body
(
'''
{
"description"
:
"Consumer
Test
verifies
provider"
,
"name"
:
"Consumer
Test"
,
"type"
:
"testing
product"
}
'''
)
headers
{
header
('Content-Type'
,
'application/json
;charset=UTF-8'
)
}
}
}
|
1.2。在提供者端生成自动接受测试
当您在提供者端时,可以使用spring-cloud-contract-verifier的可能性自动生成基于此合同的测试,验证提供者的实现。在此之前,您需要实现所有生成的测试将从中延伸的基类。在我们的例子,这是ContractVerifierBaseTest的.java,其在中配置的build.gradle文件(完整 的build.gradle示例,可以在GitHub的库)。
1
2
3
4
|
.
.
.
contracts
{
baseClassForTests
=
'product
_demo
.ContractVerifierBaseTest'
}
|
当您从提供者项目执行“ gradle generateContractTests ”时,生成的测试必须位于/ build / generated-test-sources下。以下代码块显示了测试的样子。其名称由前缀validate_ +合约文件的名称(= validate_productContractDemo)组成。鉴于ProductController请求映射已经实现并且您执行测试,它预计会通过。但是,更可能的情况是,如果您尚未实现RestController的逻辑,则测试将失败。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
|
public
class
ContractVerifierTest
extends
ContractVerifierBaseTest
{
@Test
public
void
validate_productContractDemo
(
)
throws
Exception
{
// given:
MockMvcRequestSpecification
request
=
given
(
)
;
// when:
ResponseOptions
response
=
given
(
)
.
spec
(
request
)
.
queryParam
(
"id"
,
"537"
)
.
get
(
"/product"
)
;
// then:
assertThat
(
response
.
statusCode
(
)
)
.
isEqualTo
(
200
)
;
assertThat
(
response
.
header
(
"Content-Type"
)
)
.
isEqualTo
(
"application/json;charset=UTF-8"
)
;
// and:
DocumentContext
parsedJson
=
JsonPath
.
parse
(
response
.
getBody
(
)
.
asString
(
)
)
;
assertThatJson
(
parsedJson
)
.
field
(
"type"
)
.
isEqualTo
(
"testing product"
)
;
assertThatJson
(
parsedJson
)
.
field
(
"description"
)
.
isEqualTo
(
"Consumer Test verifies provider"
)
;
assertThatJson
(
parsedJson
)
.
field
(
"name"
)
.
isEqualTo
(
"Consumer Test"
)
;
}
}
|
一旦测试通过并且提供者服务端必要的逻辑被实现,我们就可以继续下一步 - 从提供者生成和安装存根。但是在我们在技术层面这样做之前,让我们看看为什么这些存根是必要的。对于消费者,我们不希望针对真正部署的Provider服务执行我们的测试。另一方面,我们不希望使用过时的模拟提供商服务,正如我们之前的博客文章中已经讨论的那样在综合测试下。因此,Spring Cloud Contract为我们提供了在我们的消费者测试中使用存根提供者的功能,这同时与真实的提供者保持同步。这就是我们到目前为止的实际做法 - 我们已经创建了一个合同,生成了一个针对实际Provider实现执行的自动化测试。一旦它变成绿色,我们就可以生成Provider Stub并将其传递给Consumer。
我们通过执行gradle clean build install来做到这一点。 这个命令为我们做了两个步骤。
- 首先,它生成WireMock(WireMock是一个用于服务虚拟化的框架)JSON存根定义(在/ build / stubs / mappings下)。
- 其次,它将这些存根发布到Maven本地存储库中。jar文件包含合同和存根。
第2步操作概述
1.1。在消费者端配置Stub Runner在
Consumer端,我们必须编写我们的测试并进行配置,以对提供者存根执行它。理想情况下,我们将在与供应商签订合同之前编写测试。该ConsumerDemoTest.java做无非执行用于获取基于产品编号产品数据的请求更多。最后,它声称收到的数据如预期的那样。更有趣的是这部分配置:
1
2
3
|
.
.
.
@AutoConfigureStubRunner
(
ids
=
"info.novatec.spring.contract.cloud.example:
provider-service:+:stubs:8082"
,
workOffline
=
true
)
|
使用这条线我们做了spring-cloud-starter-contract-stub-runner的配置。WorkOffline等于true,意味着存根运行程序将在 Maven本地存储库中搜索提供程序存根。使用ids的值,我们说使用maven repo中最新安装的存根版本与groupId = info.novatec.spring.contract.cloud.example,artifcatId = provider-service 并指定测试端口。Stub runner附带嵌入式WireMock,因此在开始测试时,Stub runner将在内部启动WireMock服务器并在其上安装Provider存根。
1.2。执行消费者测试和检查验证结果
之后,它将执行测试逻辑并最终执行断言。如果一切正常,测试预计会通过。您可以在下面看到来自测试执行日志的简短信息,证明存根安装和运行过程。如果发现错误,那么从事消费者服务的团队必须与提供商的服务团队进行沟通。
1
2
3
4
5
6
7
8
9
10
|
.
.
.
.
.
.
o
.s
.c
.c
.stubrunner
.AetherStubDownloader
:
Remote
repos
not
passed
but
the
switch
to
work
offline
was
set
.
Stubs
will
be
used
from
your
local
Maven
repository
.
.
.
.
o
.s
.c
.c
.stubrunner
.AetherStubDownloader
:
Desired
version
is
[+
]
-
will
try
to
resolve
the
latest
version
.
.
.
o
.s
.c
.c
.stubrunner
.AetherStubDownloader
:
Resolved
version
is
[1
.0-SNAPSHOT
]
.
.
.
o
.s
.c
.c
.stubrunner
.AetherStubDownloader
:
Resolving
artifact
[info
.novatec
.spring
.contract
.cloud
.example
:provider-service
:jar
:stubs
:1
.0-SNAPSHOT
]
using
remote
repositories
[
]
.
.
.
o
.s
.c
.c
.stubrunner
.AetherStubDownloader
:
Resolved
artifact
[info
.novatec
.spring
.contract
.cloud
.example
:provider-service
:jar
:stubs
:1
.0-SNAPSHOT
]
to
\.m2
\.
.
\provider-service-1
.0-SNAPSHOT-stubs
.jar
.
.
.
.
.
.
o
.s
.c
.contract
.stubrunner
.StubServer
:
Started
stub
server
for
project
[info
.novatec
.spring
.contract
.cloud
.example
:provider-service
:1
.0-SNAPSHOT
:stubs
]
on
port
8082
.
.
.
o
.s
.c
.c
.stubrunner
.StubRunnerExecutor
:
All
stubs
are
now
running
RunningStubs
[namesAndPorts=
{info
.novatec
.spring
.contract
.cloud
.example
:provider-service
:1
.0-SNAPSHOT
:stubs=8082
}
]
.
.
.
|
概要
在这篇博文中,我们介绍了支持CDC - Spring Cloud Contract背后概念的第二个框架。它仅适用于基于JVM的应用程序。另一方面,PACT提供了更广泛的编程语言支持,这可能是我们使用微服务时的一个因素,至少其中一个不是基于Java的。Spring Cloud Contract更具平台特定性,但提供了更多功能,例如存根运行程序和供应商方面的自动测试生成。是否应该成为Java的首选,是项目背景,需求和个人选择的问题。
CDC的想法背后有一个有趣的结果,它实际上在架构层面上推动了测试驱动开发的概念。您首先在消费者方面实施一项测试,该测试最初因为没有提供商而失败。然后,您写入/生成与提供者的合同。然后,提供者可以从本合同中生成测试,即它需要满足的要求。起初,测试将失败。在实现API逻辑后,它将(希望)通过。最后,消费者还可以对残存的提供商运行测试并使其通过。
原文链接:https://blog.novatec-gmbh.de/consumer-driven-contract-testing-spring-cloud-contract/