使用Spring Cloud合約進行消費者驅動的合同測試
使用Spring Cloud合約進行消費者驅動的合同測試
網址:https://specto.io/blog/2016/11/16/spring-cloud-contract/
湯米·斯德爾 2016年11月16日隨著系統拓撲的增長,測試微服務成為一項艱巨的任務。當微服務器鏈接在一起以實現業務功能時,通過編寫集成測試來驗證他們正在一起工作是很有挑戰性的。如果您沿著這條路徑走下去,您將需要擁有所有的應用程序,基礎資源(如數據庫,S3存儲區)和第三方API在已知狀態下連接並運行,以確保“服務A”可以通話到“服務B”。
事實上這是麻煩的設置不是唯一的問題在這裏。你的測試有時可能會神秘地失敗。“有時”可能意味著各種薄片,如網絡超時,第三方API速率限制,或僅僅是從以前的測試運行留下的數據。
幸運的是,可以使用像Hoverfly或WireMock這樣的服務虛擬化工具來嘲笑依賴的服務。測試服務A和B之間的集成成為服務A的隔離組件測試,其中嵌入了一個服務B。
然而,這又造成了另一個困境:您如何保證服務B的存根始終跟蹤實際服務的更改?想象一下,在服務B工作的開發人員悄悄地推出一個API更新,使服務A使用的存根無效,並且連續的部署管道為基於服務A通過測試的發布提供了綠燈。這最終會導致生產中的消防。
也許現在是考慮兩個服務之間的協議的時候了。服務A(作為消費者)創建一個服務B(作為制作人)必須遵守的合同。這種合同作為服務之間的隱形粘合劑 - 盡管它們分別獨立於代碼庫並運行在不同的JVM上。
這被稱為消費者驅動合同(CDC)測試,這是在分布式架構中測試服務虛擬化的有效方式。在本博客中,我將介紹Spring Cloud Contract:基於JVM的項目的CDC框架,特別是使用Spring Boot的項目。
一個簡單的用例
在這個演示中,我們有兩個微服務器:訂閱和帳戶。我們需要為訂閱服務添加新功能,以便對朋友的帳戶進行訂閱是免費的。要查明帳戶是否標記為“朋友”,訂閱服務需要使用帳戶服務的“按ID獲取帳戶”API。您可以在GitHub上找到此博客的源代碼。
你需要什麽
- Java的
- 彈簧啟動(1.4.1.RELEASE)
- Spring Cloud合約(1.0.1.RELEASE)
- 畢業(3.1)
- Maven倉庫
在Spring Cloud Contract項目網站上可以找到一個示例Gradle構建文件。
關鍵依賴關系是spring-cloud-starter-contract-verifier
生產者自動生成API驗證測試,spring-cloud-starter-stub-runner
消費者自動配置存根服務器。
分步工作流程
CDC測試類似於架構/ API級別的TDD,因此共享類似的工作流程。
添加測試: 在消費者方面,我們首先編寫新功能的功能測試,並實現與生產者端點通信的網關。
@RunWith(SpringRunner.class)
@SpringBootTest
public class SubscriptionTest {
@Autowired
private SubscriptionService service;
@Test
public void shouldGiveFreeSubscriptionForFriends() throws Exception {
// given:
String accountId = "12345";
Subscription subscription = new Subscription(accountId, MONTHLY);
// when:
Invoice invoice = service.createInvoice(subscription);
// then:
assertThat(invoice.getPaymentDue()).isEqualTo(0);
assertThat(invoice.getClientEmail()).isNotEmpty();
}
}
運行所有測試: 顯然它們失敗了
org.springframework.web.client.ResourceAccessException: I/O error on GET request for "http://localhost:8082/account/12345": Connection refused.
編寫一些代碼:缺少的實現不再在同一個代碼庫中。我們需要查看生產者的存儲庫,並根據消費者期望生產者的行為方式,使用Spring Cloud Contract Groovy DSL添加合同。該文件應位於src/test/resources/contracts/
的spring-cloud-contract-gradle-plugin
發現。
package contracts
import org.springframework.cloud.contract.spec.Contract
Contract.make {
request {
method ‘GET‘
url value(consumer(regex(‘/account/[0-9]{5}‘)), producer(‘/account/12345‘))
}
response {
status 200
body([
type: ‘friends‘,
email: ‘[email protected]‘
])
headers {
header(‘Content-Type‘: value(
producer(regex(‘application/json.*‘)),
consumer(‘application/json‘)
))
}
}
}
合同包括請求和響應對。它顯示了使用URL路徑的動態值的示例。使用值(consumer(...),producer(...))輔助方法,可以設置匹配器或具體值。在這種情況下,在消費者端(生成的存根)中添加正則表達式,以便將請求與任何帳戶ID進行匹配,並為生成的測試設置特定的帳戶ID,使其與生產者的已知狀態相匹配。
再次,生產者方面遵循某種TDD模式。
- 運行gradle
generateContractTests
在生成文件夾中生成測試:
public class ContractVerifierTest extends ContractVerifierBase {
@Test
public void validate_shouldReturnFriendsAccount() throws Exception {
// given:
MockMvcRequestSpecification request = given();
// when:
ResponseOptions response = given().spec(request)
.get("/account/12345");
// then:
assertThat(response.statusCode()).isEqualTo(200);
assertThat(response.header("Content-Type")).matches("application/json.*");
// and:
DocumentContext parsedJson = JsonPath.parse(response.getBody().asString());
assertThatJson(parsedJson).field("type").isEqualTo("friends");
assertThatJson(parsedJson).field("email").isEqualTo("[[email protected]](/cdn-cgi/l/email-protection)<script data-cfhash="f9e31" type="text/javascript">/* <![CDATA[ */!function(t,e,r,n,c,a,p){try{t=document.currentScript||function(){for(t=document.getElementsByTagName(‘script‘),e=t.length;e--;)if(t[e].getAttribute(‘data-cfhash‘))return t[e]}();if(t&&(c=t.previousSibling)){p=t.parentNode;if(a=c.getAttribute(‘data-cfemail‘)){for(e=‘‘,r=‘0x‘+a.substr(0,2)|0,n=2;a.length-n;n+=2)e+=‘%‘+(‘0‘+(‘0x‘+a.substr(n,2)^r).toString(16)).slice(-2);p.replaceChild(document.createTextNode(decodeURIComponent(e)),c)}p.removeChild(t)}}catch(u){}}()/* ]]> */</script>");
}
}
生成的測試依賴於RestAssuredMockMvc來執行HTTP請求。為了使其可運行,我們還實現了引導測試環境的基類,如有必要,嘲笑依賴關系。
- 在生產者方面,我們實施ContractVerifierBase類來加載Web上下文並設置RestAssuredMockMvc
@Ignore
@RunWith(SpringRunner.class)
@SpringBootTest(classes = AccountServiceApplication.class)
public class ContractVerifierBase {
@Autowired
private WebApplicationContext context;
@Before
public void setUp() throws Exception {
RestAssuredMockMvc.webAppContextSetup(context);
}
}
我們還需要在build.gradle文件中進行以下設置來告訴spring-cloud-contract
插件找到ContractVerifierBase類:
contracts {
packageWithBaseClasses = ‘com.demo.account.contracts‘
}
- 現在我們可以實現生產者的新端點來通過測試。
@RequestMapping(method = RequestMethod.GET, value = "/account/{id}")
public Account getAccount(@PathVariable String id) {
return accountService.getById(id);
}
- 通過合同驗證者考試後,我們有一個令人滿意的合同!運行gradle clean build install將生成並發布WireMock映射作為stubs.jar文件到本地的maven倉庫。您可以檢查文件
build/mappings
夾中的WireMock映射文件:
{
"uuid" : "79ab1fad-984f-4a6c-8b24-88deeb8cb503",
"request" : {
"urlPattern" : "/account/[0-9]{5}",
"method" : "GET"
},
"response" : {
"status" : 200,
"body" : "{\"type\":\"friends\",\"email\":\"[[email protected]](/cdn-cgi/l/email-protection)<script data-cfhash="f9e31" type="text/javascript">/* <![CDATA[ */!function(t,e,r,n,c,a,p){try{t=document.currentScript||function(){for(t=document.getElementsByTagName(‘script‘),e=t.length;e--;)if(t[e].getAttribute(‘data-cfhash‘))return t[e]}();if(t&&(c=t.previousSibling)){p=t.parentNode;if(a=c.getAttribute(‘data-cfemail‘)){for(e=‘‘,r=‘0x‘+a.substr(0,2)|0,n=2;a.length-n;n+=2)e+=‘%‘+(‘0‘+(‘0x‘+a.substr(n,2)^r).toString(16)).slice(-2);p.replaceChild(document.createTextNode(decodeURIComponent(e)),c)}p.removeChild(t)}}catch(u){}}()/* ]]> */</script>\"}",
"headers" : {
"Content-Type" : "application/json"
}
}
}
再次運行測試:最後,在消費者端,我們只是添加
@AutoConfigureStubRunner(ids = "com.demo:account-service:+:stubs:8082", workOffline = true)
到需要生產者存根的測試。這個存根運行程序將拉取並解壓縮最新的存根jar文件(當我們將版本設置為“+”符號)時,在端口8082上啟動WireMock服務器並註冊存根映射。
現在我們有生產者存根運行,測試應該通過。
在CI / CD環境中工作
到目前為止,我們只看到如何在本地機器上開發CDC的新功能。與包/構建管道集成需要更多的調整:
- 默認情況下,生產者的Gradle構建任務將生成並運行合同驗證程序測試。它只需要通過添加
uploadArchives
到其Gradle任務將存根jar發布到遠程存儲庫。 - 該消費者需要配置StubRunner解決存根。這可以通過設置Spring Boot應用程序屬性來實現:
stubrunner:
ids: com.demo:account-service:+:stubs:8082
repositoryRoot: https://demo.jfrog.io/demo/libs-snapshot</pre>
結論
消費者驅動的合同(CDC)為我們提供了快速的反饋,以驗證微服務之間的集成,以及在獨立部署時有更多的信心,而不用擔心對其他服務引入突破性的更改。
Spring Cloud合同為CDC測試提供了一個簡單的工作流程,並以最小的編碼。優點是您可以使用靜態類型的Groovy DSL編寫合同,以自動生成生成器驗證測試和存根映射文件。缺點是手工制作合同文件在某些??情況下可能是詛咒。例如,服務交互可能具有復雜的有效載荷或請求主體,並且需要花費大量的精力才能使其正確。
還有一些註意事項:
- 您的CI環境應該與maven存儲庫集成,以共享存根jar文件。Spring Cloud Contract在寫作時尚未支持從密碼保護的存儲庫中解析存根。
- 僅支持基於JVM的項目。如果您正在為Javascript,Go,.Net等尋找CDC框架, Pact框架是一個更好的選擇。
- 作為一個新興項目,您將期待看到一些出現問題。
如果您對此演示的源代碼感興趣,可以在GitHub上找到。
使用Spring Cloud合約進行消費者驅動的合同測試