GitHub Actions と pytest を組み合わせると、コードをプッシュするたびに自動でテストが走る CI 環境を無料で構築できます。
本記事では、ゼロから始める workflow ファイルの書き方から、matrix build・pip キャッシュ・artifact 保存まで、実務で使える設定をまとめました。
1. GitHub Actions で Python テストを自動化するメリット
CI/CD パイプラインを整備すると、以下の効果が得られます。
- プッシュ・PR のたびにテストが自動実行され、バグの混入を早期に検知できる
- 複数の Python バージョンへの対応状況を並行確認できる
- テスト結果がリポジトリ上に保存され、チームで共有できる
- GitHub Actions はパブリックリポジトリであれば無料で利用できる
2. ディレクトリ構成と pytest 基本設定
本記事で想定するディレクトリ構成は以下のとおりです。
my_project/
├── .github/
│ └── workflows/
│ └── test.yml
├── src/
│ └── calculator.py
├── tests/
│ ├── __init__.py
│ └── test_calculator.py
├── pyproject.toml
└── requirements.txt
pytest の設定(pyproject.toml)
pytest はプロジェクトルートの pyproject.toml で設定を管理するのが現在の推奨方法です(公式ドキュメントより)。
[tool.pytest.ini_options]
minversion = "6.0"
addopts = ["-ra", "-q", "--strict-markers"]
testpaths = ["tests"]
addopts = ["-ra", "-q"]:失敗・スキップしたテストの要約を表示し、出力を簡潔にするtestpaths = ["tests"]:テストを探索するディレクトリを明示する--strict-markers:未定義のマーカー使用時にエラーにする
サンプルの実装コードとテストコード
テスト対象(src/calculator.py):
def add(a: float, b: float) -> float:
return a + b
def divide(a: float, b: float) -> float:
if b == 0:
raise ValueError("Cannot divide by zero")
return a / b
テストコード(tests/test_calculator.py):
import pytest
from src.calculator import add, divide
def test_add():
assert add(1, 2) == 3
assert add(-1, 1) == 0
def test_divide():
assert divide(10, 2) == 5.0
def test_divide_by_zero():
with pytest.raises(ValueError, match="Cannot divide by zero"):
divide(1, 0)
3. 基本的な workflow ファイル(.github/workflows/test.yml)
まず最小構成の workflow から始めます。
# .github/workflows/test.yml
name: Python Tests
on:
push:
branches: [ "main", "develop" ]
pull_request:
branches: [ "main" ]
jobs:
test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Set up Python
uses: actions/setup-python@v5
with:
python-version: '3.12'
- name: Install dependencies
run: |
python -m pip install --upgrade pip
pip install -r requirements.txt
pip install pytest pytest-cov
- name: Run tests
run: pytest
このファイルを .github/workflows/test.yml として配置すると、main と develop ブランチへのプッシュ時および main への PR 作成時にテストが実行されます。
4. pip キャッシュで依存関係インストールを高速化
GitHub Actions は実行のたびにクリーンな環境を用意するため、pip install が毎回走ります。actions/setup-python の cache オプションを使うと、requirements.txt に変更がない限りキャッシュが再利用されます。
- name: Set up Python
uses: actions/setup-python@v5
with:
python-version: '3.12'
cache: 'pip'
GitHub 公式ドキュメントでは、このシンプルな設定が推奨されています。requirements.txt の内容をハッシュ化してキャッシュキーが自動生成されるため、依存関係が変わったときは自動的にキャッシュが更新されます。
キャッシュの詳細設定については、以下の記事も参考にしてください。
→ GitHub Actions キャッシュ完全ガイド2026【npm・pip高速化・実装一覧・失敗しない設定】
5. matrix build で複数 Python バージョンをテスト
ライブラリを公開する場合や、チームで異なる Python バージョンを使っている場合は、matrix build で複数バージョンを並行テストできます。
strategy:
matrix:
python-version: ["3.9", "3.10", "3.11", "3.12"]
fail-fast: false
steps:
- uses: actions/checkout@v4
- name: Set up Python
uses: actions/setup-python@v5
with:
python-version: ${{ matrix.python-version }}
cache: 'pip'
fail-fast: false を設定することで、Python 3.9 でテストが失敗しても Python 3.12 のテスト結果も取得できます。特定の組み合わせを除外したい場合は exclude を使います。
6. テスト結果を artifact として保存する
テスト結果を JUnit XML 形式で出力し、artifact として保存すると後から確認・ダウンロードできます。
- name: Run tests with coverage
run: |
pytest \
--junitxml=junit/test-results.xml \
--cov=src \
--cov-report=xml:coverage.xml
- name: Upload test results
uses: actions/upload-artifact@v4
with:
name: pytest-results
path: junit/test-results.xml
if: ${{ always() }}
- name: Upload coverage report
uses: actions/upload-artifact@v4
with:
name: coverage-report
path: coverage.xml
if: ${{ always() }}
if: ${{ always() }} は GitHub Actions 公式ドキュメントで推奨されている設定です。テストが失敗した場合でも artifact が保存されるため、失敗の原因調査に使えます。
7. 完成版 workflow ファイル全文
# .github/workflows/test.yml
name: Python Tests
on:
push:
branches: [ "main", "develop" ]
pull_request:
branches: [ "main" ]
jobs:
test:
runs-on: ubuntu-latest
strategy:
matrix:
python-version: ["3.9", "3.10", "3.11", "3.12"]
fail-fast: false
steps:
- uses: actions/checkout@v4
- name: Set up Python
uses: actions/setup-python@v5
with:
python-version: ${{ matrix.python-version }}
cache: 'pip'
- name: Install dependencies
run: |
python -m pip install --upgrade pip
pip install -r requirements.txt
pip install pytest pytest-cov
- name: Run tests with coverage
run: |
pytest \
--junitxml=junit/test-results-${{ matrix.python-version }}.xml \
--cov=src \
--cov-report=xml:coverage-${{ matrix.python-version }}.xml
- name: Upload test results
uses: actions/upload-artifact@v4
with:
name: pytest-results-${{ matrix.python-version }}
path: junit/test-results-${{ matrix.python-version }}.xml
if: ${{ always() }}
- name: Upload coverage report
uses: actions/upload-artifact@v4
with:
name: coverage-${{ matrix.python-version }}
path: coverage-${{ matrix.python-version }}.xml
if: ${{ always() }}
8. よくあるエラーと対処法
ModuleNotFoundError: No module named ‘src’
テスト実行時に src パッケージが見つからないエラーが出る場合は、PYTHONPATH を設定します。
- name: Run tests
run: pytest
env:
PYTHONPATH: ${{ github.workspace }}
キャッシュが効かない
requirements.txt が存在しない場合、cache: 'pip' オプションはキャッシュキーを生成できず警告が出ます。requirements.txt を用意するか、pyproject.toml に依存関係を記載してください。
matrix build の一部が常に失敗する
特定バージョンのみ失敗する場合は exclude で一時的に除外して原因を調査します。fail-fast: false にしていれば他バージョンの結果は取得できます。
関連記事
まとめ
- 基本構成:checkout → setup-python → pip install → pytest
- pip キャッシュ:
setup-pythonのcache: 'pip'で自動設定 - matrix build:
strategy.matrix.python-versionで複数バージョンを並行テスト - artifact 保存:
upload-artifact@v4とif: always()で失敗時も結果を保存
まずは完成版 workflow ファイルをそのままコピーして、自分のリポジトリに配置してみてください。