Spring Cloud 合约功能
Posted
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Spring Cloud 合约功能相关的知识,希望对你有一定的参考价值。
5.5. 存根流道弹簧云
Stub Runner可以与Spring Cloud集成。
有关实际生活示例,请参阅:
- 生产者应用程序示例
- 使用者应用程序示例
5.5.1. 存根服务发现
最重要的特点是它存根:Stub Runner Spring Cloud
-
DiscoveryClient
-
ReactorServiceInstanceLoadBalancer
这意味着,无论您使用动物园管理员,领事,尤里卡还是任何东西。 否则,您在测试中不需要它。我们正在启动您的 WireMock 实例 依赖项,我们告诉您的应用程序,无论何时使用,都要加载 平衡直接调用那些存根服务器 而不是调用真正的服务发现工具。Feign
RestTemplate
DiscoveryClient
例如,以下测试通过:
def should make service discovery work()
expect: WireMocks are running
"$stubFinder.findStubUrl(loanIssuance).toString()/name".toURL().text == loanIssuance
"$stubFinder.findStubUrl(fraudDetectionServer).toString()/name".toURL().text == fraudDetectionServer
and: Stubs can be reached via load service discovery
restTemplate.getForObject(http://loanIssuance/name, String) == loanIssuance
restTemplate.getForObject(http://someNameThatShouldMapFraudDetectionServer/name, String) == fraudDetectionServer
请注意,前面的示例需要以下配置文件:
stubrunner:
idsToServiceIds:
ivyNotation: someValueInsideYourCode
fraudDetectionServer: someNameThatShouldMapFraudDetectionServer
测试配置文件和服务发现
在集成测试中,您通常不希望调用发现服务(如 Eureka) 或配置服务器。这就是您创建要在其中禁用的其他测试配置的原因 这些功能。
由于春云公地的某些限制, 为此,您必须禁用这些属性 在静态块中,例如以下示例(对于 Eureka):
//Hack to work around https://github.com/spring-cloud/spring-cloud-commons/issues/156
static
System.setProperty("eureka.client.enabled", "false");
System.setProperty("spring.cloud.config.failFast", "false");
5.5.2. 附加配置
您可以使用 themap 将存根与应用程序的名称进行匹配。artifactId
stubrunner.idsToServiceIds:
默认情况下,所有服务发现都是存根。这意味着,无论您是否有 一个现有的,其结果将被忽略。但是,如果你想重用它,你可以设置,然后你现有的结果是 与存根的合并。 |
存根运行器使用的默认 Maven 配置可以调整 通过设置以下系统属性或设置相应的环境变量:
-
maven.repo.local
:自定义 maven 本地存储库位置的路径 -
org.apache.maven.user-settings
:自定义 maven 用户设置位置的路径 -
org.apache.maven.global-settings
:指向 maven 全局设置位置的路径
5.6. 使用存根运行器引导应用程序
Spring Cloud 合约存根运行程序启动是一个 Spring 启动应用程序,它将 REST 端点公开给 触发消息标签并访问 WireMock 服务器。
5.6.1. 存根运行程序引导安全性
存根运行程序启动应用程序在设计上不受保护 - 保护它需要为所有应用程序添加安全性 存根,即使它们实际上并不需要它。由于这是一个测试实用程序 - 服务器不适合在生产环境中使用。
预计只有受信任的客户端才能访问存根运行程序引导服务器。你不应该 在不受信任的位置将此应用程序作为胖罐或Docker 映像运行。 |
5.6.2. 存根运行服务器
要使用存根运行器服务器,请添加以下依赖项:
compile "org.springframework.cloud:spring-cloud-starter-stub-runner"
然后用注释一个类,构建一个胖罐子,它就可以工作了。@EnableStubRunnerServer
有关属性,请参阅存根流道弹簧部分。
5.6.3. 存根运行器服务器胖罐
您可以从 Maven 下载独立的 JAR(例如,对于版本 2.0.1.RELEASE) 通过运行以下命令:
$ wget -O stub-runner.jar https://search.maven.org/remotecontent?filepath=org/springframework/cloud/spring-cloud-contract-stub-runner-boot/2.0.1.RELEASE/spring-cloud-contract-stub-runner-boot-2.0.1.RELEASE.jar
$ java -jar stub-runner.jar --stubrunner.ids=... --stubrunner.repositoryRoot=...
5.6.4. 春云命令行界面
从Spring Cloud CLI项目的版本开始,您可以通过运行来启动存根运行器引导。1.4.0.RELEASE
spring cloud stubrunner
要传递配置,您可以在当前工作目录中创建一个文件, 在调用的子目录中,或 in。该文件可能类似于以下内容 运行本地安装的存根的示例:stubrunner.yml
config
~/.spring-cloud
例 2.stubrunner.yml
stubrunner:
stubsMode: LOCAL
ids:
- com.example:beer-api-producer:+:9876
然后,您可以从终端窗口调用以启动 存根运行器服务器。它在港口可用。spring cloud stubrunner
8750
5.6.5. 端点
存根运行程序启动提供两个终结点:
- HTTP
- 消息
HTTP
对于 HTTP,存根运行程序启动使以下终结点可用:
- GET:返回所有正在运行的存根注释的列表
/stubs
ivy:integer
- GET:返回给定表示法的端口(调用端点时也可以是)
/stubs/ivy
ivy
ivy
artifactId
消息
对于消息传递,存根运行程序引导使以下端点可用:
- GET:返回所有正在运行的标签注释的列表
/triggers
ivy : [ label1, label2 …]
- 开机自检:运行触发器
/triggers/label
label
- POST:运行带有给定符号的触发器 (调用端点时,也可以只)
/triggers/ivy/label
label
ivy
ivy
artifactId
5.6.6. 例子
以下示例显示了存根运行程序引导的典型用法:
@SpringBootTest(classes = StubRunnerBoot, properties = "spring.cloud.zookeeper.enabled=false")
@ActiveProfiles("test")
class StubRunnerBootSpec extends Specification
@Autowired
StubRunning stubRunning
def setup()
RestAssuredMockMvc.standaloneSetup(new HttpStubsController(stubRunning),
new TriggerController(stubRunning))
def should return a list of running stub servers in "full ivy:port" notation()
when:
String response = RestAssuredMockMvc.get(/stubs).body.asString()
then:
def root = new JsonSlurper().parseText(response)
root.org.springframework.cloud.contract.verifier.stubs:bootService:0.0.1-SNAPSHOT:stubs instanceof Integer
def should return a port on which a [#stubId] stub is running()
when:
def response = RestAssuredMockMvc.get("/stubs/$stubId")
then:
response.statusCode == 200
Integer.valueOf(response.body.asString()) > 0
where:
stubId << [org.springframework.cloud.contract.verifier.stubs:bootService:+:stubs,
org.springframework.cloud.contract.verifier.stubs:bootService:0.0.1-SNAPSHOT:stubs,
org.springframework.cloud.contract.verifier.stubs:bootService:+,
org.springframework.cloud.contract.verifier.stubs:bootService,
bootService]
def should return 404 when missing stub was called()
when:
def response = RestAssuredMockMvc.get("/stubs/a:b:c:d")
then:
response.statusCode == 404
def should return a list of messaging labels that can be triggered when version and classifier are passed()
when:
String response = RestAssuredMockMvc.get(/triggers).body.asString()
then:
def root = new JsonSlurper().parseText(response)
root.org.springframework.cloud.contract.verifier.stubs:bootService:0.0.1-SNAPSHOT:stubs?.containsAll(["delete_book", "return_book_1", "return_book_2"])
def should trigger a messaging label()
given:
StubRunning stubRunning = Mock()
RestAssuredMockMvc.standaloneSetup(new HttpStubsController(stubRunning), new TriggerController(stubRunning))
when:
def response = RestAssuredMockMvc.post("/triggers/delete_book")
then:
response.statusCode == 200
and:
1 * stubRunning.trigger(delete_book)
def should trigger a messaging label for a stub with [#stubId] ivy notation()
given:
StubRunning stubRunning = Mock()
RestAssuredMockMvc.standaloneSetup(new HttpStubsController(stubRunning), new TriggerController(stubRunning))
when:
def response = RestAssuredMockMvc.post("/triggers/$stubId/delete_book")
then:
response.statusCode == 200
and:
1 * stubRunning.trigger(stubId, delete_book)
where:
stubId << [org.springframework.cloud.contract.verifier.stubs:bootService:stubs, org.springframework.cloud.contract.verifier.stubs:bootService, bootService]
def should throw exception when trigger is missing()
when:
RestAssuredMockMvc.post("/triggers/missing_label")
then:
Exception e = thrown(Exception)
e.message.contains("Exception occurred while trying to return [missing_label] label.")
e.message.contains("Available labels are")
e.message.contains("org.springframework.cloud.contract.verifier.stubs:loanIssuance:0.0.1-SNAPSHOT:stubs=[]")
e.message.contains("org.springframework.cloud.contract.verifier.stubs:bootService:0.0.1-SNAPSHOT:stubs=")
5.6.7. 使用服务发现的存根运行程序引导
使用存根运行器引导的一种方法是将其用作“冒烟测试”的存根馈送。那是什么意思? 假设您不想按顺序将 50 个微服务部署到测试环境 以查看您的应用程序是否正常工作。您已经在构建过程中运行了一套测试, 但您还希望确保应用程序的打包正常工作。您可以 将应用程序部署到环境中,启动它,然后对其运行几个测试以查看是否 它有效。我们可以称这些测试为“烟雾测试”,因为它们的目的是只检查少数几个 的测试场景。
这种方法的问题在于,如果你使用微服务,你很可能也 使用服务发现工具。存根运行程序引导允许您通过启动 所需的存根,并在服务发现工具中注册它们。考虑以下示例 尤里卡的这种设置(假设尤里卡已经在运行):
@SpringBootApplication
@EnableStubRunnerServer
@EnableEurekaClient
@AutoConfigureStubRunner
public class StubRunnerBootEurekaExample
public static void main(String[] args)
SpringApplication.run(StubRunnerBootEurekaExample.class, args);
我们要启动存根运行器引导服务器(),启用尤里卡客户端(), 并打开存根运行程序功能()。@EnableStubRunnerServer
@EnableEurekaClient
@AutoConfigureStubRunner
现在假设我们要启动此应用程序,以便自动注册存根。 我们可以通过运行应用程序来做到这一点,其中包含以下属性列表:java -jar $SYSTEM_PROPS stub-runner-boot-eureka-example.jar
$SYSTEM_PROPS
* -Dstubrunner.repositoryRoot=https://repo.spring.io/snapshot (1)
* -Dstubrunner.cloud.stubbed.discovery.enabled=false (2)
* -Dstubrunner.ids=org.springframework.cloud.contract.verifier.stubs:loanIssuance,org.
* springframework.cloud.contract.verifier.stubs:fraudDetectionServer,org.springframework.
* cloud.contract.verifier.stubs:bootService (3)
* -Dstubrunner.idsToServiceIds.fraudDetectionServer=
* someNameThatShouldMapFraudDetectionServer (4)
*
* (1) - we tell Stub Runner where all the stubs reside (2) - we dont want the default
* behaviour where the discovery service is stubbed. Thats why the stub registration will
* be picked (3) - we provide a list of stubs to download (4) - we provide a list of
这样,您部署的应用程序可以通过服务向启动的 WireMock 服务器发送请求 发现。最有可能的是,点 1 到 3 可以默认设置,因为它们不是 可能会改变。这样,您只需提供启动时要下载的存根列表 存根运行器引导。application.yml
5.7. 消费者驱动的合同:每个消费者的存根
在某些情况下,同一终结点的两个使用者希望有两个不同的响应。
此方法还可以让您立即知道哪个使用者使用 API 的哪个部分。 您可以删除 API 生成的部分响应,并查看哪些自动生成的测试 失败。如果没有失败,您可以安全地删除响应的该部分,因为没有人使用它。 |
考虑以下为称为 它有两个使用者(和):producer
foo-consumer
bar-consumer
消费者foo-service
request
url /foo
method GET()
response
status OK()
body(
foo: "foo"
消费者bar-service
request
url /bar
method GET()
response
status OK()
body(
bar: "bar"
不能为同一请求生成两个不同的响应。这就是为什么您可以正确打包 合同,然后从该功能中获利。stubsPerConsumer
在生产者端,使用者可以拥有一个包含仅与他们相关的合同的文件夹。 通过将 theflag 设置为 ,我们不再注册所有存根,而只注册那些 对应于使用者应用程序的名称。换句话说,我们扫描每个存根的路径,并且, 如果它包含路径中具有使用者名称的子文件夹,则只有这样才会注册。stubrunner.stubs-per-consumer
true
在生产者方面,合同看起来像这样foo
.
└── contracts
├── bar-consumer
│ ├── bookReturnedForBar.groovy
│ └── shouldCallBar.groovy
└── foo-consumer
├── bookReturnedForFoo.groovy
└── shouldCallFoo.groovy
消费者可以设置理论或 theto,或者,您可以按如下方式设置测试:bar-consumer
spring.application.name
stubrunner.consumer-name
bar-consumer
@SpringBootTest(classes = Config, properties = ["spring.application.name=bar-consumer"])
@AutoConfigureStubRunner(ids = "org.springframework.cloud.contract.verifier.stubs:producerWithMultipleConsumers",
repositoryRoot = "classpath:m2repo/repository/",
stubsMode = StubRunnerProperties.StubsMode.REMOTE,
stubsPerConsumer = true)
@ActiveProfiles("streamconsumer")
class StubRunnerStubsPerConsumerSpec extends Specification
...
然后,仅允许引用在其名称中包含路径(即文件夹中的路径)下注册的存根。bar-consumer
src/test/resources/contracts/bar-consumer/some/contracts/…
您还可以显式设置使用者名称,如下所示:
@SpringBootTest(classes = Config)
@AutoConfigureStubRunner(ids = "org.springframework.cloud.contract.verifier.stubs:producerWithMultipleConsumers",
repositoryRoot = "classpath:m2repo/repository/",
consumerName = "foo-consumer",
stubsMode = StubRunnerProperties.StubsMode.REMOTE,
stubsPerConsumer = true)
@ActiveProfiles("streamconsumer")
class StubRunnerStubsPerConsumerWithConsumerNameSpec extends Specification
...
然后,只允许引用在包含其名称的路径下注册的存根(即文件夹中的存根)。foo-consumer
src/test/resources/contracts/foo-consumer/some/contracts/…
5.8. 从一个位置获取存根或合约定义
而不是从中选取存根或协定定义 Artifactory,Nexus或Git,你可以指向 驱动器或类路径上的位置。这样做在多模块项目中特别有用,其中一个模块需要 重用另一个模块的存根或协定,而无需 需要在本地 Maven 中实际安装它们 存储库以将这些更改提交到 Git。
为了实现这一点,您可以在设置存储库根参数时使用协议 在存根运行器或 Spring Cloud 合约插件中。stubs://
在此示例中,项目已成功 在文件夹下生成了构建和存根。作为使用者,可以使用协议设置存根运行程序以从该位置选取存根。producer
target/stubs
stubs://
@AutoConfigureStubRunner(
stubsMode = StubRunnerProperties.StubsMode.REMOTE,
repositoryRoot = "stubs://file://location/to/the/producer/target/stubs/",
ids = "com.example:some-producer")
合同和存根可以存储在一个位置,每个生产者都有自己的专用文件夹,用于合同和存根映射。在该文件夹下,每个使用者都可以有自己的设置。若要使存根运行程序从提供的 ID 中找到专用文件夹,可以传递属性或系统属性。 以下清单显示了合同和存根的排列:stubs.find-producer=true
stubrunner.stubs.find-producer=true
└── com.example
├── some-artifact-id
│ └── 0.0.1
│ ├── contracts
│ │ └── shouldReturnStuffForArtifactId.groovy
│ └── mappings
│ └── shouldReturnStuffForArtifactId.json
└── some-other-artifact-id
├── contracts
│ └── shouldReturnStuffForOtherArtifactId.groovy
└── mappings
└── shouldReturnStuffForOtherArtifactId.json
使用者的组 ID |
具有工件 ID 的使用者 [某些工件 ID] |
具有工件 ID [某些工件 ID] 的使用者的合约 |
具有项目 ID 的使用者的映射 [some-artifact-id] |
具有工件 ID 的使用者 [其他工件 ID] |
@AutoConfigureStubRunner(
stubsMode = StubRunnerProperties.StubsMode.REMOTE,
repositoryRoot = "stubs://file://location/to/the/contracts/directory",
ids = "com.example:some-producer",
properties="stubs.find-producer=true")
5.9. 在运行时生成存根
作为使用者,您可能不希望等待生成者完成其实现,然后发布其存根。此问题的解决方案可以在运行时生成存根。
作为生产者,定义合约时,您需要使生成的测试通过,以便发布存根。在某些情况下,您希望取消阻止使用者,以便他们可以在您的测试实际通过之前获取存根。在这种情况下,应将此类合同设置为进行中。您可以在“正在进行的合同”部分下阅读有关此内容的更多信息。这样,不会生成测试,但会生成存根。
作为使用者,您可以切换开关以在运行时生成存根。存根运行程序忽略所有现有的存根映射,并为所有协定定义生成新的映射。另一种选择是传递系统属性。以下示例显示了此类设置:stubrunner.generate-stubs
@AutoConfigureStubRunner(
stubsMode = StubRunnerProperties.StubsMode.REMOTE,
repositoryRoot = "stubs://file://location/to/the/contracts",
ids = "com.example:some-producer",
generateStubs = true)
5.10. 无存根失败
默认情况下,如果未找到存根,存根运行程序将失败。若要更改该行为,请在注释中设置属性或在 JUnit 规则或扩展上调用该方法。以下示例演示如何执行此操作:failOnNoStubs
false
withFailOnNoStubs(false)
@AutoConfigureStubRunner(
stubsMode = StubRunnerProperties.StubsMode.REMOTE,
repositoryRoot = "stubs://file://location/to/the/contracts",
ids = "com.example:some-producer",
failOnNoStubs = false)
5.11. 通用属性
本节简要介绍常见属性,包括:
- JUnit 和 Spring 的通用属性
- 存根运行程序存根 ID
5.11.1. JUnit 和 Spring 的通用属性
您可以使用系统属性或 Spring 配置来设置重复属性 性能。下表显示了其名称及其默认值:
属性名称 | 默认值 | 描述 |
| | 带有存根的已启动 WireMock 的端口的最小值。 |
| | 带有存根的已启动 WireMock 的端口的最大值。 |
| Maven repository URL.如果为空,则调用本地 Maven 存储库。 | |
| | 存根项目的默认分类器。 |
| | 要获取和注册存根的方式。 |
| 要下载的常春藤符号存根数组。 | |
| 可选用户名,用于访问存储 JAR 的工具 存根。 | |
| 可选密码,用于访问存储 JAR 的工具 存根。 | |
| | 设置为 如果要将不同的存根用于 每个使用者,而不是为每个使用者注册所有存根。 |
| 如果要为每个使用者使用存根,并且想要 覆盖使用者名称,更改此值。 |
5.11.2. 存根运行器存根 ID
您可以在系统属性中设置要下载的存根。他们 使用以下模式:stubrunner.ids
groupId:artifactId:version:classifier:port
请注意,和是可选的。version
classifier
port
- 如果您不提供,则随机选择一个。
port
- 如果未提供,则使用默认值。(请注意,您可以 以这种方式传递一个空分类器:)。
classifier
groupId:artifactId:version:
- 如果您不提供,则通过,最新的是 下载。
version
+
port
表示 WireMock 服务器的端口。
从版本 1.0.4 开始,您可以提供一系列版本 希望存根运行器考虑在内。您可以阅读有关 以太版本控制范围在这里。 |
6. 春云合约电汇模拟
Spring Cloud 合约 WireMock 模块允许您在 弹簧启动应用程序。有关更多详细信息,请查看示例。
如果你有一个使用 Tomcat 作为嵌入式服务器的 Spring Boot 应用程序(这是 默认为),您可以添加到类路径中,并在测试中使用 Wiremock。Wiremock 作为存根服务器运行,而您 可以通过使用 Java API 或使用静态 JSON 声明作为 您的测试。以下代码显示了一个示例:spring-boot-starter-web
spring-cloud-starter-contract-stub-runner
@AutoConfigureWireMock
@SpringBootTest(webEnvironment = WebEnvironment.RANDOM_PORT)
@AutoConfigureWireMock(port = 0)
public class WiremockForDocsTests
// A service that calls out over HTTP
@Autowired
private Service service;
@BeforeEach
public void setup()
this.service.setBase("http://localhost:"
+ this.environment.getProperty("wiremock.server.port"));
// Using the WireMock APIs in the normal way:
@Test
public void contextLoads() throws Exception
// Stubbing WireMock
stubFor(get(urlEqualTo("/resource")).willReturn(aResponse()
.withHeader("Content-Type", "text/plain").withBody("Hello World!")));
// Were asserting if WireMock responded properly
assertThat(this.service.go()).isEqualTo("Hello World!");
要在其他端口上启动存根服务器,请使用(例如)。对于随机端口,请使用值 of。存根 服务器端口可以在测试应用程序上下文中与属性绑定。使用添加类型到 测试应用程序上下文,在方法和类之间缓存 具有相同的上下文。Spring 集成测试也是如此。另外,您可以 将类型的 bean 注入到您的测试中。 注册的 WireMock 服务器在每个测试类后重置。 但是,如果需要在每个测试方法之后重置它,请将属性设置为 。@AutoConfigureWireMock(port=9999)
0
wiremock.server.port
@AutoConfigureWireMock
WiremockConfiguration
WireMockServer
wiremock.reset-mappings-after-each-test
true
6.1. 自动注册存根
如果使用,它会从文件中注册 WireMock JSON 存根 系统或类路径(缺省情况下为 from)。您可以 通过使用注释中的属性自定义位置,该属性可以是 Ant 样式的资源模式或目录。在目录的情况下,是 附加。以下代码显示了一个示例:@AutoConfigureWireMock
file:src/test/resources/mappings
stubs
*/.json
@RunWith(SpringRunner.class)
@SpringBootTest
@AutoConfigureWireMock(stubs="classpath:/stubs")
public class WiremockImportApplicationTests
@Autowired
private Service service;
@Test
public void contextLoads() throws Exception
assertThat(this.service.go()).isEqualTo("Hello World!");
实际上,WireMock总是从as加载映射。 以及属性中的自定义位置。若要更改此行为,可以 还要指定文件根目录,如本文档的下一节所述。 |
此外,位置中的映射不被视为Wiremock的“默认映射”和调用的一部分 在测试期间不会导致映射 在包含的位置。但是,在每个测试类之后重置映射(包括从存根位置添加映射),并且可以选择 在每种测试方法之后(由财产保护)。 |
如果您使用春云合约的默认存根罐,您的 存根存储在文件夹中。 如果要从该位置注册所有嵌入式 JAR 中的所有存根,可以使用 以下语法:/META-INF/group-id/artifact-id/versions/mappings/
@AutoConfigureWireMock(port = 0, stubs = "classpath*:/META-INF/**/mappings/**/*.json")
6.2. 使用文件指定存根主体
WireMock 可以从类路径或文件系统上的文件中读取响应正文。在 在文件系统的情况下,您可以在 JSON DSL 中看到响应具有 a而不是 (字面意思)。这些文件是相对于根目录解析的(默认情况下)。要自定义此位置,可以将注释中的属性设置为父项的位置 目录(换句话说,是一个子目录)。您可以使用 Spring 资源 表示法以引用位置。通用网址不是 支持。可以给出一个值列表 — 在这种情况下,WireMock 解析第一个文件 当它需要找到响应正文时,它就存在。bodyFileName
body
src/test/resources/__files
files
@AutoConfigureWireMock
__files
file:…
classpath:…