微服务集成测试之痛
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,团队懂契约测试。在这种状况下,团队才或许情愿被动实施契约测试,才干成功的实施契约测试。所以首先是要让开发团队懂契约测试,比如契约测试能处置什么疑问,实施流程,相关测试框架等,然前期待团队无法忍受相关痛点后,成功的实施契约测试就可以水到渠成了。