GitHub Actions 自動測試
持續整合(Continuous Integration, CI)是指每次 push 或開 Pull Request 時,自動在乾淨的環境裡執行測試。用 GitHub Actions 跑 pytest 只需要在 repo 加一個 YAML 檔,就能確保「我電腦上會過」的程式在別的環境也會過,並在合併前擋下壞掉的修改。
最小可用的工作流程
在 repo 建立 .github/workflows/test.yml:
.github/workflows/test.yml
name: test
on:
push:
branches: [main]
pull_request:
jobs:
pytest:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v5
- name: Set up uv
uses: astral-sh/setup-uv@v6
with:
enable-cache: true
- name: Install dependencies
run: uv sync --frozen
- name: Run tests
run: uv run pytest
每一步的作用:
actions/checkout:把 repo 程式碼取出到執行環境。astral-sh/setup-uv:安裝 uv,並快取下載過的套件以加快下次執行。uv sync --frozen:依uv.lock安裝完全相同版本的依賴;--frozen表示 lock 檔與pyproject.toml不一致時直接失敗,而不是偷偷更新。uv run pytest:執行測試。pytest 有測試失敗時結束代碼不為 0,GitHub 就會把這次執行標成失敗(紅色叉叉)。
在多個 Python 版本上測試
套件需要支援多個 Python 版本時,用矩陣(matrix)同時跑:
jobs:
pytest:
runs-on: ${{ matrix.os }}
strategy:
fail-fast: false
matrix:
os: [ubuntu-latest, windows-latest]
python-version: ["3.10", "3.12", "3.14"]
steps:
- uses: actions/checkout@v5
- uses: astral-sh/setup-uv@v6
with:
python-version: ${{ matrix.python-version }}
- run: uv sync --frozen
- run: uv run pytest
fail-fast: false 讓某個組合失敗時,其他組合仍然跑完,方便一次看出是哪些版本有問題。
加上覆蓋率門檻
搭配 pytest-cov,覆蓋率低於門檻時讓 CI 失敗:
- name: Run tests with coverage
run: uv run pytest --cov=my_package --cov-report=term-missing --cov-fail-under=80
門檻建議從專案目前的覆蓋率開始,再逐步提高,避免一開始就訂一個做不到的數字。
實用建議
- 在 repo 設定分支保護:GitHub 的 branch protection 可以要求 CI 通過才能合併 Pull Request。
- 慢的測試分開跑:用
-m "not slow"讓每次 push 快速回饋,另外排程(on: schedule)每天跑一次完整測試。 - 失敗先在本機重現:CI 失敗時,用同樣的指令(
uv sync --frozen加uv run pytest)在本機執行,通常能重現問題。
推薦影音
Automated Testing in Python with pytest, tox, and GitHub Actions(mCoding)
mCoding 約 27 分鐘的完整示範:把一個普通專案整理成可安裝的套件,加入 pytest 測試、mypy 型別檢查與 flake8 程式碼檢查,再用 tox 與 GitHub Actions 在多個作業系統與 Python 版本上自動執行。工具選擇與本頁略有不同(使用 tox 而非 uv),但整體流程與觀念相同。