微服務測試之接口測試和契約測試
日常開發(fā)過程中,項目的接口通常由服務提供方約定和提供,微服務模式下接口被多個消費者調(diào)用更是常態(tài),那么提供方接口的變更如何快速、高效、無遺漏的通知給消費者呢?另外,當一個service同時被多個使用者調(diào)用,如何保證對service的修改可以讓其它所有使用者造成的影響都能被感知到?這些問題契約測試可以給你答案。另外,微服務模式下,接口測試是非常重要的測試手段,它在實際的項目中幫助驗證微服務之間的協(xié)同和交互,大幅降低測試成本和提高測試效率方面提供了很大幫助,可以說接口測試是業(yè)務功能測試前置的助推器。因此,這里對這兩種測試手段進行介紹。
接口測試和契約測試所處的階段
在實際的工作中,結(jié)合隨行付的實際情況我們對自動化測試金字塔原理進行了定制,加入契約自動化測試內(nèi)容,形成如下新版自動化測試金字塔結(jié)構(gòu)。

由圖可知,一個項目的測試過程,從項目推進的維度,首先進行單元測試,其次接口自動化測試、契約測試,最后UI自動化測試和手工測試。
微服務模式下如何開展接口測試
接口測試屬于集成測試范疇,他是單元測試的擴展和延續(xù)。它主要的關(guān)注點是內(nèi)部接口功能實現(xiàn)是否完整,比如說內(nèi)部邏輯是不是正常,異常處理是不是正確。它是單元測試和契約測試的過渡階段,它是項目單個代碼邏輯最終串聯(lián)形成有價值業(yè)務邏輯的橋梁。因此,其作用舉足輕重。隨行付開展接口測試,采用的思路是規(guī)范和方法先行,其次是工具選擇、人員培訓,然后是實施和過程優(yōu)化,最后常態(tài)化持續(xù)提效和質(zhì)量保證的過程。
接口測試規(guī)范化要求
接口測試的質(zhì)量保證和測試過程的流程化需要通過規(guī)范和方法進行指導和約束。我們定制了如下要求(部分內(nèi)容):
- 需求存在新增接口或者接口變更時,要求進行新增接口測試案例的編寫或存量接口案例的維護;
- 需求涉及到的存量接口需要進行回歸測試;
- 接口測試覆蓋率要求達到100%;
- 需求測試結(jié)束前至少進行一輪接口回歸測試,且回歸通過率達到100%
測試流程規(guī)范涉及從需求提出、腳本編寫、執(zhí)行到測試報告的各個過程。
- 接口文檔。接口文檔是接口測試案例設計的依據(jù),接口文檔的全面性和準確性決定了接口測試范圍的全面性和接口測試結(jié)果的正確性、有效性。隨行付采用swagger進行接口文檔管理。
- 接口用例設計。根據(jù)接口文檔設計接口測試案例,接口測試案例通過接口測試平臺進行編寫,且需要滿足不重不漏原則。
- 接口用例評審。根據(jù)項目實際情況,接口測試案例編寫完成后,需組織相關(guān)干系人進行案例評審,記錄并發(fā)送會議紀要。
- 接口用例執(zhí)行。需求測試結(jié)束前接口測試案例至少在測試環(huán)境中執(zhí)行了一次回歸測試,要求案例執(zhí)行通過率達到100%
- 缺陷管理和測試報告。
- 腳本納入回歸體系,定時回歸,持續(xù)保障接口的質(zhì)量,以及接口質(zhì)量的持續(xù)和及時反饋。
腳本命名規(guī)范和編寫規(guī)范如下(部分內(nèi)容):
- 接口命名要求:采用“接口名稱_接口描述”進行命名,用于定義唯一接口。
- 方法命名要求:采用“方法名_描述”進行命名,用于定義唯一方法。
- 案例命名要求:采用“序號_場景操作_期望結(jié)果”進行命名,用于定義唯一案例。
- 【強制】每個接口測試案例都必須包含至少一個斷言;
- 【強制】對于json格式的報文,接口入?yún)⒑蛿嘌皂憫念A期值需要使用嚴格的json格式;
- 【強制】swagger腳本導入到接口測試平臺時,需要導入.json文件,且文件內(nèi)容為無BOM的UTF-8編碼;
- 【強制】數(shù)據(jù)初始化和斷言的sql必須帶where條件,且能唯一定位到期望的數(shù)據(jù);
- 【強制】數(shù)據(jù)庫回退的sql必須帶where條件,且能唯一定位到需要回退的數(shù)據(jù);
- 【強制】影響公共表(如:T_BAP_CDE_BNK表)或者其他組數(shù)據(jù)庫表(如:資金組)的sql,在數(shù)據(jù)初始化、回退、接口影響的數(shù)據(jù)回退、斷言回退時必須嚴格審查;
- 【強制】數(shù)據(jù)庫斷言sql中的where條件的主鍵組合需要放到前面,用于斷言失敗時快速定位問題;
接口測試用例設計要求
為了保證接口的質(zhì)量,需要進行全面的接口測試,因此在涉及接口測試用例時需要依賴方法,因此我們總結(jié)了接口測試用例的設計要求,如下圖所示。

接口測試工具
接口測試過程提效、測試過程自動化需要依賴自動化測試工具,武器不好很難打勝仗。經(jīng)過調(diào)研,市面上很多接口自動化測試工具均無法滿足所有的測試要求,因此我們自研了接口自動化測試平臺。自動化測試平臺具有如下能力:
- 案例自動生成。http/https接口案例自動化生成和導入。
- 測試過程集中可視化管理。通過將自動化測試過程web化實現(xiàn)了自動化測試計劃、自動化測試用例編寫、自動化測試用例執(zhí)行、自動化測試用例管理和自動化測試報告管理各個過程的可視化。
- 模擬性能場景。自動化測試實現(xiàn)了通過接口案例模擬性能測試場景的能力。通過使用平臺中提供的接口案例,進行并行執(zhí)行模擬性能場景。
- 多協(xié)議多報文類型支持。支持http/https協(xié)議、dubbo協(xié)議、socket協(xié)議、rabbitMQ協(xié)議等協(xié)議的自動化測試,并支持對協(xié)議的擴展。同時支持xml、json、sop、8583等多種報文類型以及報文類型的擴展。
- 測試資產(chǎn)有效積累。
- 自動化調(diào)度執(zhí)行和郵件發(fā)送。自動化測試執(zhí)行通過定時對案例進行調(diào)度執(zhí)行,可對指定的構(gòu)建版本對應的案例進行自動化的分批、定時調(diào)度執(zhí)行并郵件發(fā)送測試報告。
- 系統(tǒng)質(zhì)量的可視化反饋。通過對自動化案例的執(zhí)行結(jié)果統(tǒng)計,分析出系統(tǒng)的質(zhì)量趨勢,做到系統(tǒng)質(zhì)量的持續(xù)化反饋。通過根因分析,統(tǒng)計系統(tǒng)問題的根本原因的比例,更有針對性的解決質(zhì)量問題。

通過接口測試持續(xù)運行1年多的持續(xù)運營,隨行付核心業(yè)務接口基本實現(xiàn)接口測試用例全覆蓋,且均納入到定期回歸過程,持續(xù)為接口的質(zhì)量保駕護航。
微服務模式下如何開展契約測試
契約測試的價值
契約測試分兩種類型,一種是消費者驅(qū)動,一種是提供者驅(qū)動。其中最常用的,是消費者驅(qū)動的契約測試(Consumer-Driven Contract Test,簡稱 CDC)。核心思想是從消費者業(yè)務實現(xiàn)的角度出發(fā),由消費者端定義需要的數(shù)據(jù)格式以及交互細節(jié),生成一份契約文件。然后生產(chǎn)者根據(jù)契約文件來實現(xiàn)自己的邏輯,并在持續(xù)集成環(huán)境中持續(xù)驗證該實現(xiàn)結(jié)果是否正確。對于基于Restful API的微服務來說,它的契約就是指 API 的請求和響應的規(guī)則。
如下圖所示:

- 對于請求,包括請求 URL 及參數(shù),請求頭,請求內(nèi)容等;
- 對于響應,包括狀態(tài)碼,響應頭,響應內(nèi)容等。
- 對于元數(shù)據(jù),指對消費者與提供者間一次協(xié)作過程的描述。譬如消費者/提供者的名稱、上下文及場景描述等。
那么契約測試能給微服務帶來什么價值呢?文章開頭已經(jīng)提到了契約測試的一部分價值,即接口變更快速通知,servise修改的快速感知。除此之外,它還帶來下列價值:
- 降低服務集成的難度。把服務集成這個過程分解成了更細的單元測試和接口測試,它從消費者的需求為出發(fā)點,把消費者的需求作為測試用例驅(qū)動實現(xiàn)一份契約,然后驗證提供者端的功能。
- 開發(fā)并行,提高開發(fā)效率。契約隔離了消費者和提供者,雙方可以并行開展工作,開發(fā)過程中就利用契約進行預集成測試,不用等到聯(lián)調(diào)再來集成調(diào)通接口,一旦成熟,在保證質(zhì)量的前提下,聯(lián)調(diào)的成本可以減低到幾乎為0。
- 確保變動的安全性和準確性。只要有變化,契約測試即可第一時間發(fā)現(xiàn),保證安全和對接的準確性。
- 作為Mock server為消費者提供Mock服務。集成測試為服務者提供

微服務下如何開展契約測試
隨行付采用在Spring Cloud Contract開展契約測試。其核心流程包括2步:
- 對消費者的業(yè)務邏輯進行驗證時,先對其期望的響應做模擬提供者(Mock);并將請求(消費者)-響應(基于模擬提供者)的協(xié)作過程,記錄為契約;
- 通過契約,對提供者進行回放,保證提供者所提供的內(nèi)容滿足消費者的期望。
下面用一個簡單的例子說明設計契約測試的方法。這個例子中,一個微服務提供了一個包含三個字段(“IP”、“name”和“password”)的資源,供三個消費者微服務使用。這三個微服務分別使用這個資源中的不同部分。消費者 A 使用其中的 IP 和 name 這兩個字段。因此,測試腳本中將只驗證來自提供者的資源中是否正確包含這兩個字段,而不需要驗證 password 字段。消費者 B 使用 IP 和 password 字段,而不需要驗證 name 字段。消費者 C 則需要確認資源中包含了所有這三個字段?,F(xiàn)在,如果提供者需要將 name 分為姓(first name)和名(last name),那么就需要去掉原有的 name 字段,加入新的 first name 字段和 last name 字段。這時執(zhí)行契約測試,就會發(fā)現(xiàn)消費者 A 和 C 的測試用例就會失敗。測試用例 B 則不受影響。這意味著消費者 A 和 C 服務的代碼需要修改,以兼容更新之后的提供者。修改之后,還需要對契約內(nèi)容進行更新。

下面以一個例子介紹如何使用Spring Cloud Contract開展契約測試的。
-
- Spring Cloud Contract契約是用基于Groovy的DSL定義的,下面是一個契約測試的代碼段
package contracts
org.springframework.cloud.contract.spec.Contract.make {
request {
method 'PUT'
url '/fraudcheck'
body([
"client.id": $(regex('[0-9]{10}')),
loanAmount: 99999
])
headers {
contentType('application/json')
}
}
response {
status OK()
body([
fraudCheckStatus: "FRAUD",
"rejection.reason": "Amount too high"
])
headers {
contentType('application/json')
}
}
}
-
- 服務方-server (HTTP) / producer (Messaging) 端增加Spring Cloud Contract Verifier的gralde插件,它用于解析契約文件生成測試。生成測試的命令。
./gradlew generateContractTests
下面是自動生成的測試腳本
@Test
public void validate_shouldMarkClientAsFraud() throws Exception {
//given:
MockMvcRequestSpecification request = given()
.header("Content-Type", "application/vnd.fraud.v1+json")
.body("{"client.id":"1234567890","loanAmount":99999}");
//when:
ResponseOptions response = given().spec(request)
.put("/fraudcheck");
//then:
assertThat(response.statusCode()).isEqualTo(200);
assertThat(response.header("Content-Type")).matches("application/vnd.fraud.v1.json.*");
//and:
DocumentContext parsedJson = JsonPath.parse(response.getBody().asString());
assertThatJson(parsedJson).field("['fraudCheckStatus']").matches("[A-Z]{5}");
assertThatJson(parsedJson).field("['rejection.reason']").isEqualTo("Amount too high");
}
這個一個標準的JUnit測試,用RestAssured來啟動Spring的webApplicationContext。
@Before
public void setup() {
RestAssuredMockMvc.webAppContextSetup(webApplicationContext);
}
-
- 調(diào)用方-在服務方通過命令生成Stub服務的Jar包
./gradlew verifierStubsJar
Spring Cloud Contract Stub Runner在集成測試中通過運行WireMock實例或者消息路由模擬真實的服務。 因此在運行之前,需要將依賴加入到gralde中,當然可以把他加到私服倉庫中。
spring-cloud-starter-contract-stub-runner
對于調(diào)用方,Spring Cloud Contract提供了Stub Runner來簡化Stub的使用。現(xiàn)在可以使用@AutoConfigureStubRunner注解.為了Spring Cloud Contract Stub Runner運行stubs注解中增加了group-id和artifact-id,舉例如下:
@RunWith(SpringRunner.class)
@SpringBootTest(webEnvironment=WebEnvironment.NONE)
@AutoConfigureStubRunner(ids = {"cn.vbill.service:test-client-stubs:1.5.0-SNAPSHOT:stubs:6565"},
stubsMode = StubRunnerProperties.StubsMode.LOCAL)
public class LoanApplicationServiceTests {
......
}
注解AutoConfigureStubRunner,里面設置了下載Stub Jar包的私庫地址以及包的完整 ID,最后的6565就是指定Stub運行的本地端口。測試的時候訪問Stub端口,就會根據(jù)契約返回內(nèi)容。
由于隨行付微服務是基于spring cloud技術(shù)棧,因此采用spring cloud contract進行微服務下的契約測試使得測試過程更流暢,更順利,同時Spring Cloud Contract和SpringBoot以及 Junit的集成更簡單方便。相信隨著spring cloud contract版本的優(yōu)化,契約測試可以做的更好。
總結(jié)
本篇分別從微服務模式下如何開展接口自動化測試,契約測試的價值以及如何開展契約測試角度進行了介紹,微服務模式下,服務間的調(diào)用關(guān)系復雜,接口測試和契約測試是保證服務提高質(zhì)量的重要手段,因此要充分利用。
