Spring-Cloud-Contract實戰
文章目錄
Spring-Cloud-Contract
消費者驅動的契約測試(Consumer-Driven Contracts,簡稱CDC),是指從消費者業務實現的角度出發,驅動出契約,再基於契約,對提供者驗證的一種測試方式。
主要應用場景
1. 多服務、多團隊系統、前後端聯調使用
若想測試應用v1,我們可以:
- 部署所有微服務並執行端到端測試。
優點:
模擬生產。
測試服務之間的真實通訊。
缺點:
要測試一個微服務,我們必須部署6個微服務,幾個資料庫等。
執行時間很長,穩定性差,容易失敗。
非常難以除錯,依賴服務不受控制。
- 在單元/整合測試中模擬其他微服務。
優點:
非常快速的反饋,簡單易用。
沒有基礎設施要求,如DB,網路等。
缺點:
模擬不夠真實。
部分場景測試不到。
2. 契約工作流程
使用契約後,測試v1就不用啟動其他服務了(有依賴其他服務的資料的話)。
通俗來講,契約使用步驟可大致分為:
2.1 生產者提供定義好的契約(介面,包含request Method、header,parameter,body)
2.2 生產者生成stub jar,提供給消費者,可mvn install到Maven庫(本地/遠端倉庫)
2.3 消費者呼叫生產者的介面(從契約獲取資料)
更為詳細的契約工作流程如下:
Spring-Cloud-Contract的開發者MARCIN GRZEJSZCZAK在部落格中提到:Spring Cloud Contract in a polyglot world
The producer:(生產者)
- Applies a Maven or Gradle Spring Cloud Contract plugin.
- (譯) 使用Maven/Gradle的Spring Cloud Contract外掛。
- Defines YAML contracts under src/test/resources/contracts/.
- (譯) 在目錄src/test/resources/contracts下,定義Groovy/YAML形式的合同。
- Generates tests and stubs from the contract.
- (譯) 從契約中生成測試類和存根
- Creates a base class that extends the generated tests and sets up the test context.
- (譯) 建立一個基類並設定測試的上下文,用於被生成的測試類繼承
- Once the tests pass, creates a JAR with stubs classifier where contracts and stubs are stored.
- (譯) 測試通過後,會建立一個stubs-jar,其中儲存了契約和存根
- Uploads the JAR with a stubs classifier to binary storage.
- (譯) 上傳stubs-jar到Maven庫
The consumer:(消費者)
- Uses Stub Runner to fetch the stubs of the producer. Stub Runner starts in memory HTTP servers (by default, those are WireMock servers) fed with the stubs.
- (譯) 使用stub Runner獲取生產者的存根。Stub Runner在記憶體中啟動了HTTP伺服器(預設情況下,那些是WireMock伺服器)。
- Runs tests against the stubs.
- (譯) 針對存根執行測試。
Consequently, using Spring Cloud Contract and Contract Testing gives you:
(譯) 因此,使用契約和契約測試會帶給你
- stubs reliability: They were generated only after the tests have passed.
- (譯) 存根可靠性:測試通過才生成
- stubs reusability: They can be downloaded and reused by multiple consumers.
- (譯) 存根可重用性:可被多個消費者下載,使用
3. 使用契約-Producer side(服務提供端)
3.1 新增依賴&外掛
<!-- 自動生成單元測試程式碼 -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-contract-verifier</artifactId>
<version>2.0.3.BUILD-SNAPSHOT</version>
<scope>test</scope>
</dependency>
<!--該外掛自動生成測試類、stubs(存根)-->
<plugin>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-contract-maven-plugin</artifactId>
<version>2.0.2.BUILD-SNAPSHOT</version>
<extensions>true</extensions>
<configuration>
<!--測試基類-->
<baseClassForTests>com.xiaobai.producer.BaseMock</baseClassForTests>
</configuration>
</plugin>
3.2 建立測試基類
建立外掛指定的測試基類
BaseMock.java
@RunWith(SpringRunner.class)
@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.MOCK)
//測試的application context會被關閉,同時快取會清除
@DirtiesContext
@AutoConfigureMessageVerifier
public class BaseMock {
@Autowired
private WebApplicationContext context;
@Before
public void setUp() {
//使用上下文構建
RestAssuredMockMvc.webAppContextSetup(context);
}
}
3.3 Producer side-待測試的介面
@RestController
@RequestMapping("/user")
@Slf4j
public class UserController {
@Autowired
private UserMapper userMapper;
@GetMapping("/{id}")
public UserPO getOneUser(@PathVariable("id") Integer id){
/**
(介面開發完成的情況)介面的返回結果跟契約定義的結果需一致
,否則生成的測試類,斷言會不通過,也就無法生成stub jar了。這種情況應該是契約的正確使用方式,消費者定義契約,驗證生產者的介面是否符合契約,符合才能構建成功。
(介面業務程式碼未實現的情況)可以不需要介面,只寫契約,Maven install時,只需mvn clean install -DskipTests,即可跳過verifier的測試,這種情況我是用來將生成的.json扔到mock伺服器上。
**/
return userMapper.selectByPrimaryKey(id);
}
...
}
3.4 Producer side-新增合同
合同預設位置src/test/resources/contracts,
支援Groovy或yaml編寫的合同定義語言(DSL)
shouldReturnOneUserSuccess.groovy:
import org.springframework.cloud.contract.spec.Contract
Contract.make {
//ignored()
description "should return one user success"
request {
method 'GET'
urlPath('/user/1')
}
response {
status 200
body(
'''
{
"id": 1,
"childName": "小白",
"parentName": "大白",
"phone": "18880000"
}
'''
)
headers {
header('Content-Type', 'application/json;charset=UTF-8')
}
}
}
3.5 Maven install(Producer side生成stubs-jar)
(介面未寫業務程式碼的情況) mvn clean install -DskipTests
(介面已實現業務程式碼的情況) 直接install,外掛會自動生成測試類(1),合同(2),對映的json(3),存根jar(4)
生成的測試類ContractVerifierTest.java
合同(2)即是我們3.4步驟書寫的契約檔案
對映的json檔案(3),該檔案可放到Mock伺服器執行,後面會提到。
存根jar(4)已安裝到本地Maven倉庫,可提供給其他服務呼叫,後面會呼叫到。
4.Consumer Side(消費存根)
消費存根jar可以用Fegin,RestTemplate…這裡筆者使用RestTemplate.
這裡提一下,Fegin消費存根需要在application.yml增加stubrunner屬性,如下:
#Fegin消費存根需增加的配置,意思是哪個jar包對應哪個Fegin,這樣就無需註冊中心了。
stubrunner:
ids-to-service-ids:
需要消費的jar包artifactID1:Fegin對應的value屬性值1
需要消費的jar包artifactID2:Fegin對應的value屬性值2
4.1 Consumer Side-新增依賴
<!-- 自動下載、執行stub-jar -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-contract-stub-runner</artifactId>
<version>2.0.2.RELEASE</version>
</dependency>
4.2 寫測試類(消費存根)
ConsumerTest.java
@RunWith(SpringRunner.class)
@SpringBootTest(classes = ConsumerApplication.class, webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
//初始化測試配置,測試controller需要
@AutoConfigureMockMvc
@AutoConfigureJsonTesters
@AutoConfigureStubRunner(ids = {"com.xiaobai:producer:+:stubs:10001"},stubsMode = StubRunnerProperties.StubsMode.LOCAL)
@Slf4j
public class ConsumerTest {
@Autowired
private RestTemplate restTemplate;
@Test
public void testMethod() throws Exception {
ResponseEntity<JSONObject> response = restTemplate.exchange(
"http://localhost:10001/user/1",
HttpMethod.GET,
null,
JSONObject.class
);
log.info("測試資料:"+ response.getBody().toJSONString());
}
}
@AutoConfigureStubRunner自動下載存根,
ids屬性格式
groupId:artifactId:version:classifier:port
執行該測試類
當測試上下文啟動,無需啟動生產者的服務,在測試類呼叫生產者的介面不會404,因為Spring Cloud Contract Stub Runner將自動啟動測試中的WireMock伺服器並使用從伺服器端生成的存根來提供返回資料。
5.主要應用場景
消費者驅動契約,生產者根據契約進行開發,spring-cloud-contract-verifier確保了生產者的介面一定符合契約,才能構建成功。契約需要雙方共同遵守,任意一方修改了契約,可能會導致測試失敗。 如消費者更新了契約(舊版->新版),而生產者未更新業務程式碼,那麼生產者的構建就會導致失敗。
So,基於契約開發才是spring-cloud-contract的正確開啟方式。
5.1 多服務、多團隊,跨服務測試
當涉及多個系統時,可以通過公共的庫,下載stubs-jar即可進行本地跨服務測試。
5.2 使用契約進行聯調
如介面提供方,呼叫方存在多個團隊,初期可使用契約進行聯調。
如,前端需要後端提供介面,當後端開發滯後時,可先定義契約的入參、回參,部署到Mock伺服器,前端呼叫Mock的請求,返回定義好的資料,這樣就不會阻塞前端工作。
WireMock伺服器可以在自己的程式中執行,並通過Java API,JSON over HTTP或JSON檔案進行配置。
WireMock沒怎麼研究,我只會一種方式:將生產者生成的.json檔案(/target/stubs/MERA-INF/groupId/artifactID/version/mappings下),扔到WireMock的/mapping目錄下
5.2.1 下載WireMock standalone(不推薦這種方式)
(推薦) tips:將所有的.json檔案扔到mock伺服器未免太麻煩,stub runner將.json打包成stubs-jar,然後直接執行該jar也可達到相同效果
參考官方文件https://cloud.spring.io/spring-cloud-contract/single/spring-cloud-contract.html#_stub_runner_server_fat_jar
以下方式略顯麻煩,可參考上面的stub_runner_server_fat_jar
執行在本地或者伺服器上
java -jar wiremock-standalone-2.18.0.jar --port 10001
啟動Mock伺服器會在當前目錄生成2個空資料夾:__files和mappings。__files是放上傳/下載/錄製檔案用的,mappings存放.json.
Tips:新增修改mapping檔案後,都需要重啟服務才能生效
5.2.2 .json放到__mapping下(不推薦)
將該.json放到Mock伺服器的_mapping下。重啟Mock的jar。
5.2.3 訪問mock介面
此功能好像很多工具都可以實現,如淘寶的rap介面管理工具…
5.3 確保介面符合契約
如果使用spring-cloud-contract-verifier生成測試類的話,介面的業務程式碼返回的資料一定符合契約時,才能構建成功。這裡也可用於單元測試,驗證自己的介面格式,好處是不用寫單元測試程式碼(自動生成)。
6.自動化
我認為, 契約正確開啟方式應該是:
- 消費者定義契約(.groovy 或.yaml),釋出到公共契約庫。
- 生產者拉取契約,放至/src/test/resources/contracts下,然後生產者實現介面業務(如業務未實現的情況下,可以直接寫.json到WireMock伺服器下,這樣也可用於給消費者通過Rest呼叫),spring-cloud-contract-verifier會驗證介面是否符合契約的預期返回值,符合則構建stubs-jar成功。
- 消費者通過Stubs Runner遠端下載,執行生產者的stubs-jar,即可進行契約測試,消費對方的契約。
未實現…-.-
上圖參考的連結
https://piotrminkowski.wordpress.com/tag/spring-cloud-contract/
可供參考:
https://www.baeldung.com/spring-cloud-contract
如果對您有幫助,不妨點贊支援我,有錯誤、改善的地方請指出。
契約的國內資料暫時比較少 ,有些踩過的坑,動態屬性,正則匹配等, 本文未提到(懶得更新啦),如有疑問可以提出,或許我能提供幫助 ?
相關文章
- RocketMQ實戰系列從理論到實戰MQ
- Maven實戰與原理分析(二):maven實戰Maven
- 實戰篇——CSRF漏洞pikachu靶場實戰
- Activiti實戰
- Git實戰Git
- flex實戰Flex
- MQTT 實戰MQQT
- CoreOS實戰
- Shell——實戰
- es 實戰
- AutoGPT實戰GPT
- LangChain實戰LangChain
- SEO 實戰
- ClickHouse實戰
- Sentinel實戰
- Docker實戰Docker
- 實戰NginxNginx
- SaltStack實戰
- php實戰PHP
- Puppet實戰
- RMAN實戰
- WebService實戰Web
- Jedis實戰
- OSGi實戰
- Cassandra實戰
- REM實戰REM
- Redis實戰Redis
- DDD實戰課(實戰篇)--學習筆記筆記
- 01-kNN演算法實戰-(機器學習實戰)KNN演算法機器學習
- javaNIO實戰4----> java NIO的通道Channel實戰Java
- shiro實戰系列(二)之入門實戰續
- 【靶場實戰】vulntarget-b漏洞靶場實戰
- Source Generator實戰
- Vue 元件實戰Vue元件
- Redisson實戰-BloomFilterRedisOOMFilter
- redis - hash 實戰Redis
- DevOps實戰dev
- Flink實戰