詳解Spring云端微服務(wù)的組件測試
譯文軟件開發(fā)教父--Martin Fowler在其題為《??微服務(wù)架構(gòu)的測試策略??》講演中,詳細詮釋了測試不同級別的微服務(wù)的概念,其中就提到了如下圖所示的“測試金字塔”模型。該模型從下到上分別為:單元、集成、組件、端到端和探索。
而不可否認的是,隨著業(yè)界廣泛采用云端微服務(wù),我們在得益于處理多個可獨立部署的組件的同時,需要提高微服務(wù)應(yīng)用的測試級別,并按需增加測試策略的復(fù)雜性。下面,我將從使用者的角度出發(fā),以一個Spring Cloud微服務(wù)為例,深入探究各種服務(wù)組件的相關(guān)測試。
服務(wù)
我們的Spring Boot微服務(wù)示例會具有如下特征:
- 將啟用??Spring Cloud Netflix??。它會使用Spring Boot應(yīng)用的Netflix OSS集成,來執(zhí)行服務(wù)注冊與發(fā)現(xiàn)、分布式外部配置(Spring Cloud Config)、以及客戶端的負載平衡
- 將與關(guān)系型數(shù)據(jù)庫(如PostgreSQL)相集成
- 將能調(diào)用另一個(內(nèi)部)微服務(wù)
- 將調(diào)用第三方(外部)Web服務(wù)
- 將啟用Spring Security,以充當(dāng)OAuth2的資源服務(wù)器
- 將被“隱藏”在API網(wǎng)關(guān)服務(wù)器,例如??Spring Cloud Gateway??的后面
我們將通過Java 11、Apache Maven、Docker、以及一組協(xié)作庫,“盡早地”在CI/CD管道中,進行單獨的服務(wù)測試,而無需實際部署或占用其他服務(wù)、數(shù)據(jù)庫、甚至是完整的測試環(huán)境資源。同時,您可以通過鏈接--https://github.com/kmandalas/spring-cloud-component-tests,在GitHub上獲取該示例的所有代碼。
該示例中的“訂單跟蹤”微服務(wù)是由一個Spring Controller、Service和Repository所組成。它公開了兩個端點:
- GET/api/orders/{trackingNumber}/status:它通過給定的跟蹤號,執(zhí)行數(shù)據(jù)庫查詢,來獲取相關(guān)訂單;然后調(diào)用FulfillmentService的內(nèi)部服務(wù),來確定交付的狀態(tài);進而讓最終外部服務(wù)根據(jù)狀態(tài),調(diào)用位置服務(wù)來實現(xiàn)定位。這是一個帶有有效的JWT、且受保護的API調(diào)用。
- GET/api/orders:通過查詢數(shù)據(jù)庫,以列出所有訂單。這是一個受到額外授權(quán)限制的、且受保護的API調(diào)用。它僅適用于具有“back-office”角色的用戶。
組件測試
OrderControllerTest.java類將針對API提供的多種方法,來封裝組件測試。例如,我們可以選用包括:??Maven插件??、??JUnit功能??、??Spring Boot測試切片??和分類單元測試、集成測試、組件測試、合同測試等方法。當(dāng)然,并非所有的測試類別都需要在CI/CD管道中被執(zhí)行(或重新執(zhí)行)。鑒于該示例過于簡單,我強烈建議您實施適當(dāng)?shù)姆诸悺?/p>
在??/src/test/resources/application.yml??中,我們針對屬性的測試配置如下:
YAML
server
port0
spring
application
name order-service-test
cloud
service-registry
auto-registration
enabledfalse
loadbalancer
ribbon
enabledfalse
config
enabledfalse
jpa
show-sqltrue
eureka
client
enabledfalse
service-url
registerWithEurekafalse
okta
oauth2
issuer https //kmandalas/oauth2/default
location-service
url http //localhost 9999/v1/track/
在上述代碼段所示中,我們禁用spring.cloud.config、eureka.client和spring.cloud.service-registry.auto-registration的原因在于,方便孤立地測試微服務(wù)。因此,既不會有Spring Cloud Config服務(wù)器在啟動時,為OrderService的配置屬性提供服務(wù);也不會有Eureka服務(wù)器提供注冊,并能夠使用它來按需調(diào)用FulfillmentService的動態(tài)服務(wù)發(fā)現(xiàn)。
數(shù)據(jù)庫
當(dāng)出于測試目的而必須與數(shù)據(jù)庫(關(guān)系型或NoSQL)集成時,我們通常有如下三種選擇:
- 使用嵌入式或內(nèi)存中(in-memory)方案,例如:H2,https://www.h2database.com/
- 使用一個能在測試期間可供訪問的真實數(shù)據(jù)庫
- 使用與生產(chǎn)數(shù)據(jù)庫接近甚至相同的臨時數(shù)據(jù)庫
不同的選項所涉及到的測試資源,將會不盡相同。
- 如果采用第一種方法,將H2進行集成和組件測試,那么由于生產(chǎn)環(huán)境的數(shù)據(jù)庫很可能與H2不同,因此您將不得不維護各種獨立的DDL和DML腳本。此外,您也可能會用到原生查詢、或其他特定于某個數(shù)據(jù)庫的功能。
- 如果您需要進行端到端或性能測試的話,那么就應(yīng)該部署真實的數(shù)據(jù)庫,并在測試環(huán)境中啟動并運行它。對此,現(xiàn)代化的IaC(infrastructure as code,基礎(chǔ)設(shè)施即代碼)工具、以及詳盡的??測試數(shù)據(jù)管理??,將可以為項目按需提供靈活性。
- 在本測試示例中,我們將使用第三種方法,利用??testcontainers???和??Flyway??,實現(xiàn)與Spring Boot的配合,而數(shù)據(jù)庫才采用PostgreSQL。在testcontainers的幫助下,我們將在測試的初始化階段,創(chuàng)建一個臨時的dockerized數(shù)據(jù)庫實例。而Flyway將會在這個臨時模式(schema)上觸發(fā)???遷移腳本??(DDL/DML),以便我們的代碼將透明地、針對該臨時模式運行。而在測試完成時,我們會處理掉這個dockerized數(shù)據(jù)庫。
可見,我們實際上只需要OrderControllerTest類上的@Testcontainers注釋,以及如下的靜態(tài)聲明:
Java
static PostgreSQLContainer database = new PostgreSQLContainer("postgres:12")
.withDatabaseName("tutorial")
.withUsername("kmandalas")
.withPassword("dzone2022");
?
static void setDatasourceProperties(DynamicPropertyRegistry propertyRegistry) {
propertyRegistry.add("spring.datasource.url", database::getJdbcUrl);
propertyRegistry.add("spring.datasource.password", database::getPassword);
propertyRegistry.add("spring.datasource.username", database::getUsername);
}
內(nèi)部服務(wù)調(diào)用
我們將使用??Spring Cloud OpenFeign??來調(diào)用FulfillmentService,它是另一個“內(nèi)部”的Spring Cloud微服務(wù),可以被注冊到Eureka上。在正常執(zhí)行的情況下,后臺的feign客戶端能夠通過名稱定位目標(biāo)服務(wù)實例,實現(xiàn)客戶端的負載均衡(如果發(fā)現(xiàn)了多個實例的話)。
在我們的測試中,在沒有Eureka(或者是??Consul??等其他發(fā)現(xiàn)機制)的情況下,我們需要通過如下兩個方面,盡可能真實地模擬此類集成:
- 通過??WireMock??啟動一個模擬服務(wù)器。該服務(wù)器能夠根據(jù)URL的不同模式,來截獲請求,并回復(fù)由我們提供的模擬響應(yīng)。
- 使用@TestConfiguration來模擬各種FulfillmentService實例的發(fā)現(xiàn),并將其指向WireMock服務(wù)器的URI。您可以通過鏈接--https://github.com/kmandalas/spring-cloud-component-tests/blob/50241126932fce3e9cfc6351291af5857f77806a/src/test/java/gr/kmandalas/dzone/OrderControllerTest.java#L55,查看到此類測試配置。
當(dāng)然,您也可以使用??Hoverfly??作為嵌入式模擬服務(wù)器。在本示例里,我們通過如下依賴項設(shè)置,來引入WireMock:
XML
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-contract-stub-runner</artifactId>
<scope>test</scope>
</dependency>
通過spring-cloud-starter-contract-stub-runner,WireMock在Spring Boot應(yīng)用測試套件中的引導(dǎo)被簡化了許多,同時這對于??契約測試??(contract tests)也是非常實用的。請通過查看Spring Cloud Contract WireMock的鏈接--https://docs.spring.io/spring-cloud-contract/docs/current/reference/html/project-features.html#features-wiremock,了解更多相關(guān)信息。
有了上面的基礎(chǔ),我們只需要使用@AutoConfigureWireMock去注釋測試類,并在測試資源目錄下的??JSON文件??中定義各種WireMock映射即可。
外部服務(wù)調(diào)用
在集成的過程中,為了能夠調(diào)用某些外部的(第三方)服務(wù),我們?nèi)匀恍枰蕾囉行У腤ireMock映射(畢竟能夠提供的響應(yīng)多多益善),以便在??application.yml??中定義測試URL資源。下面是一個簡單的示例:
YAML
location-service
url http //localhost 9999/v1/track/
我們在外部服務(wù)的URL端點路徑處,提供了WireMock嵌入式服務(wù)器運行的主機和端口。端口號雖然不必經(jīng)過硬編碼,但是可以被定義為動態(tài)的,以便在CI/CD管道中并行運行多個組件測試,且不會發(fā)生端口沖突。
值得一提的是,WireMock不僅可以用于模擬來自RESTful服務(wù)的各種JSON響應(yīng),還可以模擬基于SOAP的Web服務(wù)的響應(yīng)。
安全
正如前文提到的,Spring Cloud微服務(wù)基礎(chǔ)設(shè)施通常能夠合并出一個諸如Spring Cloud Gateway的API網(wǎng)關(guān)。據(jù)此,我們可以使用OAuth 2.0、JavaScript對象簽名和加密(Object Signing and Encryption,JOSE)、以及JSON Web令牌標(biāo)準(zhǔn)的令牌中繼模式,來處理用戶的身份識別,授權(quán)應(yīng)用程序查看他們的個人資料,以及訪問網(wǎng)關(guān)后面的安全資源。通常,此類安全設(shè)置會由如下組件構(gòu)成:
- 單點登錄服務(wù)器,如??Keycloak???、Cloud Foundry 的用戶帳戶和???身份驗證服務(wù)器???、以及??諸如Okta??之類商用的OAuth2身份驗證提供程序。
- Spring Cloud Gateway之類的API網(wǎng)關(guān)服務(wù)器,將用戶帳戶的管理和授權(quán)委托給單點登錄服務(wù)器。
- 資源服務(wù)器:在本Spring Boot微服務(wù)示例中為OrderService。
針對本測試示例,我們在單獨測試Spring Boot微服務(wù)時,會采用??Spring Security??的SecurityMockMvcRequestPostProcessors。它將使我們能夠在MockMvc調(diào)用期間,傳遞有效的JWT,定義權(quán)限(即用戶角色),并在啟用安全性的情況下,測試組件的行為。例如:
Java
mockMvc.perform(get("/api/orders/11212/status").with(jwt())).andExpect(status().isOk())
和
mockMvc.perform(get("/api/orders/").with(jwt().authorities(new
SimpleGrantedAuthority("backoffice"))))
.andExpect(status().isOk());
小結(jié)
如今,對于成功的產(chǎn)品交付而言,開發(fā)人員是否能夠在CI/CD管道中,以自動化的方式執(zhí)行各類測試是至關(guān)重要的。希望上述討論的有關(guān)Spring Cloud微服務(wù)組件測試的相關(guān)指南和注意事項,能夠給您的實際項目交付提供幫助。
譯者介紹
陳峻 (Julian Chen),51CTO社區(qū)編輯,具有十多年的IT項目實施經(jīng)驗,善于對內(nèi)外部資源與風(fēng)險實施管控,專注傳播網(wǎng)絡(luò)與信息安全知識與經(jīng)驗;持續(xù)以博文、專題和譯文等形式,分享前沿技術(shù)與新知;經(jīng)常以線上、線下等方式,開展信息安全類培訓(xùn)與授課。
原文標(biāo)題:Component Tests for Spring Cloud Microservices,作者:Kyriakos Mandalas和Dimitris Stavroulakis