GitHub Actions 自動化測試流程

在現代軟體開發中,持續整合(Continuous Integration, CI)已經不是一個「選項」,而是一個「必需品」。它能確保我們每次提交的程式碼都經過自動化測試,及早發現問題,從而提升整個團隊的開發品質與效率。對於專案來說,一個好的 CI 流程更是不可或缺。

我們將深入解析一個為 Django 專案設計的 GitHub Actions 工作流程。這個流程不僅涵蓋了基本的測試,還巧妙地結合了原生 Python 環境與 Docker Compose 環境,實現了快速回饋與高擬真度測試的雙重保障。

GitHub Actions 核心概念

在深入程式碼之前,先快速了解幾個 GitHub Actions 的核心概念:

  • Workflow(工作流程): 由一個或多個 job 組成,定義了整個自動化過程。通常以 YAML 檔案形式存放在專案的 .github/workflows/ 目錄下。
  • Event(事件): 觸發工作流程的特定活動,例如 push(推送到儲存庫)或 pull_request(發起拉取請求)。
  • Job(任務): 工作流程中的一個執行單元,會在一台虛擬機(runner)上執行。一個工作流程可以有多個任務,它們可以並行或依序執行。
  • Step(步驟): 任務中的一個獨立指令或動作。一個任務由多個步驟組成。
  • Action(動作): 可重用的程式碼單元,是建構步驟的積木。例如 actions/checkout 就是一個官方提供的 Action,用來拉取你的程式碼。

工作流程檔案解析 (Django CI Tests)

讓我們來看看這次的主角,這個精心設計的 Django CI Tests 工作流程。

name: Django CI Tests

on: [push, pull_request]

jobs:
  # ... 兩個 jobs 的定義將在下面詳細解說 ...
  • name: Django CI Tests:為這個工作流程取一個易於辨識的名字。
  • on: [push, pull_request]:這行是關鍵!它定義了觸發條件。這意味著,每當有新的程式碼被推送到任何分支,或者當有人發起一個 Pull Request 時,這個自動化測試流程就會被啟動。

這個工作流程包含了兩個並行執行的任務(Jobs):native-pytestdocker-compose-pytest。讓我們逐一拆解它們的設計理念與實現細節。


任務一:native-pytest - 追求極速的原生 Python 測試

第一個任務的目標是「快」。它在一個乾淨的 Ubuntu 環境中直接設定 Python,並執行測試。這適用於那些不依賴外部服務(如資料庫、快取)的單元測試,能夠在最短時間內給予開發者回饋。

  native-pytest:
    name: Native Python Pytest
    runs-on: ubuntu-latest
    defaults:
      run:
        working-directory: backend

    env:
      SECRET_KEY: ${{ secrets.SECRET_KEY }}
      DEBUG: "True"
      USE_POSTGRES: "False" # 關鍵點:不使用 PostgreSQL
      ALLOWED_HOSTS: localhost,127.0.0.1
      SOCIAL_AUTH_GOOGLE_CLIENT_ID: ${{ secrets.SOCIAL_AUTH_GOOGLE_CLIENT_ID }}
      SOCIAL_AUTH_GOOGLE_CLIENT_SECRET: ${{ secrets.SOCIAL_AUTH_GOOGLE_CLIENT_SECRET }}

    steps:
      - uses: actions/checkout@v4

      - name: Set up Python
        uses: actions/setup-python@v5
        with:
          python-version: "3.13"

      - name: Install uv
        run: pip install uv

      - name: Install backend dependencies
        run: uv sync

      - name: Run migrations
        run: uv run python manage.py migrate

      - name: Run pytest
        run: uv run pytest -v --tb=short --maxfail=5 --disable-warnings

拆解步驟:

  1. 基本設定:

    • runs-on: ubuntu-latest: 指定任務運行在最新版的 Ubuntu 虛擬機上。
    • defaults.run.working-directory: backend: 這是一個很方便的設定,它將後續所有 run 指令的預設工作目錄都設定為 backend,省去了在每個步驟中都 cd backend 的麻煩。
  2. 環境變數 (env):

    • 這裡設定了 Django 運作所需的環境變數。
    • SECRET_KEY 等敏感資訊透過 ${{ secrets.SECRET_KEY }} 從 GitHub 的 Secrets 中安全地讀取,避免了硬編碼在程式碼中。
    • 最重要的設定是 USE_POSTGRES: "False"。這通常會告訴 Django 的 settings.py 使用輕量級的 SQLite 作為測試資料庫,因為它不需要額外的服務,啟動速度極快。
  3. 執行步驟 (steps):

    • actions/checkout@v4: 第一步總是它!這個 Action 會將你的專案程式碼拉取到虛擬機中。
    • setup-python@v5: 設定指定的 Python 版本(這裡用了最新的 3.13!)。
    • Install uv & uv sync: 這裡使用 uv 這個新一代的超高速 Python 套件管理器來取代傳統的 pipuv sync 會根據 pyproject.tomlrequirements.txt 檔案來安裝所有依賴,速度飛快。
    • uv run python manage.py migrate: 在執行測試前,先執行資料庫遷移。對於 SQLite 來說,這會在記憶體或一個暫存檔案中建立資料庫結構。
    • uv run pytest ...: 核心步驟!uv run 會在 uv 管理的虛擬環境中執行 pytest
      • -v: 顯示詳細的測試結果。
      • --tb=short: 使用簡潔的錯誤追蹤格式。
      • --maxfail=5: 當有 5 個測試失敗時就立即停止,節省時間。
      • --disable-warnings: 隱藏不影響功能的警告,讓輸出更乾淨。

小結:native-pytest 的優勢是速度快、設定簡單,適合快速驗證程式碼邏輯。


任務二:docker-compose-pytest - 模擬生產環境的高擬真度測試

第二個任務的目標是「擬真」。它使用 Docker Compose 來啟動整個應用程式環境,包括 Django 應用本身和 PostgreSQL 資料庫。這確保了測試環境與你的開發環境,甚至生產環境,都盡可能地保持一致。

  docker-compose-pytest:
    name: Docker Compose Pytest
    runs-on: ubuntu-latest

    steps:
      - uses: actions/checkout@v4

      - name: Create backend .env file
        run: |
          echo "SECRET_KEY=${{ secrets.SECRET_KEY }}" > backend/.env
          # ... 其他 echo 指令 ...
          echo "USE_POSTGRES=True" >> backend/.env
          echo "POSTGRES_HOST=db" >> backend/.env
          # ...

      - name: Build docker-compose services
        run: docker compose -f docker-compose.dev.yml build

      - name: Run pytest in docker-compose
        run: docker compose -f docker-compose.dev.yml run --rm backend uv run pytest -v --tb=short --maxfail=5 --disable-warnings

      - name: Check container logs on failure
        if: failure()
        run: docker compose -f docker-compose.dev.yml logs backend

      - name: Shutdown docker-compose
        run: docker compose -f docker-compose.dev.yml down -v

拆解步驟:

  1. 建立 .env 檔案:

    • 這是此任務中最巧妙的一步。Docker Compose 通常會讀取一個 .env 檔案來設定容器的環境變數。
    • 這個步驟動態地建立了一個 backend/.env 檔案,並將 GitHub Secrets 和其他設定值寫入其中。
    • 注意這裡的關鍵差異USE_POSTGRES 被設為 "True",並且新增了所有 POSTGRES_* 相關的變數,例如資料庫主機 POSTGRES_HOST=dbdbdocker-compose.dev.yml 中定義的資料庫服務名稱)。
  2. 建置與執行 Docker Compose:

    • docker compose ... build: 根據 docker-compose.dev.yml 和相關的 Dockerfile 來建置服務的映像檔。
    • docker compose ... run --rm backend ...: 這是執行測試的核心指令。
      • run backend: 告訴 Docker Compose 啟動 backend 服務(以及它所依賴的 db 服務),並在 backend 容器內執行後續的指令。
      • --rm: 表示指令執行完畢後,自動刪除這個臨時建立的容器,保持環境乾淨。
      • 後面的 uv run pytest ... 指令與任務一相同,但這次,它是在一個包含完整資料庫連線的 Docker 容器內執行的。
  3. 偵錯與清理:

    • if: failure(): 這是 GitHub Actions 的一個強大功能。這個步驟只會在前面的步驟失敗時才會執行。
    • docker compose ... logs backend: 如果測試失敗,這個指令會印出 backend 容器的日誌,這對於排查連線問題或環境錯誤非常有幫助。
    • docker compose ... down -v: 無論成功或失敗,最後都要執行清理工作。down 會關閉並移除所有容器和網路,-v 則會一併刪除相關的 volumes(例如資料庫數據),確保每次執行都是從零開始的乾淨狀態。

小結:docker-compose-pytest 的優勢是環境擬真度高,能測試應用與資料庫等外部服務的整合,確保程式碼在類生產環境下也能正常運作。

為什麼需要兩種測試策略?

這個工作流程最精彩的設計,就是同時採用了這兩種策略:

  • 快速回饋:開發者推送一個小修改後,native-pytest 會在 1-2 分鐘內給出結果。如果只是修改了一個不涉及資料庫的函式,可以很快知道有沒有改壞。
  • 深度驗證docker-compose-pytest 雖然慢一些(可能需要 3-5 分鐘),但它提供了更全面的保障。它能捕捉到原生測試無法發現的問題,例如錯誤的資料庫查詢、環境變數設定錯誤等。

當你在 GitHub 上看到一個 Pull Request 時,這兩個任務會並行執行。你將會看到兩個綠色的勾勾,這代表你的程式碼不僅通過了快速的單元測試,也通過了嚴苛的整合測試,讓你有十足的信心去合併它。

總結

透過這個 Django CI Tests 工作流程,學習到如何利用 GitHub Actions 建立一個高效且可靠的自動化測試流程。它結合了:

  1. 事件驅動的自動化 (on: [push, pull_request])。
  2. 使用 GitHub Secrets 安全管理敏感資訊。
  3. 利用原生環境實現快速測試。
  4. 利用 Docker Compose 模擬生產環境進行整合測試。
  5. 巧妙的條件執行 (if: failure()) 來輔助偵錯。

將這樣的 CI 流程整合到專案中,無疑會大大提升開發品質與部署信心!