Spring Cloud Contract 微服務契約測試
阿新 • • 發佈:2020-01-22
簡介
使用場景
主要用於在微服務架構下做CDC(消費者驅動契約)測試。下圖展示了多個微服務的呼叫,如果我們更改了一個模組要如何進行測試呢?
- 傳統的兩種測試思路
- 模擬生產環境部署所有的微服務,然後進行測試
- 優點
- 測試結果可信度高
- 缺點
- 測試成本太大,裝一整套環境耗時,耗力,耗機器
- 優點
- Mock其他微服務做端到端的測試
- 優點
- 不用裝整套產品了,測的也方便快捷
- 缺點
- 需要寫很多服務的Mock,要維護一大堆不同版本用途的simulate(模擬器),同樣耗時耗力
- 優點
- 模擬生產環境部署所有的微服務,然後進行測試
- Spring Cloud Contrct解決思路
- 每個服務都生產可被驗證的 Stub Runner,通過WireMock呼叫,服務雙方簽訂契約,一方變化就更新自己的Stub,並且測對方的Stub。Stub其實只提供了資料,也就是契約,可以很輕量的模擬服務的請求返回。而Mock可在Stub的基礎上增加驗證
契約測試流程
- 服務提供者
- 編寫契約,可以用Groovy DSL 指令碼也可以用 YAML檔案
- 編寫測試基類用於構建過程中外掛自動生成測試用例
- 生成的測試用例會自動執行,這時如果我麼提供的服務不能滿足契約中的規則就會失敗
- 提供者不斷完善功能直到服務滿足契約要求
- 釋出Jar包,同時將Stub字尾的jar一同釋出
- 服務消費者
- 對需要依賴外部服務的介面編寫測試用例
- 通過註解指定需要依賴服務的Stub jar包
- 驗證外部服務沒有問題
簡單案例
服務提供者
模擬一個股票價格查詢的服務
專案地址
springcloud-contract-provider-rest
專案結構
專案依賴
<dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-contract-verifier</artifactId> <scope>test</scope> </dependency> <build> <plugins> <plugin> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-contract-maven-plugin</artifactId> <version>2.2.1.RELEASE</version> <extensions>true</extensions> <configuration> <!--用於構建過程中外掛自動生成測試用例的基類--> <baseClassForTests> com.example.springcloudcontractproviderrest.RestBaseCase </baseClassForTests> </configuration> </plugin> <plugin> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-maven-plugin</artifactId> </plugin> </plugins> </build>
編寫契約
既然是消費者驅動契約,我麼首先需要制定契約,這裡為了方便假設查詢貴州茅臺的股價返回值是固定的999,也可以通過正則等方式去限制返回值
Contract.make {
description "query by id should return stock(id,price)"
request {
method GET()
url value {
// 消費者使用時請求任何 /stock/price/數字 都會被轉為 /stock/price/600519
consumer regex('/stock/price/\\d+')
producer "/stock/price/600519"
}
}
response {
status OK()
headers {
contentType applicationJson()
}
// 提供給消費者的預設返回
body([
id : 600519,
price: 999
])
// 服務端在測試過程中,body需要滿足的規則
bodyMatchers {
jsonPath '$.id', byRegex(number())
jsonPath '$.price', byRegex(number())
}
}
}
測試基類
主要是載入環境,然後由於不是真實環境模擬了資料庫查詢
@SpringBootTest
@RunWith(SpringRunner.class)
public class RestBaseCase {
@Autowired
private StockController stockController;
@MockBean
private StockRepository stockRepository;
@Before
public void setup() {
init();
RestAssuredMockMvc.standaloneSetup(stockController);
}
private void init() {
Mockito.when(stockRepository.getStockById(600519)).thenReturn(new StockDTO(600519, "貴州茅臺", 999L, "SH"));
}
}
實現服務並測試
實現我們的服務功能,具體程式碼邏輯可以在專案地址中檢視,然後測試看是否符合契約
mvn clean test
可以在生成(target)目錄中找到 generated-test-sources 這個目錄,外掛為我們自動生成並且執行的case就在其中
public class StockTest extends RestBaseCase {
@Test
public void validate_shoudReturnStockIdAndPrice() throws Exception {
// given:
MockMvcRequestSpecification request = given();
// when:
ResponseOptions response = given().spec(request)
.get("/stock/price/600519");
// then:
assertThat(response.statusCode()).isEqualTo(200);
assertThat(response.header("Content-Type")).matches("application/json.*");
// and:
DocumentContext parsedJson = JsonPath.parse(response.getBody().asString());
// and:
assertThat(parsedJson.read("$.id", String.class)).matches("-?(\\d*\\.\\d+|\\d+)");
assertThat(parsedJson.read("$.price", String.class)).matches("-?(\\d*\\.\\d+|\\d+)");
}
}
釋出
如果一切順利就可以deploy了
服務消費者
模擬查詢個人資產的服務,需要遠端呼叫股票價格查詢服務,計算總資產
專案地址
springcloud-contract-consumer-rest
專案結構
驗證服務
編寫測試用例驗證服務
@SpringBootTest
@RunWith(SpringRunner.class)
@AutoConfigureStubRunner(
ids = {"com.example:springcloud-contract-provider-rest:+:stubs:8880"},
stubsMode = StubRunnerProperties.StubsMode.LOCAL
)
public class StockApiTest {
@Autowired
private StockApi stockApi;
@Test
public void testStockApi() throws IOException {
StockPriceDTO stockPrice = stockApi.getStockPrice(600519).execute().body();
BDDAssertions.then(stockPrice.getId()).isEqualTo(600519);
BDDAssertions.then(stockPrice.getPrice()).isEqualTo(999);
}
}