目錄
前言
傳統測試在微服務架構中有兩大缺點:手動測試效率極低、在交付流程中才進行測試為時已晚;應該採取新的測試策略提高微服務架構的可測試性;
進行自動化測試是縮短交付週期的唯一方法;
這是一本關於微服務架構設計方面的書,這是本人閱讀的學習筆記。以下對一些符號做些說明:
()為補充,一般是書本里的內容;
[]符號為筆者筆注;
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)的行為。在這個定義中,系統是一個泛指,意味著被測試的軟體元素。它可能像一個類一樣小,也可能像整個應用程式一樣大,或者是介於兩者之間,例如一組類或一個單獨的服務。測試套件是一組相關測試的集合;
- 簡化和加快測試的一個好方法是使用測試替身。測試替身是一個模擬被測系統依賴項的行為的物件。有兩種型別的測試替身:樁和模擬。樁是一個測試替身,它將值返回給被測系統。模擬也是一個測試替身,由測試用來驗證被測系統是否正確呼叫依賴;
- 使用測試金字塔可確定將測試工作重點放在服務的哪個部分。大多數測試應該是快速、可靠且易於編寫的單元測試。必須儘量減少端到端測試的數量,因為它們寫入速度慢、脆弱且耗時;