前言
傳統測試在微服務架構中有兩大缺點:手動測試效率極低、在交付流程中才進行測試為時已晚;應該采取新的測試策略提高微服務架構的可測試性;
進行自動化測試是縮短交付周期的唯一方法;
這是一本關於微服務架構設計方面的書,這是本人閱讀的學習筆記。以下對一些符號做些說明:
()為補充,一般是書本里的內容;
[]符號為筆者筆注;
1. 微服務架構中的測試策略概述
本章重點在用於驗證應用程序或服務的功能的自動化測試;
1.1 編寫自動化測試

圖解:
- 自動化測試通常使用測試框架編寫,如JUnit;
- 自動化測試通常包括四個階段:設置環境、執行測試、驗證結果、清理環境;
- 清理環境很容易被忽略,在涉及數據庫的測試可能需要在測試后將數據庫狀態回滾到設置環境階段的初始狀態;
- 測試套件是一組測試類的集合,測試由測試運行器執行;
1.2 使用模擬和樁進行測試
用來解決被測系統與其他系統之間的一些麻煩依賴關系;

圖解:
- 使用測試替身來消除被測系統的依賴性;
- 測試替身是一個對象,該對象負責模擬依賴項的行為;
- 有兩種類型的測試替身:樁(stub)和模擬(mock)
- 樁是一個測試替身,它代表依賴項來向被測系統發送調用的返回值;
- 模擬也是一個測試替身,用來驗證被測系統是否正確調用依賴項;此外,模擬通常也扮演樁的角色;
1.3 使用范圍對測試進行分類
- 單元測試:測試服務的一小部分,如類;
- 集成測試:驗證服務是否可以與基礎設施服務(如數據庫)或其他應用程序服務進行交互;
- 組件測試:單個服務的驗收測試;
- 端到端測試:整個應用程序的驗收測試;
1.4 使用測試象限對測試進行分類

圖解
- 兩個維度:
- 測試是面向業務還是面向技術:使用領域專家的術語來描述面向業務的測試,使用開發人員的術語和實現來描述面向技術的測試;
- 測試的目標是協助開發還是尋找產品缺陷:開發人員使用協助開發的測試作為日常工作的一部分;尋找產品缺陷的測試旨在確定需要改進的部分;
- 四種不同測試類別:
- Q1協助開發 / 面向技術:單元和集成測試;
- Q2協助開發 / 面向業務:組件和端到端測試;
- Q3尋找產品缺陷 / 面向業務:易用性和探索性測試;
- Q4尋找產品缺陷 / 面向技術:非功能性驗收測試,如性能測試;
1.5 使用測試金字塔對測試進行分類

圖解:
- 測試范圍越大,其構成部件越多,可靠性越低;
- 強調要對服務的每一個細分元素進行測試,能最大限度地減少測試整個服務的組件測試數量;
1.6 微服務架構中的測試挑戰
進程通信是微服務架構的核心,應用程序模塊之間的任何交互都是通過編程語言級別的API進行的;因此測試驗證API的服務是否能與其依賴關系和客戶端進行正常交互尤為重要;

圖解:
- 圖中的進程間通信方式:
- REST客戶端 - 服務端:API Gateway將請求路由到服務並實現API組合;
- 領域事件使用者 - 發布者:Order History Service使用Order Service發布的事件;
- 命令式消息請求方 - 回復方:Order Service將命令式消息發送到各種服務並使用回復;
- 一對服務之間的交互代表了這兩個服務之間的契約或協議,契約要求雙方就事件消息結構及其發布的通道達成一致;
- 作為開發人員,需要確保服務具有穩定的API,做出的改動盡量不要破壞原有的API;
- 測試兩個服務可以交互的一種方法是同時運行兩個服務,調用觸發通信的API,並驗證是否有預期結果;這會遇到集成的問題,解決方案是消費者驅動的契約測試;
1.7 消費者驅動的契約測試

圖解:
- 消費者驅動的契約測試模式:驗證服務是否滿足它的消費者期望;
- 消費者契約測試模式:驗證服務的客戶端是否可以與服務通信;
- 消費者驅動的契約測試通常使用樣例測試,消費者和提供者之間的交互由一組樣例定義,稱為契約;每個契約都包含在一次交互期間交換的樣例消息;
- 消費者契約測試側重於驗證提供者API的參數定義是否符合消費者的期望;
- 對於REST接口,契約測試將驗證提供者程序實現的接口是否:
- 具有預期的HTTP方法和路徑;
- 接受預期的HTTP頭部;
- 接受請求主體;
- 返回預期中的響應,包括狀態代碼、頭部和主體等;
- REST API的消費者契約測試實際上是通過模擬控制器進行的測試;
1.8 使用Spring Cloud的契約測試服務

圖解:
- API Gateway團隊(消費者)編寫的契約定義API Gateway如何與Order Service進行交互;
- 編寫的契約包括API Gateway可能發送給Order Service的HTTP請求和預期的HTTP響應;
- Order Service團隊(提供者)使用這些契約來測試Order Service,並使用它們來測試API Gateway;測試代碼Spring Cloud Contract生成;
- Order Service團隊(提供者)將測試Order Service的契約打包成jar包發布到Maven存儲庫;
- API Gateway團隊(消費者)使用已發布的契約為API Gateway編寫測試;

- 一個契約示例;
- 請求元素是REST接口 GET/orders/{ orderId } 的一個HTTP請求;
- 響應元素是一個該接口對應的HTTP響應,它描述了API Gateway所期望的Order;
1.9 部署流水線

圖解:
- 其包含一系列執行測試套件的階段,后面是一個發布或部署服務的階段;
- 理想情況下流水線是完全自動化的,也可能包含手動步驟;
- 部署流水線包含以下幾個階段:
- 提交前測試階段:執行單元測試。這是由開發人員在提交代碼更改之前執行的;
- 提交測試階段:編譯服務,執行單元測試,並執行靜態代碼分析;
- 集成測試階段:執行集成測試;
- 部署階段:將服務部署到生產環境中;
2. 為服務編寫單元測試
2.1 兩種類型的單元測試

圖解:
- 獨立型單元測試:使用針對類的依賴性的模擬對象隔離測試;
- 協作型單元測試:測試一個類及其依賴項;
2.2 類的職責決定使用哪種單元測試

圖解:
- 每個類的典型策略如下的:
- Order等具有持久化身份對象的實體,使用協作型單元測試;
- Money等作為值集合對象的實體,使用協作型單元測試;
- Sages等需要維護服務之間的數據一致性,使用協作型單元測試;
- OrderService等實現不屬於實體或值對象的業務邏輯的類,使用獨立型單元測試;
- OrderController等處理HTTP請求的控制器類,使用獨立型單元測試;
- 入站和出站等消息網關,使用獨立型測試;
2.3 為實體編寫單元測試

- 單元測試可以徹底測試業務邏輯;
2.4 為值對象編寫單元測試

- 對值對象的測試通常會創建特定狀態的值對象,調用其中一個方法,並對返回值進行斷言;
- 這里使用獨立型單元測試,因為此時Money類沒有任何依賴;前文說使用協作型單元測試,是針對FTGO中Money對象的實際情況;
2.5 為Saga編寫單元測試

- Saga會實現重要的業務邏輯,其是一個持久化對象,向Saga參與方發送命令式消息並處理他們的回復;
- 對Saga的測試除了要為正常執行的場景編寫測試單元,還必須為Saga回滾的各種場景編寫測試;
- 一種方法是編寫使用真實數據庫和消息代理以及樁服務的測試,以此來模擬各種Saga參與方;這種測試非常緩慢;
- 一種更有效的方法是編寫模型與數據庫和消息代理交互的類的測試;這樣可以專注測試Saga的核心職責;
- 上述代碼使用Eventuate Tram Saga測試框架編寫,框架提供一個易於使用的DSL,其抽象出於Saga相互作用的細節;
- DSL可以創建一個Saga並驗證其是否發出正確的消息;
- 事實上,Saga測試框架使用數據庫和消息傳遞基礎設施的模擬來配置Saga框架;
2.6 為領域服務編寫單元測試


- 服務的大多數業務邏輯通過實體類、值對象和Saga實現;領域服務類實現業務邏輯的其余部分;
- 測試領域服務類使用獨立型單元測試,它可以模擬儲存庫和消息傳遞類等依賴項;
- 每個測試按如下方法完成各自測試階段:
- 設置:配置服務依賴項的模擬對象;
- 執行:調用服務方法;
- 驗證:驗證服務方法返回的值是否正確,以及是否已正確調用依賴項;
2.7 為控制器編寫單元測試


- 服務類通常具有一個或多個控制器,用於處理來自其他服務和API Gateway的HTTP請求;
- 控制器類由一組請求處理程序方法組成,每個方法實現一個REST API端點;
- 使用某些框架,如Spring Mock Mvc編寫的測試會產生模擬的HTTP請求,並對HTTP響應進行斷言;
- 這些框架在測試HTTP請求路由以及Java對象與JSON之間的轉換時,無須進行真正的網絡調用;
- 上述代碼是一個獨立單元測試,利用模擬對象來解決OrderController的依賴項;
- 上述代碼使用REST Assured Mock MVC編寫,提供一個DSL,抽象出與控制器交互的細節;其可以輕松地模擬HTTP請求發送到控制器並驗證響應;
2.8 為事件和消息處理程序編寫單元測試

- 與控制器類似,消息適配器往往是調用領域服務的簡單類,因此可以使用類似針對控制器進行單元測試的方法;
- 每個測試示例都是消息適配器,向消息通道發送消息,並驗證是否正確調用了服務模擬;
- 使用Eventuate Tram Mock Messaging框架進行測試,框架提供了一個易於使用的DSL,用於編寫模擬消息測試;
- 為了驗證服務是否與正確地與其他服務交互,必須編寫集成測試;還需要編寫單獨測試整個服務的組件測試;這點在下一章進行討論;
3. 本章小結
- 自動化測試是快速、安全地交互軟件的重要基石。更重要的是,由於微服務架構固有的復雜性,要從微服務架構中充分受益,必須實現自動化測試;
- 測試的目的是驗證被測試系統(SUT)的行為。在這個定義中,系統是一個泛指,意味着被測試的軟件元素。它可能像一個類一樣小,也可能像整個應用程序一樣大,或者是介於兩者之間,例如一組類或一個單獨的服務。測試套件是一組相關測試的集合;
- 簡化和加快測試的一個好方法是使用測試替身。測試替身是一個模擬被測系統依賴項的行為的對象。有兩種類型的測試替身:樁和模擬。樁是一個測試替身,它將值返回給被測系統。模擬也是一個測試替身,由測試用來驗證被測系統是否正確調用依賴;
- 使用測試金字塔可確定將測試工作重點放在服務的哪個部分。大多數測試應該是快速、可靠且易於編寫的單元測試。必須盡量減少端到端測試的數量,因為它們寫入速度慢、脆弱且耗時;
最后
