Categories
程式開發

沒經過這些測試,你的微服務架構也敢進入生產環境?


微服務架構是指將應用程序拆分為一系列較小、且直接用於解決具體問題的組件的實踐方案。以此為基礎,架構中的每一個組件都將通過各類常規協議(例如 HTTP 或者更輕量化的 TCP)相互通信。
說到這裡,大家可能會好奇,對於微服務架構來說,測試真的很重要嗎?

答案當然是重要!測試的重要性是體現在多方面的,不過比較重要的是以下幾點:

  • 節約金錢與時間

  • 更安全

  • 強化生產質量(減少 bug 與錯誤數量)

  • 提升客戶滿意度

  • 最重要的是,夜裡能睡得更安穩

隨時出 Bug、動不動就宕機的應用程序,沒人會喜歡,而且往往這種應用程序的安全漏洞很多,如果黑客想從中竊取憑證或者搶劫資金,簡直易如反掌。如果我們想要開發一款具備一定複雜性的應用程序,那麼測試是一定需要的。

使用什麼測試方法?

目前軟件測試的種類比較多,大致可以分為功能測試和非功能測試兩大類。其中功能測試類包括單元測試、集成測試、通煙測試、回歸測試、健全測試、Beta/ 驗收測試和端到端(e2e)測試,而非功能測試則包括了性能測試、負載測試、壓力測試、安全測試、合規測試和可用性測試。

一般來說,應用程序的複雜度越高,需要使用的測試類型也就越多。不過,有幾個測試是所有應用程序都不可或缺的:

  • 單元測試

  • 集成測試

  • E2E 測試、回歸測試與安全測試相結合

整體流程應該是,先編寫程序來檢查應用中各個層面是否在按照預期設計運作,若應用已經上線,那麼就需要進一步編寫測試來檢查代碼更新是否會對原有功能造成破壞。如果是微服務架構,那麼除了以上的基礎測試之外,可能還需要編寫專門的測試,例如負載測試,用於檢查系統在正常與預期峰值負載條件下的運行狀況。

少說話,多編碼

接下來,我們一起探討一下如何在微服務架構當中實現上述基礎軟件測試類型。微服務架構使用 TCP 協議實現組件間的通信,並利用 Nest Framework 以 Node.JS 編寫而成。

很多人可能不太了解 NestJS,我們先簡單介紹一下,官方 GitHub repo 是這樣描述 Nest 的:

Nest 是一款框架,用於構建高效且可擴展的 Node.js 服務器端應用程序。它利用現代 JavaScript 的特性,由 TypeScript 構建而成(保留純 JavaScript 兼容性),同時結合有 OOP(面向對象編程)、FP(函數編程)以及 FRP(函數響應式編程)的元素。從底層來看,Nest 不僅能夠使用 Express,同時也兼容其他多種庫,包括 Fastify,旨在輕鬆使用大量現有第三方插件。 ”

在示例中,我們將使用一個簡單的模塊 name:user 配合一個簡單函數 createUser,在數據庫內創建一個新用戶。

該模塊的文件夾結構如下所示:

沒經過這些測試,你的微服務架構也敢進入生產環境? 1

我們設了一個監聽 create_user 消息的控制器。在利用 ValidationPipe 進行驗證之後,它會在服務之內調用一個具有相同名稱的函數。

沒經過這些測試,你的微服務架構也敢進入生產環境? 2

沒經過這些測試,你的微服務架構也敢進入生產環境? 3

在服務之內,我們會對用戶密碼進行哈希處理。接下來,使用 TypeORM 將新用戶保存在數據庫內。

沒經過這些測試,你的微服務架構也敢進入生產環境? 4

對這個模塊,我們使用 TypeORM 作為鏈接至表 User 的 ORM;同時,利用名為 UtilsModule 的另一個模塊實現某些輔助功能:

沒經過這些測試,你的微服務架構也敢進入生產環境? 5

沒經過這些測試,你的微服務架構也敢進入生產環境? 6

單元測試

所謂單元,是指應用程序當中的最小可測試部分,例如函數、類或者過程。單元測試則代表一種軟件測試方法,旨在測試源代碼中的各個單元,以確定其是否符合開發階段的預期設計。

編寫單元測試,是為了保證不同代碼形式(函數、類等)的每個簡單實現,均符合設計、要求並能夠按預期運行。

單元測試的目標,在於隔離程序中的各個部分,並測試這些部分是否正常工作。

換言之,與當前測試單元無關的其他代碼部分,則以模擬形式存在,僅作為運行環境使用。

在我們的示例中,需要測試的單元自然就是之前提到的 createUser 了。這意味著我們首先得把它跟其他組件隔離開來。因此,第一步就是模擬 user repository 類,這個類代表著數據庫使用 TypeORM 時的鏈接。

如果分析服務中的 createUser 函數,就會發現它的作用只是對密碼進行哈希處理,而後將 User 對象保存在數據庫內。以此為基礎,我們編寫出以下測試套件:

沒經過這些測試,你的微服務架構也敢進入生產環境? 7

首先,我們編寫一個 beforeAll 函數來創建測試模塊。接下來,使用模擬類替換原始 repository,該模擬類將僅返回需要保存在數據庫中的對象。

在這個函數中,我們還得考慮這樣一種極端要求:

使用特定屬性(郵件、密碼等)創建一個新用戶對象,請確保密碼經過哈希處理

這裡,我們會模擬 save() 函數,因為它來自 TypeORM、不屬於測試中的對象單元範疇,使用簡單函數將其覆蓋以返回我們傳遞的對象。

到這裡,我們的工作就很簡單了:檢查在發送對象過程中使用的郵件屬性與哈希密碼是否正確。

集成測試

集成測試屬於另一種軟件測試方法,用於驗證源代碼單元內的組成功能是否正常。

單元測試的目標是保證代碼符合其設計與功能要求,同時能夠按照預期方式運行。集成測試則更進一步,將不同模塊融合在一起,並測試它們是否能夠正確交互。

在本示例中,我們將 UserModule 與 TypeORM 模塊(存在依賴關係)結合起來,檢查新用戶是否被正確保存在數據庫內。

這一次,我們仍然需要使用之前提到的函數,只是具體測試流程有所區別:

沒經過這些測試,你的微服務架構也敢進入生產環境? 8

這一次,beforeAll 函數不再模擬 userRepository,而直接使用原始庫;此外,我們還添加 databaseModule 以創建指向數據庫的連接。

與此同時,由於我們現在使用的是真實數據庫,因此必須編寫對應函數調整數據庫以完成測試。

在測試之前與之後,我們需要清空數據庫,保證不存在任何干擾內容。

另外,我們還需要手動關閉指向數據庫的連接,這樣才能保證測試完成後所有處理程序都被正確關閉。

通過單元測試,我們已經檢查了函數能否正常工作。因此,這裡可以直接測試該函數能夠與 TypeORM 的 save() 方法相結合,進而將新用戶對象存儲在數據庫內。

我們編寫了名為 getOneUserFromDb 的輔助函數,它的作用顧名思義——從數據庫內獲取一個用戶。接下來,檢查郵件與 accountConfimed 屬性是否正確(後者在實體類內應默認設置為 false)。

端到端測試

端到端測試是一種軟件測試方法,旨在持續跟踪應用程序的整個運行流程是否與設計思路一致。

這類測試的目標,在於確保應用程序能否在真實場景下按預期方式運行。

到目前為止,我們已經測試了用戶密碼是否經過正確的哈希處理,以及密碼及郵件是否被保存在數據庫內。

現在,我們需要通過請求測試驗證流程。我們的控制器內包含一條驗證管道,通過測試傳入的有效負載檢查對像是否與 CreateUserDto 相匹配。

沒經過這些測試,你的微服務架構也敢進入生產環境? 3

下面來看測試過程:

沒經過這些測試,你的微服務架構也敢進入生產環境? 10

在這裡,我們希望通過測試觀察系統在創建用戶之後,是否會發送完整對像或者以錯誤格式發送屬性。

這就是我們在某些​​極端情況下,使用三種基礎軟件測試方法得出的示例結果。

手動測試與自動測試

說到這裡,我們的測試過程一直以手動方式編寫——因為示例規模不大,所以過程非常順利。但如果代碼量龐大,那麼測試的複雜度與工作量會急劇增加。

例如,如果需要測試身份驗證系統,大家就必須複製真實用戶的完整行為。另外,在測試環境的構建階段還需要模擬請求與響應部分,包括 cookie 及其他內容。很明顯,測試套件越複雜,運行需要的時間就越長。

幸運的是,自動化工具已經成為當前測試工作中的有力武器。這類工具包含多種內置功能,允許用戶模擬整個測試環境,輕鬆搞定手動方式幾乎無法實現的測試流程。

大家還可以走得更遠,在應用程序中用上 API 自動測試工具。這些工具帶有多種附加選項,能夠高效生成負載測試、回歸測試與實際運行狀況等數據報告。

另外,它們還擁有良好的 UI 設計,進一步降低測試編寫難度。

總結

要讓軟件真正為生產環境做好準備,測試絕對是不可或缺的一環。而隨著應用程序複雜性的持續提升,測試工作很可能成為開發團隊的最大瓶頸。

在這種情況下,請確保按照具體類型將測試套件區分開來,正如我們在示例中的做法。想要測試哪類功能,就使用與之對應的套件,保證事半功倍。

如果手動套件不足以滿足用例要求,或者您發現測試太難且編寫耗時太長,那麼不妨選擇自動化工具及平台。目前這類方案已經相當成熟,絕對能夠成為各位日常工作中的好幫手。

原文鏈接:

https://www.freecodecamp.org/news/testing-microservices-are-they-production-ready-2/