Docker 中微服務的自動化測試以實現持續集成

在微服務架構開發相關的專案中,CI/CD從可喜的機會範疇走向了迫切需要的範疇。 自動化測試是持續整合不可或缺的一部分,一種有效的方法可以讓團隊與家人和朋友度過許多愉快的夜晚。 否則,該專案就有永遠無法完成的風險。

可以透過模擬物件的單元測試來覆蓋整個微服務程式碼,但這只能部分解決問題,並留下許多問題和困難,特別是在使用資料進行測試時。 像往常一樣,最迫切的問題是測試關聯式資料庫中的資料一致性、測試雲端服務的工作以及在編寫模擬物件時做出錯誤的假設。

所有這些以及更多問題都可以透過在 Docker 容器中測試整個微服務來解決。 確保測試有效性的一個毫無疑問的優勢是測試投入生產的相同 Docker 映像。

這種方法的自動化帶來了許多問題,以下將描述這些問題的解決方案:

  • 同一docker主機中並行任務的衝突;
  • 測試迭代期間資料庫中的標識符衝突;
  • 等待微服務準備就緒;
  • 合併日誌並輸出到外部系統;
  • 測試傳出 HTTP 請求;
  • Web 套接字測試(使用 SignalR);
  • 測試 OAuth 身份驗證和授權。

本文基於 我的演講 在 SECR 2019 上。所以對於那些懶得閱讀的人來說, 這是演講錄音.

Docker 中微服務的自動化測試以實現持續集成

在本文中,我將告訴您如何使用腳本在 Docker 中執行被測服務、資料庫和 Amazon AWS 服務,然後在 Postman 上進行測試,完成後停止並刪除已建立的容器。 每次程式碼變更時都會執行測試。 透過這種方式,我們可以確保每個版本都能與 AWS 資料庫和服務正確搭配。

開發人員自己在 Windows 桌面上執行相同的腳本,並由 Linux 下的 Gitlab CI 伺服器執行。

公平地說,引入新的測試不需要在開發人員的電腦上或在提交上運行測試的伺服器上安裝額外的工具。Docker 解決了這個問題。

由於以下原因,測試必須在本機伺服器上執行:

  • 網路永遠不會完全可靠。 一千個請求中,有一個可能會失敗;
    在這種情況下,自動測試將不起作用,工作將停止,你必須在日誌中尋找原因;
  • 某些第三方服務不允許太頻繁的請求。

此外,不宜使用支架,因為:

  • 支架不僅會因運行在其上的錯誤代碼而損壞,還會因正確代碼無法處理的資料而損壞;
  • 無論我們多麼努力地嘗試恢復測試期間所做的所有更改,都可能會出現問題(否則,為什麼要測試?)。

關於專案和流程組織

我們公司開發了一個在 Amazon AWS 雲端中的 Docker 中運行的微服務 Web 應用程式。 專案中已經使用了單元測試,但經常發生單元測試未偵測到的錯誤。 有必要測試整個微服務以及資料庫和 Amazon 服務。

該專案使用標準的持續整合流程,其中包括在每次提交時測試微服務。 分配任務後,開發人員會對微服務進行更改、手動測試並執行所有可用的自動化測試。 如有必要,開發人員會更改測試。 如果沒有發現問題,則提交到該問題的分支。 每次提交後,測試都會自動在伺服器上執行。 成功審核後,會合併到公共分支並對其啟動自動測試。 如果共用分支上的測試通過,服務將在 Amazon Elastic Container Service(工作台)上的測試環境中自動更新。 支架對於所有開發人員和測試人員來說都是必需的,不建議破壞它。 在此環境中的測試人員透過執行手動測試來檢查修復或新功能。

專案架構

Docker 中微服務的自動化測試以實現持續集成

該應用程式由十多個服務組成。 其中一些是用 .NET Core 編寫的,有些是用 NodeJs 編寫的。 每個服務都在 Amazon Elastic Container Service 中的 Docker 容器中執行。 每個都有自己的Postgres資料庫,有的還有Redis。 沒有通用的資料庫。 如果多個服務需要相同的數據,那麼當這些數據發生變化時,會透過SNS(簡單通知服務)和SQS(亞馬遜簡單佇列服務)傳輸到每個服務,並且服務將其保存在自己單獨的資料庫中。

SQS 和 SNS

SQS 允許您使用 HTTPS 協定將訊息放入佇列並從佇列中讀取訊息。

如果多個服務讀取一個佇列,則每個訊息僅到達其中一個。 當運行相同服務的多個實例以在它們之間分配負載時,這非常有用。

如果希望每個訊息傳遞到多個服務,則每個接收者必須有自己的佇列,並且需要SNS將訊息複製到多個佇列中。

在 SNS 中,您建立一個主題並訂閱它,例如 SQS 佇列。 您可以向主題發送訊息。 在這種情況下,訊息將發送到訂閱該主題的每個隊列。 SNS 沒有讀取訊息的方法。 如果在偵錯或測試過程中您需要了解傳送到 SNS 的內容,您可以建立 SQS 佇列,訂閱所需的主題並讀取佇列。

Docker 中微服務的自動化測試以實現持續集成

API網關

大多數服務不能直接從網路存取。 存取是透過 API 網關進行的,該網關檢查存取權限。 這也是我們的服務,也有測試。

即時通知

該應用程式使用 信號R向使用者顯示即時通知。 這是在通知服務中實現的。 它可以直接從 Internet 訪問,並且本身可以與 OAuth 一起使用,因為與集成 OAuth 和通知服務相比,在網關中構建對 Web 套接字的支援是不切實際的。

眾所周知的測試方法

單元測試用模擬物件替換資料庫之類的東西。 例如,如果微服務嘗試使用外鍵在表中建立記錄,且該鍵所引用的記錄不存在,則無法執行該要求。 單元測試無法檢測到這一點。

В 文章來自微軟 建議使用記憶體資料庫並實作模擬物件。

記憶體資料庫是實體框架支援的 DBMS 之一。 它是專門為測試而創建的。 此類資料庫中的資料僅儲存至使用該資料庫的程序終止為止。 它不需要建立表,也不檢查資料完整性。

模擬物件僅在測試開發人員了解其工作原理的範圍內對它們要替換的類別進行建模。

微軟文章中沒有具體說明如何讓Postgres在執行測試時自動啟動並執行遷移。 我的解決方案做到了這一點,此外,沒有添加任何專門用於微服務本身測試的程式碼。

讓我們繼續討論解決方案

在開發過程中,我們發現單元測試不足以及時發現所有問題,因此決定從不同的角度來解決這個問題。

設定測試環境

第一個任務是部署測試環境。 運行微服務所需的步驟:

  • 配置本地環境的待測服務,在環境變數中指定連接資料庫和AWS的詳細資訊;
  • 啟動 Postgres 並透過執行 Liquibase 執行遷移。
    在關係型 DBMS 中,在將資料寫入資料庫之前,需要建立資料模式,即表。 更新應用程式時,必須將表轉換為新版本使用的形式,並且最好不要丟失資料。 這稱為遷移。 在最初為空的資料庫中建立表格是遷移的一種特殊情況。 遷移可以內建到應用程式本身。 .NET 和 NodeJS 都有遷移框架。 在我們的例子中,出於安全原因,微服務被剝奪了更改資料模式的權利,並且遷移是使用 Liquibase 執行的。
  • 啟動 Amazon LocalStack。 這是在家運行的 AWS 服務的實作。 Docker Hub 上有一個現成的 LocalStack 映像。
  • 執行腳本以在 LocalStack 中建立必要的實體。 Shell 腳本使用 AWS CLI。

用於專案測試 郵差。 它以前就存在,但它是手動啟動的,並測試了展位上已經部署的應用程式。 此工具可讓您發出任意 HTTP(S) 請求並檢查回應是否符合預期。 查詢被組合成一個集合,並且可以運行整個集合。

Docker 中微服務的自動化測試以實現持續集成

自動測試如何進行?

在測試過程中,Docker 中一切正常:被測服務、Postgres、遷移工具和 Postman,或者更確切地說是它的控制台版本 - Newman。

Docker 解決了很多問題:

  • 獨立於主機配置;
  • 安裝依賴:Docker從Docker Hub下載映像;
  • 將系統恢復到原始狀態:只需移除容器即可。

Docker-compose 將容器聯合成一個與互聯網隔離的虛擬網絡,容器透過網域相互查找。

測試由 shell 腳本控制。 為了在 Windows 上執行測試,我們使用 git-bash。 因此,一個腳本對於 Windows 和 Linux 來說就足夠了。 專案中的所有開發人員都安裝了 Git 和 Docker。 在 Windows 上安裝 Git 時,會安裝 git-bash,因此每個人也都安裝了 git-bash。

該腳本執行以下步驟:

  • 建置 docker 映像
    docker-compose build
  • 啟動資料庫和LocalStack
    docker-compose up -d <контейнер>
  • LocalStack的資料庫遷移與準備
    docker-compose run <контейнер>
  • 啟動正在測試的服務
    docker-compose up -d <сервис>
  • 運行測試(紐曼)
  • 停止所有容器
    docker-compose down
  • 在 Slack 中發布結果
    我們進行了一次聊天,其中包含帶有綠色複選標記或紅色十字以及日誌連結的訊息。

這些步驟涉及以下Docker映像:

  • 正在測試的服務與生產服務的鏡像相同。 測試的配置是透過環境變數進行的。
  • 對於 Postgres、Redis 和 LocalStack,使用 Docker Hub 中的現成映像。 還有 Liquibase 和 Newman 的現成圖像。 我們在他們的框架上建立我們的框架,並在那裡添加我們的文件。
  • 要準備 LocalStack,您可以使用現成的 AWS CLI 映像並建立包含基於該映像的腳本的映像。

運用 ,您不必僅為了將檔案新增至容器而建置 Docker 映像。 但是,磁碟區不適合我們的環境,因為 Gitlab CI 任務本身在容器中運行。 您可以從這樣的容器控制 Docker,但磁碟區只能掛載來自主機系統的資料夾,而不是來自其他容器的資料夾。

您可能遇到的問題

等待準備

當帶有服務的容器正在運行時,這並不意味著它已準備好接受連線。 您必須等待連線繼續。

有時可以使用腳本解決此問題 等待它.sh,等待建立 TCP 連線的機會。 但是,LocalStack 可能會拋出 502 Bad Gateway 錯誤。 此外,它由許多服務組成,如果其中一項服務準備就緒,則不會說明其他服務。

解決方法:等待來自 SQS 和 SNS 的 200 回應的 LocalStack 設定腳本。

平行任務衝突

多個測試可以在同一台 Docker 主機上同時執行,因此容器和網路名稱必須是唯一的。 此外,來自同一服務的不同分支的測試也可以同時執行,因此將它們的名稱寫入每個 compose 檔案中是不夠的。

解決方法:腳本將 COMPOSE_PROJECT_NAME 變數設定為唯一值。

Windows功能

在 Windows 上使用 Docker 時,我想指出很多事情,因為這些經驗對於理解錯誤發生的原因非常重要。

  1. 容器中的 Shell 腳本必須有 Linux 行結尾。
    shell CR 符號是語法錯誤。 從錯誤訊息中很難看出是這種情況。 在 Windows 上編輯此類腳本時,您需要合適的文字編輯器。 此外,版本控制系統必須正確配置。

這是 git 的配置方式:

git config core.autocrlf input

  1. Git-bash 模擬標準 Linux 資料夾,並且在呼叫 exe 檔案(包括 docker.exe)時,將絕對 Linux 路徑替換為 Windows 路徑。 但是,這對於不在本機電腦上的路徑(或容器中的路徑)沒有意義。 無法停用此行為。

解決方法:在路徑開頭加上一個斜線://bin 而不是 /bin。 Linux 能夠理解這樣的路徑;對於它來說,多個斜杠與一個斜杠是相同的。 但 git-bash 無法辨識此類路徑,也不會嘗試轉換它們。

日誌輸出

執行測試時,我希望查看來自 Newman 和正在測試的服務的日誌。 由於這些日誌的事件是相互關聯的,因此將它們組合在一個控制台中比兩個單獨的檔案方便得多。 紐曼推出透過 docker-compose 運行,因此它的輸出最終出現在控制台中。 剩下的就是確保服務的輸出也到達那裡。

原來的解決方案是做 碼頭工人組成 沒有旗幟 -d,但是使用 shell 功能,將此進程傳送到背景:

docker-compose up <service> &

這種方法一直有效,直到需要將日誌從 Docker 傳送到第三方服務為止。 碼頭工人組成 停止向控制台輸出日誌。 然而,團隊努力了 碼頭工人附加.

解決方法:

docker attach --no-stdin ${COMPOSE_PROJECT_NAME}_<сервис>_1 &

測試迭代期間的識別碼衝突

測試會進行多次迭代。 資料庫未清除。 資料庫中的記錄具有唯一的 ID。 如果我們在請求中寫下特定的ID,我們將在第二次迭代時遇到衝突。

為了避免這種情況,ID 必須是唯一的,或者必須刪除測試創建的所有物件。 由於需要,某些物件無法刪除。

解決方法:使用 Postman 腳本產生 GUID。

var uuid = require('uuid');
var myid = uuid.v4();
pm.environment.set('myUUID', myid);

然後在查詢中使用該符號 {{myUUID}},它將被替換為變數的值。

透過 LocalStack 進行協作

如果正在測試的服務讀取或寫入 SQS 佇列,則為了驗證這一點,測試本身也必須使用該佇列。

解決方法:Postman 向 LocalStack 發出請求。

AWS 服務 API 已記錄,允許在沒有 SDK 的情況下進行查詢。

如果服務寫入佇列,那麼我們會讀取它並檢查訊息的內容。

如果服務傳送訊息到SNS,在準備階段LocalStack也會建立一個佇列並訂閱這個SNS主題。 那麼一切都歸結於上面所描述的。

如果服務需要從佇列中讀取訊息,那麼在前面的測試步驟中我們會將此訊息寫入佇列。

測試源自於被測微服務的 HTTP 請求

某些服務透過 HTTP 與 AWS 以外的其他服務一起運作,且某些 AWS 功能未在 LocalStack 中實現。

解決方法:在這些情況下它可以提供幫助 模擬伺服器,其中有一個現成的圖像 Docker中心。 預期的請求和對它們的回應由 HTTP 請求配置。 API 已記錄,因此我們向 Postman 發出請求。

測試 OAuth 身份驗證和授權

我們使用 OAuth 和 JSON Web令牌(JWT)。 該測試需要我們可以在本地運行的 OAuth 提供者。

服務和 OAuth 提供者之間的所有互動都歸結為兩個請求:首先,請求配置 /.well-known/openid-configuration,然後在設定中的位址請求公鑰 (JWKS)。 所有這些都是靜態內容。

解決方法:我們的測試 OAuth 提供者是一個靜態內容伺服器及其上的兩個檔案。 令牌產生一次並提交給 Git。

SignalR 測試的特點

Postman 不支援 websocket。 創建了一個特殊工具來測試 SignalR。

SignalR 用戶端不僅僅是一個瀏覽器。 .NET Core 下有一個客戶端程式庫。 以 .NET Core 編寫的客戶端建立連線、進行驗證並等待特定的訊息序列。 如果收到意外訊息或連線遺失,則用戶端退出並傳回代碼 1。如果收到最後一條預期訊息,則用戶端退出並傳回代碼 0。

紐曼與客戶同時工作。 啟動多個客戶端來檢查訊息是否已傳遞給每個需要它們的人。

Docker 中微服務的自動化測試以實現持續集成

若要執行多個客戶端,請使用該選項 - 規模 在 docker-compose 命令列上。

在運行之前,Postman 腳本會等待所有用戶端建立連線。
我們已經遇到過等待連線的問題。 但是有伺服器,這裡是客戶端。 需要採取不同的方法。

解決方法:容器中的客戶端使用該機制 健康檢查通知主機上的腳本其狀態。 一旦建立連接,客戶端就會在特定路徑(例如 /healthcheck)建立一個檔案。 docker 檔案中的 HealthCheck 腳本如下所示:

HEALTHCHECK --interval=3s CMD if [ ! -e /healthcheck ]; then false; fi

團隊 碼頭工人檢查 顯示容器的正常狀態、健康狀態和退出代碼。

Newman 完成後,腳本檢查客戶端的所有容器是否已終止,代碼為 0。

幸福存在

克服了上述困難後,我們有了一套穩定的運行測試。 在測試中,每個服務作為一個單元運行,與資料庫和 Amazon LocalStack 互動。

這些測試可保護 30 多名開發人員組成的團隊免受 10 多個微服務的複雜互動和頻繁部署的應用程式中的錯誤的影響。

來源: www.habr.com

添加評論