1. 首页 > 娱乐

探秘Spring Contract 如何保障您的API符合预期

微服务集成测试之痛

1.什么是契约测试

契约测试(Contract testing)是一种测试技术,它通过以隔离检查集成点上的每个应用的方式,确保应用发送或接收的消息符合调用双方共识,并允许随着时间的推移进行演化。

契约测试是对单元测试的增强,针对服务接口provider测试,覆盖了一部分本来需要集成测试才能测试到的场景。

2.为什么要做契约测试

契约测试主要解决在存在沟通边界情况下,测试替身(Test Double)与生产代码表现可能不一致的问题。在契约测试中,契约由代码生成,保持与现实同步,而且应用可以独立于其它应用而仅基于契约进行快速测试。

由于集成测试容易受到网络缓慢或不可靠,以及服务不可靠等因素的影响而运行缓慢或失败,所以通常会引入测试替身来代替真实外部服务,以快速完成覆盖度更广的测试,让测试真正起到作用。

3.契约测试的定位

金字塔模型是构建健康、快速、可维护测试集的成熟理论。

4.契约测试的价值

众所周知,越是在项目生命周期的后期发现Bug,其修复的成本就越高。

不同于端到端(E2E)测试,契约测试可以在开发人员推送代码之前运行,在开发阶段提早发现问题。

契约测试还有很多端到端测试不具备的好处:

引入契约测试,还会带来如下福利:

没有两个团队是完全一样的,契约测试也不是万能的,关键要看契约测试可以为团队和项目带来什么。

5.契约测试适合的场景

契约测试可以用于任何需要通信的两个服务,比如Web前端与后端API服务。

在微服务架构体系中,因为存在更多团队独立、服务间调用及服务单独演进的情形,契约测试有了更好更大的用武之地。良好的契约测试,使得开发人员很容易避免版本地狱,是微服务开发和部署的利器。

6.概念术语

契约测试主要涉及如下概念术语:

7.契约测试模式

契约测试分为消费者驱动(consumer-driven)和提供者驱动(Provider-driven)两种模式。

消费者驱动更具哲学意义,将API的消费者置于设计过程的核心,来倡导更好的内部微服务设计。该模式的优点在于,只有消费者正在使用的部分会得到测试,而提供者可以自由地更改消费者不使用的任何其它部分,而不必破坏任何现有测试。

提供者驱动思路较为常规,更适合开放数据或系统的场景。

无论采用哪种风格,关键在于获得契约测试的好处,实现引入契约测试的目的。

(1)消费者驱动

消费者驱动的契约测试运行步骤如下:

(2)提供者驱动

提供者驱动模式由提供者定义契约并驱动整个过程。

8.契约测试工具

流行的契约测试工具为:

9.利用Pact进行消费者驱动的测试价值

利用Pact进行契约测试的整个流程示意如下,使用了 pact 之后,依然是每个服务独立的进行单元测试,但是可以模拟出真实集成场景。

10.Pact示例

pact契约测试分为两步:

以下为nlp-pact-parent示例:

(1)父项目pom包相关

<dependencies><dependency><groupId>au.com.dius</groupId><artifactId>pact-jvm-consumer-junit5</artifactId><version>4.0.10</version><scope>test</scope></dependency><dependency><groupId>au.com.dius</groupId><artifactId>pact-jvm-provider-junit5</artifactId><version>4.0.10</version><scope>test</scope></dependency>

(2)nlp-pact-consumer消费端项目

编写ConsumerTest生成契约:

@ExtendWith(PactConsumerTestExt.class)@SpringBootTest(classes = PactConsumerApplication.class, webEnvironment = SpringBootTest.WebEnvironment.DEFINED_PORT)@PactTestFor(providerName = "nlp-pact-provider", port = "8202")public class ConsumerTest {private TestRestTemplate restTemplate = new TestRestTemplate();@Test@PactTestFor(pactMethod = "greetingPact")void greeting_shouldReturnMessage() {// ArrangeHttpHeaders headers = new HttpHeaders();headers.add("Content-Type", "application/json");// ActResponseEntity<Map> response = restTemplate.getForEntity("http://localhost:8202/greeting?name=John", Map.class);// AssertassertEquals(HttpStatus.OK, response.getStatusCode());assertEquals(Collections.singletonMap("message", "Hello, John!"), response.getBody());}// Pact 定义@Pact(consumer = "nlp-pact-consumer", provider = "nlp-pact-provider")public RequestResponsePact greetingPact(PactDslWithProvider builder) {return builder.given("a request for greeting with name 'John'").uponReceiving("a request to greet John").path("/greeting").method("GET").query("name=John").willRespondWith().status(200).headers(Collections.singletonMap("Content-Type", "application/json")).body("{\"message\": \"Hello, John!\"}").toPact();}}

(3)执行ConsumerTest测试用例,生成如下契约文件:

# nlp-pact-consumer-nlp-pact-provider.json{"provider": {"name": "nlp-pact-provider"},"consumer": {"name": "nlp-pact-consumer"},"interactions": [{"description": "a request to greet John","request": {"method": "GET","path": "/greeting","query": {"name": ["John"]}},"response": {"status": 200,"headers": {"Content-Type": "application/json"},"body": {"message": "Hello, John!"}},"providerStates": [{"name": "a request for greeting with name 'John'"}]}],"metadata": {"pactSpecification": {"version": "3.0.0"},"pact-jvm": {"version": "4.0.10"}}}

(4)nlp-pact-provider提供端验证契约

build配置如下:

<plugin><groupId>au.com.dius</groupId><artifactId>pact-jvm-provider-maven</artifactId><version>4.0.0</version><configuration><serviceProviders><serviceProvider><name>nlp-pact-provider</name><protocol>http</protocol><host>localhost</host><port>8200</port><path>/</path><pactFileDirectory>resources/pacts</pactFileDirectory></serviceProvider></serviceProviders><pactBrokerUrl/></configuration></plugin>

(5)运行命令:mvn pact:verify,验证契约

Found 1 pact filesVerifying a pact between nlp-pact-consumer and nlp-pact-provider[Using File D:\IdeaProjects\nlp-other-project-dev\nlp-pact-parent\nlp-pact-provider\target\pacts\nlp-pact-consumer-nlp-pact-provider.json]Given a request for greeting with name 'John'WARNING: State Change ignored as there is no stateChange URLa request to greet Johnreturns a response whichhas status code 200 (OK)has a matching body (OK)[WARNING] Skipping publishing of verification results as it has been disabled (pact.verifier.publishResults is not 'true')[INFO] ------------------------------------------------------------------------[INFO] BUILD SUCCESS[INFO] ------------------------------------------------------------------------[INFO] Total time:1.589 s[INFO] Finished at: 2023-03-08T15:02:33+08:00[INFO] --------

11.Pact Broker

Pact Broker是一个用于共享消费者驱动的合同和验证结果的应用程序。

pact主页面:

查看服务间关系:

与CICD集成:

12.总结

我在不少项目中都尝试过实施契约测试,但是真正实施成功的并不多,主要原因还是规模和痛点不够大,从而导致团队觉得没有必要做,或者觉得做了收益比投入少。而成功的一般的都是团队人员足够痛,或者经历过大型多团队项目中服务改变等各种痛点,从而导致他们解决自己的痛点而主动实施契约测试,但是前提是他们都知道契约测试。所以要成功实施契约都是有两个主要的前提条件:1,团队对于相关问题足够痛,2,团队懂契约测试。在这种情况下,团队才可能愿意主动实施契约测试,才能成功的实施契约测试。所以首先是要让开发团队懂契约测试,比如契约测试能解决什么问题,实施流程,相关测试框架等,然后等待团队无法忍受相关痛点后,成功的实施契约测试就可以水到渠成了。

本网站的文章部分内容可能来源于网络和网友发布,仅供大家学习与参考,如有侵权,请联系站长进行删除处理,不代表本网站立场,转载者并注明出处:https://jmbhsh.com/yule/35543.html

联系我们

QQ号:***

微信号:***

工作日:9:30-18:30,节假日休息