現代軟體開發講求快速迭代和交付,CI/CD 已成為不可或缺的實踐。然而,速度不能犧牲品質,因此在 CI/CD 管線中整合程式碼品品檢查和測試至關重要。本文將以 GitLab CI/CD 為例,探討如何結合 Code Quality 功能、pytest 框架和 Fuzzing 測試技術,開發兼顧效率和品質的軟體開發流程。首先,利用 GitLab 內建的 Code Quality 功能,可以有效地掃描程式碼,識別潛在的程式碼風格問題、複雜度問題和效能瓶頸。接著,整合 pytest 測試框架,撰寫涵蓋各種情境的單元測試,並將測試結果以 JUnit 格式輸出,方便在 GitLab 介面中檢視測試報告。更進一步,匯入 Fuzzing 測試技術,透過隨機資料輸入,找出程式碼中隱藏的錯誤和漏洞,提升軟體的穩定性和安全性。透過以上方法,開發團隊可以在每次程式碼提交時自動執行程式碼品品檢查和測試,及早發現問題並修復,確保軟體品質。

CI/CD 管道中的程式碼品品檢查

在持續整合與持續交付(CI/CD)管道中,確保程式碼品質是一個至關重要的步驟。GitLab 提供了一個名為「Code Quality」的功能,這個功能可以幫助我們檢查程式碼是否符合特定的品質標準。這個功能類別似於一個強化版的 lint 工具,能夠幫助我們識別並解決程式碼中的潛在問題。

Code Quality 功能的作用

Code Quality 功能依賴於一個外部服務,名為 Code Climate。這個服務可以掃描多種主流程式語言的程式碼,但並非所有語言都能完全支援。你可以參考 Code Climate 的官方檔案來檢視支援的語言列表,不過大多數常用的語言,如 Java、Python、Ruby、JavaScript 等,都能很好地運作。

Code Quality 功能主要關注以下幾個方面:

  1. 效能:檢查程式碼是否存在效能瓶頸。
  2. 風格:確保程式碼符合特定的風格規範。
  3. 複雜度:檢查程式碼的複雜度是否過高。
  4. 安全性:識別潛在的安全漏洞。
  5. 可疑模式:找出可能導致錯誤的程式碼模式。

具體來說,它可以檢測到以下問題:

  • 函式引數過多
  • 函式有過多的離開點
  • 函式或類別過長
  • 複雜的邏輯表示式
  • 垂直空白過多或過少
  • 重複的程式碼

此外,如果你所使用的程式語言有一套已經建立的風格規範(例如 Python 的 PEP-8 規範或 Ruby 的 Rubocop 規則集),你可以組態 Code Quality 功能來包含這些規則。

啟用 Code Quality 功能

在 GitLab 中啟用 Code Quality 功能非常簡單,只需要兩個步驟:

  1. 確保你的管道中已經定義了測試階段(通常這個階段已經存在,所以你不需要做任何修改)。
  2. 包含一個 GitLab 提供的範本檔案 Code-Quality.gitlab-ci.yml,這個檔案會在你的管道中新增一個 Code Quality 工作。

以下是具體操作:

stages:
  - test

include:
  template: Code-Quality.gitlab-ci.yml

如果你已經定義了其他階段或包含了其他範本檔案,只需將 test 階段新增到現有階段中,並將新範本新增到現有範本中即可。

檢視 Code Quality 結果

讓我們看一個具體例子。假設你有一個名為 hats-for-cats.py 的檔案,內容如下:

def register(username, password, phone, city, state, zip):
    # TODO finish this code

這段程式碼有兩個問題:函式引數過多,TODO 評論應該被處理並移除。如果你啟用了 Code Quality 功能並執行管道,管道詳細資訊頁面會顯示一個名為「Code Quality」的新選項卡,展示 Code Quality 掃描結果。

此圖示示範如何展示「Code Quality」結果

此外,你還可以在合併請求中檢視相同的資訊。不過,合併請求中的報告與管道詳細資訊頁面中的報告有一個重要區別:前者顯示的是源分支和目標分支之間的差異。通常目標分支是專案的預設分支(例如 mainmaster),所以報告會告訴你源分支上的工作是否增加了新的品質問題或解決了舊問題。換句話說,它會顯示你的分支提交是使專案的程式碼變好還是變壞。

假設你建立了一個分支並為其提交了一次變更,刪除了 TODO 評論並增加了一個新的 FIXME 評論。你會期望在合併請求報告中看到一個舊問題(TODO)已被解決並增加了一個新問題(FIXME)。這正是合併請求報告中展示的內容。

此圖示展示如何在合併請求中檢視「Code Quality」結果

@startuml
skinparam backgroundColor #FEFEFE

title CI/CD 管線整合程式碼品品檢查與 Fuzzing 測試技術

|開發者|
start
:提交程式碼;
:推送到 Git;

|CI 系統|
:觸發建置;
:執行單元測試;
:程式碼品質檢查;

if (測試通過?) then (是)
    :建置容器映像;
    :推送到 Registry;
else (否)
    :通知開發者;
    stop
endif

|CD 系統|
:部署到測試環境;
:執行整合測試;

if (驗證通過?) then (是)
    :部署到生產環境;
    :健康檢查;
    :完成部署;
else (否)
    :回滾變更;
endif

stop

@enduml

結果解析

無論是在管道詳細資訊頁面還是在合併請求中,每個檢測到的問題都會有相應的條目。這些條目會告訴你每個問題的名稱、檔名和發生位置。這些資訊應該足夠讓你決定是否修復或忽略每個問題。

總結來說,Code Quality 功能是一個強大且易於使用的工具,能夠幫助我們在 CI/CD 管道中自動檢查和改程式式碼品質。透過這些步驟和工具,我們可以確保我們的程式碼更加健壯和高效。

自動化功能測試在 CI/CD 機制中的應用

在現代軟體開發中,持續整合和持續佈署(CI/CD)已成為確保程式碼品質的關鍵工具。其中,自動化功能測試是 CI/CD 中最常見的任務之一,旨在確保程式碼行為符合預期。以下是玄貓對於在 GitLab 中實施自動化功能測試的深度探討。

使用 pytest 框架進行自動化測試

假設你正在開發一個名為「Hats for Cats」的 Python 應用程式,並希望透過 pytest 框架來進行單元測試。以下是如何在 GitLab 中實作這一目標的詳細步驟。

範例測試程式碼

首先,我們需要編寫一些 pytest 基礎的單元測試來驗證應用程式的登入功能。這些測試可能位於 test/test_login.py 檔案中:

def test_login():
    # 新增嘗試使用正確憑證登入的程式碼
    assert True

def test_login_bad_password():
    # 新增嘗試使用錯誤密碼登入的程式碼
    assert True

def test_login_no_password():
    # 新增嘗試不使用密碼登入的程式碼
    assert False

這些測試程式碼僅作為示範用途,實際應用中應包含完整的邏輯來驗證登入功能。

在 CI/CD 機制中執行自動化測試

要在 GitLab 的 CI/CD 機制中執行這些自動化測試,我們需要新增一個工作專案(job)來觸發這些測試。以下是具體的工作專案定義:

unit-tests:
  stage: test
  image: python:3.10
  script:
    - pip install pytest
    - pytest test/

這段組態指定了工作專案的階段為 test,並使用 Python 3.10 的 Docker 命令列環境。首先安裝 pytest 函式庫,然後執行位於 test/ 目錄中的所有單元測試。

支援 JUnit 報告格式

要使測試結果更易於閱讀,我們可以將 pytest 的結果轉換為 JUnit 格式並儲存為 GitLab 的工件(artifact)。以下是如何修改工作專案定義來實作這一目標:

artifacts:
  reports:
    junit: unit_test_results.xml
  when: always

這段組態指示 GitLab 儲存 pytest 生成的 unit_test_results.xml 檔案,並將其標示為包含 JUnit 格式測試結果的報告。即使某些測試失敗,GitLab 仍會保留這個工件以供檢視。

檢視自動化功能測試結果

在執行新的管線例項後,你會在管線詳細頁面上看到一個標示為「Tests」的新選項卡。點選該選項卡即可檢視所有自動化測試的總覽結果:

此圖示展示了每個觸發自動化測試工作專案的結果行數。點選任何行可進一步檢視每個測試的詳細結果,包括哪些測試透過或失敗。

自動化功能測試結果檢視

此圖示展示了每個觸發自動化測試工作專案的結果行數。點選任何行可進一步檢視每個測試的詳細結果,包括哪些測試透過或失敗。

比較不同分支間的測試結果

除了檢視當前分支的測試結果外,GitLab 還允許我們比較不同分支之間的變更。例如,如果你正在修復某些功能且該分支與主分支相比有新增或修復某些測試失敗情況,GitLab 的合併請求報告將顯示相應資訊。

此圖示顯示了合併請求中各分支之間的差異檢視。

Fuzz 測試在 CI/CD 機制中的應用

除了傳統功能測試外,Fuzz 測試是另一種高階技術手段來找出程式碼中的錯誤。Fuzz 測試透過向程式碼函式傳送半隨機資料來嘗試觸發錯誤。然而,Fuzz 清理需要更多設定工作,但它可以幫助發現其他方法可能無法發現的錯誤。

Fuzz 清理架構與工作流程

Fuzz 清理由四個主要元件構成:待測試程式碼、CI/CD 工作項、模糊引擎和模糊目標。以下是每個元件及其相互關係的詳細介紹。

模糊引擎與模糊目標

Fuzz 清理對單個程式碼函式進行針對性攻擊。該函式可以用 GitLab 支援的任何語言編寫,並接受一個或多個引數。模糊引擎會向這些函式傳送隨機資料以嘗試觸發錯誤。

模糊引擎與模糊目標

模糊引擎與模糊目標協同工作以生成隨機輸入資料並監控函式行為。如果發現任何錯誤或意外行為,模糊引擎會報告這些問題以便進一步除錯和修復。

模糊引擎與模糊目標

玄貓認為 Fuzz 清理是一種強大且高階的測試技術,特別適合發現那些傳統測試方法難以發現的潛在問題。透過將其整合到 CI/CD 機制中,開發團隊可以更全面地驗證程式碼品質並提高應用程式的穩定性和安全性。

進行程式碼檢驗

在這個範例中,我們假設有一個 Python 函式,這是我們要進行檢驗的程式碼。這個函式位於一個名為 name_checker.py 的檔案中。

def is_bob(name: str) -> bool:
    if len(name) == 0:
        return False
    return name[0] == 'b' and name[1] == 'o' and name[2] == 'b'

這個簡單的函式接受一個字串作為引數。如果字串為空,則立即傳回 False。否則,如果字串是 “bob”,則傳回 True;如果不是,則傳回 False

函式的缺陷

這個函式的設計明顯有問題。首先,它沒有驗證傳入的字串是否至少有三個字元。因此,如果傳入的字串只有一個字元且是 “b”,或只有兩個字元且是 “bo”,函式在嘗試讀取不存在的第二個或第三個字元時會丟擲 IndexError

使用模糊測試來找出錯誤

模糊測試(fuzz testing)是一種自動化測試技術,它透過向程式碼輸入隨機資料來發現潛在的錯誤。讓我們看看如何在 CI/CD 管線中進行模糊測試。

在 CI/CD 管線中設定模糊測試

首先,我們需要在 .gitlab-ci.yml 中定義一個專門用於模糊測試的工作(job)。以下是設定過程:

  1. 包含範本:在 .gitlab-ci.ymlincludes: 段落中新增範本。
includes:
  - template: Coverage-Fuzzing.gitlab-ci.yml
  1. 定義階段:確保在 .gitlab-ci.yml 中有 fuzz 階段,並且它執行在 buildtest 階段之後。
stages:
  - build
  - test
  - fuzz
  1. 定義模糊測試工作:新增一個模糊測試工作來執行我們的測試。
fuzz-test-is-bob:
  image: python:latest
  extends: .fuzz_base
  script:
    - pip install --extra-index-url https://gitlab.com/api/v4/projects/19904939/packages/pypi/simple pythonfuzz
    - ./gitlab-cov-fuzz run --engine pythonfuzz -- is_bob_fuzz_target.py

這個工作首先指定使用包含最新版 Python 的 Docker 構像。然後,它從 GitLab 託管的套件登入檔安裝一個 Python 基礎的模糊引擎。最後,它執行 gitlab-cov-fuzz 二進位制檔案,指向正確的模糊引擎和目標。

模糊引擎

模糊引擎是一個 GitLab 提供的二進位制檔案,它向模糊目標傳送隨機位元組流。這些位元組用作輸入資料的基礎,模糊目標會將其傳遞給待測程式碼。

模糊目標

模糊目標是一小段程式碼,你必須使用與待測程式碼相同的語言編寫。它作為模糊引擎和待測程式碼之間的翻譯器或中介。模糊目標有兩個任務:

  1. 轉換隨機位元組:將模糊引擎傳送的隨機位元組轉換為待測程式碼期望接收的資料型別。
  2. 呼叫待測函式:將轉換後的隨機位元組傳遞給待測函式。

在這個範例中,模糊目標需要將隨機位元組轉換為字串,然後將該字串傳遞給 is_bob 函式。以下是 is_bob_fuzz_target.py 的內容:

from name_checker import is_bob
from pythonfuzz.main import PythonFuzz

@PythonFuzz
def fuzz(random_bytes):
    try:
        random_bytes_as_string = str(random_bytes, 'UTF-8')
        is_bob(random_bytes_as_string)
    except UnicodeDecodeError:
        pass

if __name__ == '__main__':
    fuzz()

這段程式碼首先使待測程式碼可用於傳遞隨機資料。然後,它定義了一個名為 fuzz 的函式來處理隨機位元組。最後,它嘗試將隨機位元組轉換為字串並傳遞給 is_bob 函式。

內容解密:

  • 從待測程式碼匯入功能:首先匯入 is_bob 函式以便在模糊目標中使用。
  • PythonFuzz 裝飾器:將 fuzz 函式標記為可由 PythonFuzz 引擎呼叫。
  • 處理隨機位元組:將隨機位元組轉換為 UTF-8 編碼的字串並嘗試呼叫 is_bob 函式。
  • 錯誤處理:如果轉換失敗(例如遇到 UnicodeDecodeError),則忽略該錯誤。
  • 主程式入口:確保當指令碼直接執行時可以執行模糊測試。

模糊測試工作流程

以下是整個模糊測試工作流程的一個概述:

  1. 安裝模糊引擎:使用 pip 安裝所需的 Python 模糊引擎。
  2. 執行 gitlab-cov-fuzz:啟動 gitlab-cov-fuzz 二進位制檔案並指定正確的模糊引擎和目標。
  3. 生成隨機資料:模糊引擎生成隨機位元組並將其傳送給模糊目標。
  4. 轉換並呼叫函式:模糊目標將隨機位元組轉換為所需格式並呼叫待測函式。
  5. 檢查錯誤:如果發現錯誤(如 IndexError),則記錄並進行調查。

結果分析

經過一段時間後,我們可以檢視測試結果來分析哪些輸入導致了錯誤。例如:

  • 長度小於三的輸入可能會導致 IndexError
  • 包含非 ASCII 字元的輸入可能會導致 UnicodeDecodeError

未來趨勢與改進建議

未來的趨勢可能包括更先進的程式碼覆寫率分析和自動化修復工具。此外,可以考慮使用更多樣化的資料集來進行更全面的測試。

差異化觀點

玄貓認為,雖然模糊測試在發現潛在錯誤方面非常有效,但它也需要結合靜態分析和單元測試來提供更全面的保障。此外,定期更新和維護模糊測試指令碼以適應新增功能和變更也是至關重要的一環。

以上就是如何透過 CI/CD 管線中的模糊測試來驗證您的程式碼。