Spring Cloud Contract是契約測試的一個實(shí)現(xiàn),最早看到契約測試還是在《微服務(wù)設(shè)計》書中,不過那時候絕對想不到真的會接觸它。
什么是契約測試?
首先,先談?wù)勊枷耄裁词瞧跫s測試?事實(shí)上在很多地方都稱為消費(fèi)者驅(qū)動契約(CDC) ,似乎都喜歡加驅(qū)動,比如TDD測試驅(qū)動等,但我不喜歡在這里加,契約是由提供者與消費(fèi)者共同制定的,不可能由一方驅(qū)動。而契約測試也將同時作用于兩方:
- 驗(yàn)證提供方是否履約
- 驗(yàn)證消費(fèi)方在提供方履約下是否正常工作
由于一般都只考慮提供方的履約驗(yàn)證,所以誤解為消費(fèi)者驅(qū)動。事實(shí)呢,提供方與消費(fèi)方是唯一的么?你的契約不會變么?一旦發(fā)生改變,那么契約測試也會對消費(fèi)方進(jìn)行驗(yàn)證。
為什么要使用
隨著服務(wù)拆分,服務(wù)間的調(diào)用變得更加頻繁。原本的測試方案(單元測試與集成測試)變得力不從心
單元測試,我們會mock服務(wù)間的調(diào)用,確保我們對很小的原本測試。但是,一旦提供方提供的服務(wù)發(fā)生改變,這不會及時的響應(yīng)在單元測試中,這種疏漏會導(dǎo)致單元測試失效。
集成測試,每次我們都需要運(yùn)行多個服務(wù),以配合完成。其中涉及到服務(wù)的配置等等各種問題。
這就好比是場舞臺戲,如果每個人都練自己的,那么很可能配合失敗。如果一直一起排練,又很占時間。所以需要一份實(shí)時都是最新的劇本,每個人都依據(jù)它,練習(xí)自己的部分。
實(shí)現(xiàn)
Spring Cloud Contract 提供不錯的實(shí)現(xiàn),它分為驗(yàn)證服務(wù)(Verifier)和對契約內(nèi)容Mock服務(wù)(Stub Runner)兩部分。
Verifier
通過groovy腳本或者yaml定義接口,由Spring Cloud Contract幫助我們生成測試用例并驗(yàn)證。
Step 1:環(huán)境配置
需要引入依賴
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-contract-verifier</artifactId>
<scope>test</scope>
</dependency>
<plugins>
<plugin>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-contract-maven-plugin</artifactId>
<extensions>true</extensions>
<configuration>
<baseClassMappings>
<baseClassMapping>
<contractPackageRegex>.*</contractPackageRegex>
<baseClassFQN>com.jtj.cloud.springcontractexample.AbstractDnocmTest</baseClassFQN>
</baseClassMapping>
</baseClassMappings>
</configuration>
</plugin>
</plugins>
其中插件的baseClassFQN配置是,為了讓生成的類繼承指定類。
需要配置web容器
@RunWith(SpringRunner.class)
@SpringBootTest(
classes = SpringContractExampleApplication.class,
webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT
)
@AutoConfigureMockMvc
public abstract class AbstractDnocmTest {
@Autowired
private WebApplicationContext context;
@Before
public void setup(){
RestAssuredMockMvc.webAppContextSetup(context);
}
}
注意:webflux沒有WebApplicationContext,所以目前只能配Controller
Step 2:編寫契約,可以使用groovy,也可以yaml
package contracts
import org.springframework.cloud.contract.spec.Contract
Contract.make {
request {
method 'GET'
urlPath('/groovy') {
queryParameters {
parameter "name": value(consumer(regex(nonEmpty())), producer("從入門到棄坑"))
}
}
}
response {
status OK()
body([
"name": fromRequest().query("name"),
"price": 1000
])
headers {
contentType('application/json;charset=UTF-8')
}
}
}
request:
method: GET
url: /yaml
queryParameters:
name: "從入庫到精通"
price: 666
matchers:
url:
queryParameters:
- {key: name, type: matching, value: "[\\S\\s]+"}
- {key: price, type: matching, value: '^[0-9]{1,}$'}
response:
status: 200
body:
name: "{{{ request.query.name.[0] }}}"
price: "{{{ request.query.price.[0] }}}"
headers:
Content-Type: application/json;charset=UTF-8
注意:matchers的queryParameters部分貌似沒有效果(按照文檔,照理說沒問題的唉),yaml可讀性較強(qiáng),但相對來說自定義較差,如果可能盡可能選擇groovy
Step 3:運(yùn)行
- 使用命令
mvn spring-cloud-contract:generateTests生成測試用例(在target\generated-test-sources\contracts目錄下),并執(zhí)行單條測試 - 運(yùn)行
mvn test會生成并執(zhí)行所有測試用例
Step 4:發(fā)布
時效性對于契約來說十分重要,若無法保證,那單元測試中的問題就等于沒解決,所以需要及時的發(fā)布生成的-stubs jar包
- 配置nexus oss,并發(fā)布至上面,與一般的上傳一致
mvn deploy,在例子,我使用的是jfrog bintray發(fā)布 - 如果沒有二進(jìn)制存儲庫,可以選擇Git進(jìn)行發(fā)布
- 還可以通過Pack,但是,在pack上需要先由前端定義好契約
三個方案各有優(yōu)缺點(diǎn):
第一個方案,利用maven來處理是非常合理的,maven本身就是用于二進(jìn)制文件管理,但如果你無法做到獨(dú)立升級部署(多個微服務(wù)一同修改),那么你可能需要依賴于SNAPSHOT jar,不穩(wěn)定的版本需要你多次上傳,而且maven的快照機(jī)制也造成每次都需要檢測獲取最新版本
第二個方案,對于契約的存儲獨(dú)立存放在git倉庫,它的工作原理是clone整個存儲庫至本地,解析版本以及一些其他的定義,獲取契約數(shù)據(jù)。問題在于clone,本身契約內(nèi)容是不多的,也就是clone并不會浪費(fèi)太多時間。但假如多年后呢,如果契約定義不規(guī)范頻繁改動的情況出現(xiàn)(在企業(yè)內(nèi),這是很常見的現(xiàn)象),可能造成存儲庫變得龐大
第三個方案,這是有別于前兩個,由消費(fèi)方定義契約。所以使用場景也更加明確,那就是前端人員充足時。Pact提供了圖形化的界面展示契約的驗(yàn)證情況,這是挺好的特性。但是對于后端微服務(wù)來說,提供契約需要通過其它方式(未很好的集成),這無疑造成了學(xué)習(xí)成本增加
Stub Runner
由于服務(wù)提供方對契約是校驗(yàn)的,所以我們可以認(rèn)為這個mock數(shù)據(jù)是基本準(zhǔn)確的。mock方式有多種
方案一:下載提供方源碼,編譯并運(yùn)行mvn spring-cloud-contract:convert && mvn spring-cloud-contract:run
方案二:在單元測試中使用,可以配合spring cloud替換一些注冊中心的服務(wù),在運(yùn)行時,不再需要啟動多個外部服務(wù)
方案三:獨(dú)立部署契約進(jìn)行mock,一個很好的前后端分離方案是,前端依據(jù)契約mock數(shù)據(jù)開發(fā),后端依據(jù)契約校驗(yàn)提供數(shù)據(jù)
方案四:使用spring cloud cli運(yùn)行,
安裝spring boot cli文檔,
安裝spring cloud cli(待再次實(shí)踐 按文檔教程操作失敗路過)
單元測試
需要的依賴
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-contract-stub-runner</artifactId>
<scope>test</scope>
</dependency>
需要添加自動配置注解
@AutoConfigureStubRunner
沒然后了,接下來都是些契約配置
# 關(guān)閉為了能替換ribbon服務(wù)
eureka:
client:
enabled: false
stubrunner:
ids:
# - [groupId]:artifactId:[version]:[classify]:[port]
- com.jtj.cloud:spring-contract-example:1.0.0
ids-to-service-ids:
# 映射服務(wù)
spring-contract-example: none-service
# 該模式下會從遠(yuǎn)程maven倉庫緩存到本地
stubs-mode: local
獨(dú)立部署
首先下載stub-runner.jar
wget -O stub-runner.jar 'https://search.maven.org/remotecontent?filepath=org/springframework/cloud/spring-cloud-contract-stub-runner-boot/2.1.1.RELEASE/spring-cloud-contract-stub-runner-boot-2.1.1.RELEASE.jar'
在同路徑創(chuàng)建配置文件application.yml,并添加stubrunner的配置,如下
stubrunner:
ids:
# - [groupId]:artifactId:[version]:[classify]:[port]
# 指定運(yùn)行在8081端口
- com.jtj.cloud:spring-contract-example:1.0.0:+:8081
# 該模式下會從遠(yuǎn)程maven倉庫緩存到本地
stubs-mode: local
通過java -jar stub-runner.jar運(yùn)行。即可訪問com.jtj.cloud:spring-contract-example的mock數(shù)據(jù)啦!
參考
本文作者: Mr.J
本文鏈接: https://www.dnocm.com/articles/beechnut/spring-cloud-contract/
版權(quán)聲明: 本博客所有文章除特別聲明外,均采用 BY-NC-SA 許可協(xié)議。轉(zhuǎn)載請注明出處!