隨著微服務架構的普及,如何在持續整合流程中有效地執行自動化測試成為一大挑戰。本文將介紹如何利用 Jenkins 和 Docker 建立一個健全的自動化測試流程,涵蓋單元測試、程式碼覆寫率分析、依賴套件安全性檢查等導向。我們將以 Python 和 Go 語言撰寫的微服務為例,示範如何在 Docker 容器中執行測試,並透過 Jenkins Pipeline整合測試結果,生成報告,並及早發現程式碼缺陷,確保軟體品質。同時,我們也會探討如何使用程式碼檢查工具,例如 golint,來提升程式碼品質,並利用 Nancy 等工具進行安全性掃描,降低潛在風險。最後,我們將介紹如何運用 Jenkins 的平行測試功能,加速測試流程,縮短整體建置時間。
使用 Jenkins 執行自動化測試於微服務架構
在前一章中,我們學習瞭如何設定多分支Pipeline任務(multibranch pipeline jobs)於容器化的微服務,並且透過 Webhook 持續觸發 Jenkins 任務。本章將聚焦於在持續整合(CI)Pipeline中執行自動化測試。圖 8.1 總結了目前的 CI 工作流程階段。
本章涵蓋內容
- 為根據 Python、Go、Node.js 和 Angular 的服務實作 CI Pipeline
- 使用 Headless Chrome 執行預整合測試和自動化 UI 測試
- 在 Jenkins Pipeline中執行 SonarQube 靜態程式碼分析
- 在 Docker 容器內執行單元測試並發布程式碼覆寫率報告
- 在 Jenkins Pipeline中整合相依性檢查並注入安全機制於 DevOps
圖 8.1 本章涵蓋的測試階段
測試自動化被視為敏捷開發的根本。若希望快速發布產品(甚至每日發布),並保持合理的品質,就必須轉向自動化測試。另一方面,忽視測試可能會導致客戶不滿和產品延遲。然而,自動化測試過程比自動化建置、發布和佈署過程更具挑戰性。自動化應用程式中使用的幾乎所有測試案例通常需要耗費大量精力。這是一種隨著時間而成熟的活動。並非總是能夠自動化所有測試,但目標是盡可能自動化。
圖 8.2 目標 CI Pipeline
在繼續實作 CI Pipeline之前,先簡要回顧一下與 Jenkins 整合的 Web 分散式應用程式架構:它根據微服務架構,並拆分為以不同程式語言和框架撰寫的元件/服務。圖 8.3 說明瞭此架構。
在 Docker 容器內執行單元測試
單元測試是盡早識別問題的前線努力。測試需要小而快速,以提高效率。
Python 單元測試範例
movies-loader 服務以 Python 編寫。為了定義單元測試,我們將使用 unittest 框架(隨 Python 安裝附帶)。首先,我們匯入 unittest 模組,它提供豐富的方法來建構和執行測試。以下清單 test_main.py 示範了一個簡短的單元測試,用於測試 JSON 載入和解析機制。
import unittest
import json
class TestJSONLoaderMethods(unittest.TestCase):
movies = []
@classmethod
def setUpClass(cls):
with open('movies.json') as json_file:
cls.movies = json.load(json_file)
def test_rank(self):
self.assertEqual(self.movies[0]['rank'], '1')
def test_title(self):
self.assertEqual(self.movies[0]['title'], 'The Shawshank Redemption')
def test_id(self):
self.assertEqual(self.movies[0]['id'], 'tt0111161')
if __name__ == '__main__':
unittest.main()
Dockerfile.test
為了在 Docker 容器內執行測試,我們建立一個名為 Dockerfile.test 的檔案,內容如下:
FROM python:3.7.3
WORKDIR /app
COPY test_main.py .
COPY movies.json .
這個 Dockerfile 從官方的 Python 3.7.3 映像檔建置,設定了一個名為 app 的工作目錄,並將測試檔案複製到工作目錄。
更新 Jenkinsfile
接下來,更新 Jenkinsfile 以新增一個名為「Unit Test」的階段,如下所示:
def imageName = 'mlabouardy/movies-loader'
node('workers') {
stage('Checkout') {
checkout scm
}
stage('Unit Test') {
// 建立 Docker 映像檔並執行單元測試
docker.build("${imageName}-test", "-f Dockerfile.test .")
docker.image("${imageName}-test").run("--rm")
}
}
內容解密:
- Docker 建置與執行:在「Unit Test」階段,我們使用 Docker 建置一個名為
${imageName}-test的映像檔,根據Dockerfile.test。 - 單元測試執行:建置完成後,我們從該映像檔啟動一個容器來執行單元測試。
--rm引數:容器執行完畢後自動刪除,避免佔用資源。
圖 8.3 Watchlist 微服務架構
本章節詳細介紹瞭如何在 Jenkins 中設定自動化測試流程,包括單元測試的撰寫、Dockerfile 的建立以及 Jenkinsfile 的更新,以實作在 Docker 容器內執行單元測試。接下來的章節將進一步探討其他型別的測試,如整合測試、UI 測試及靜態程式碼分析等。
在 Docker 容器中執行單元測試
在 Jenkins 中整合單元測試是一個重要的步驟,以確保程式碼的正確性和穩定性。以下將介紹如何使用 Docker 容器執行單元測試。
建立單元測試階段
首先,在 Jenkinsfile 中新增一個階段(stage)來執行單元測試。這個階段將使用 Docker 建置一個測試映像,並執行測試。
stage('Unit Tests'){
sh "docker build -t ${imageName}-test -f Dockerfile.test ."
sh "docker run --rm ${imageName}-test"
}
在這個範例中,docker build 命令用於建立一個名為 ${imageName}-test 的 Docker 映像,而 docker run 命令則用於執行這個映像中的測試。--rm 旗標用於在測試完成後自動刪除容器。
使用 Docker DSL
除了使用 shell 命令外,也可以使用 Docker DSL 來執行測試。Docker DSL 提供了一個更高層級的抽象,使得程式碼更易讀和維護。
stage('Unit Tests'){
def imageTest = docker.build("${imageName}-test", "-f Dockerfile.test .")
imageTest.inside{
sh 'python test_main.py'
}
}
在這個範例中,docker.build() 方法用於建立一個 Docker 映像,而 inside() 方法則用於在這個映像中執行測試。
內容解密:
docker.build()方法會建立一個新的 Docker 映像,並傳回一個可以代表這個映像的物件。inside()方法會在這個映像中啟動一個新的容器,並在其中執行指定的命令。sh 'python test_main.py'命令會在容器中執行test_main.py指令碼,進行單元測試。
產生 JUnit 測試報告
為了讓測試結果更容易被理解,可以使用 JUnit 測試報告。JUnit 是一個廣泛被使用的測試框架,而 Jenkins 也對 JUnit 測試報告提供了良好的支援。
首先,需要更新 test_main.py 指令碼,以便產生 JUnit 格式的測試報告。
import xmlrunner
...
if __name__ == '__main__':
runner = xmlrunner.XMLTestRunner(output='reports')
unittest.main(testRunner=runner)
接著,在 Jenkinsfile 中新增一個步驟,以便將測試報告上傳到 Jenkins。
stage('Unit Tests'){
def imageTest = docker.build("${imageName}-test", "-f Dockerfile.test .")
sh "docker run --rm -v $PWD/reports:/app/reports ${imageName}-test"
junit "$PWD/reports/*.xml"
}
內容解密:
sh "docker run --rm -v $PWD/reports:/app/reports ${imageName}-test"命令會啟動一個新的容器,並將當前目錄下的reports目錄掛載到容器中的/app/reports目錄。junit "$PWD/reports/*.xml"命令會將reports目錄下的 JUnit 測試報告上傳到 Jenkins。
自動化程式碼檢查
除了單元測試外,程式碼檢查也是確保程式碼品質的重要步驟。以下將介紹如何使用 Jenkins 自動化程式碼檢查。
使用 Go Linter
首先,需要在 Dockerfile.test 中安裝 Go linter。
FROM golang:1.13.4
WORKDIR /go/src/github.com/mlabouardy/movies-loader
ENV GOCACHE /tmp
WORKDIR /go/src/github/mlabouardy/movies-parser
RUN go get -u golang.org/x/lint/golint
COPY . .
RUN go get -v
接著,在 Jenkinsfile 中新增一個階段,以便執行 Go linter。
stage('Quality Tests'){
def imageTest = docker.build("${imageName}-test", "-f Dockerfile.test .")
imageTest.inside{
sh 'golint'
}
}
內容解密:
sh 'golint'命令會在容器中執行 Go linter,檢查程式碼的品質。- 如果需要讓 Jenkins 在發現程式碼問題時失敗,可以在
golint命令中新增-set_exit_status旗標。
stage('Quality Tests'){
def imageTest = docker.build("${imageName}-test", "-f Dockerfile.test .")
imageTest.inside{
sh 'golint -set_exit_status'
}
}
此圖示顯示了程式碼檢查的流程:
@startuml
skinparam backgroundColor #FEFEFE
skinparam componentStyle rectangle
title Jenkins 自動化測試微服務架構
package "Docker 架構" {
actor "開發者" as dev
package "Docker Engine" {
component [Docker Daemon] as daemon
component [Docker CLI] as cli
component [REST API] as api
}
package "容器運行時" {
component [containerd] as containerd
component [runc] as runc
}
package "儲存" {
database [Images] as images
database [Volumes] as volumes
database [Networks] as networks
}
cloud "Registry" as registry
}
dev --> cli : 命令操作
cli --> api : API 呼叫
api --> daemon : 處理請求
daemon --> containerd : 容器管理
containerd --> runc : 執行容器
daemon --> images : 映像檔管理
daemon --> registry : 拉取/推送
daemon --> volumes : 資料持久化
daemon --> networks : 網路配置
@enduml
圖表翻譯: 此圖表呈現了程式碼檢查的流程。首先,建置一個 Docker 映像,接著在這個映像中執行 Go linter。如果 Go linter 發現了程式碼問題,則會失敗並回報問題;否則,程式碼檢查成功。
自動化測試與 Jenkins 的整合應用
在現代軟體開發流程中,自動化測試是確保程式碼品質的重要環節。透過 Jenkins 這類別持續整合/持續佈署(CI/CD)工具,可以有效地執行自動化測試並產生相關報告。本篇文章將探討如何利用 Jenkins 執行 Go 語言專案的自動化測試,包括單元測試、程式碼覆寫率報告以及安全性測試。
單元測試的基礎架構
在 Go 語言中,單元測試是透過內建的 testing 套件來實作的。一個基本的單元測試函式需要接受 *testing.T 作為引數,並使用 t.Error() 方法來標示測試失敗。以下是一個簡單的範例:
func TestParseMovie(t *testing.T) {
expectedMovie := Movie{
Title: "John Wick (2014)",
ReleaseDate: "24 October 2014 (USA)",
Description: "An ex-hit-man comes ...",
}
currentMovie, err := ParseMovie(HTML)
if expectedMovie.Title != currentMovie.Title {
t.Errorf("returned wrong title: got %v want %v", currentMovie.Title, expectedMovie.Title)
}
}
內容解密:
- 測試函式的定義:測試函式以
Test開頭,後面接著首字母大寫的函式名稱,如TestParseMovie。 - 使用
*testing.T:透過t.Errorf方法報告錯誤,指出預期值與實際值之間的差異。 ParseMovie函式的測試:此範例主要測試ParseMovie函式是否正確解析 HTML 內容並產生預期的Movie結構。
程式碼覆寫率報告的生成
為了評估測試的完整性,Go 語言提供了內建的程式碼覆寫率分析工具。透過以下命令,可以生成 HTML 格式的覆寫率報告:
go test -coverprofile=cover/cover.cov
go tool cover -html=cover/coverage.cov -o coverage.html
內容解密:
go test -coverprofile:執行測試並生成覆寫率檔案cover.cov。go tool cover -html:將覆寫率檔案轉換為 HTML 報告,顯示每一行程式碼的覆寫情況。- Jenkins 整合:可將此命令納入 CI 工作流程,在測試階段後生成並展示覆寫率報告。
在 CI 管道中注入安全性測試
為了確保專案依賴元件的安全性,可以使用 Nancy 這類別工具掃描已知漏洞。以下是如何在 Jenkinsfile 中新增安全性測試階段:
stage('Security Tests'){
imageTest.inside('-u root:root'){
sh 'nancy /go/src/github/mlabouardy/movies-parser/Gopkg.lock'
}
}
內容解密:
- Nancy 的安裝與組態:在 Dockerfile.test 中安裝 Nancy 並設定執行路徑。
- Gopkg.lock 的掃描:Nancy 使用 Gopkg.lock 檔案檢查專案依賴的 Go 套件是否存在已知漏洞。
- 安全性報告:若發現漏洞,Nancy 將以非零程式碼離開,使建置流程失敗。
平行測試的執行
隨著測試數量的增加,執行時間可能成為瓶頸。Jenkins 提供了 parallel DSL 步驟來平行執行測試階段,以縮短整體建置時間。
parallel {
stage('Unit Tests') {
// 執行單元測試
}
stage('Other Tests') {
// 執行其他型別的測試
}
}
圖表翻譯:
此圖示展示瞭如何在 Jenkins 中平行執行多個測試階段,以提高效率。