【第2回】GitHub Actions × Python 自動化入門 — テストの自動化(pytest連携とカバレッジレポートの設定)

プログラミング

前回は GitHub Actions の基本的なワークフロー構文と、Pythonプロジェクトでの最初のCI設定を紹介しました(第1回はこちら)。今回はその続きとして、pytest と連携したテスト自動実行と、カバレッジレポートの取り扱いまで踏み込んでみます。

正直、カバレッジをCIで確認する設定って「なんとなくできそう」と思いつつ後回しにしがちなんですよね。自分もそのひとりでした。ある日コードレビュー中に「カバレッジどこで見れますか?」と聞かれて焦ったのが今回調べるきっかけです。

  • GitHub Actions で pytest を自動実行するワークフローの書き方
  • pytest-cov を使ったカバレッジ計測の設定方法
  • カバレッジレポートを Artifact として保存する手順
  • カバレッジが閾値を下回ったら CI を落とす設定
  • matrix 構文で複数の Python バージョンに対応する方法

前提となる構成

今回想定するディレクトリ構成はこんな感じです。

my-project/
├── src/
│   └── calculator.py
├── tests/
│   └── test_calculator.py
├── requirements.txt
└── .github/
    └── workflows/
        └── test.yml

src/ 配下にソースコード、tests/ にテストを置くシンプルな構成。pytest はデフォルトで test_*.py*_test.py のパターンのファイルを収集して、さらに関数名は test プレフィックスを見て実行してくれるので、命名規則さえ守れば特別な設定はほぼ不要です。

サンプルとして使うコードも軽く載せておきます。

# src/calculator.py
def add(a, b):
    return a + b

def divide(a, b):
    if b == 0:
        raise ValueError("0で割ることはできません")
    return a / b
# tests/test_calculator.py
import pytest
from src.calculator import add, divide

def test_add():
    assert add(2, 3) == 5

def test_divide():
    assert divide(10, 2) == 5.0

def test_divide_by_zero():
    with pytest.raises(ValueError):
        divide(1, 0)

基本のワークフロー:pytest を動かすだけ

まずシンプルな形から。push や PR のタイミングでテストを走らせる最小構成です。

# .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'
          cache: 'pip'  # ここ注意:依存キャッシュが効くと体感かなり速くなる

      - name: Install dependencies
        run: |
          python -m pip install --upgrade pip
          pip install -r requirements.txt
          pip install pytest

      - name: Run tests
        run: pytest tests/

actions/setup-python@v5cache: 'pip' を指定すると、pip の依存関係をキャッシュしてくれるので、依存が変わってないときはインストールが速くなりやすいです。

余談ですが、actions/checkout@v4 は現時点でも普通に現役で使われています。バージョン番号を見るたびに「最新どれだっけ」と確認しがちなので、使う前にリリースページをチラ見するのが安心かもしれません。

カバレッジレポートを追加する

pytest-cov を使うことでカバレッジの計測が簡単にできます。install コマンドに追加して、pytest の実行コマンドにオプションを足すだけです。

      - 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 tests/ \
            --cov=src \
            --cov-report=term-missing \
            --cov-report=xml \
            --cov-report=html

オプションの意味をざっくり整理すると:

  • --cov=src:カバレッジを計測する対象ディレクトリ
  • --cov-report=term-missing:ターミナルにカバーされていない行番号を出力
  • --cov-report=xml:外部ツール連携用のXMLファイルを生成(デフォルトは coverage.xml。Cobertura互換形式として扱われることが多いです)
  • --cov-report=html:人が見やすいHTMLレポートを生成(htmlcov/ フォルダ)

ちなみに --junitxml というオプションもありますが、こちらは「テスト結果」をJUnit XML形式で出力するもので、カバレッジとは別物です。名前が似てて最初混乱しました。

カバレッジレポートをArtifactとして保存する

HTMLレポートはローカルだと htmlcov/ フォルダを開けば見られますが、CIだとそのまま消えてしまいます。actions/upload-artifact を使うことで、実行ごとのHTMLレポートをGitHub上にアップロードして保存できます。

      - name: Run tests with coverage
        run: |
          pytest tests/ \
            --cov=src \
            --cov-report=term-missing \
            --cov-report=xml \
            --cov-report=html

      - name: Upload coverage report
        uses: actions/upload-artifact@v4
        with:
          name: coverage-report
          path: htmlcov/
          retention-days: 7

ワークフローが完了した後、ActionsのUIから「Artifacts」としてダウンロードできるようになります。retention-days で保存期間も設定できます。ただ、リポジトリの種別(public/private)や組織の設定によって上限が変わることがあるので、「設定できるけど環境次第」くらいに思っておくのが無難かもしれません。

カバレッジが一定以下だったらCIを落とす

ここが個人的に一番「あ、これ使える」と思った設定で、--cov-fail-under=80 を指定するとカバレッジが閾値を下回ったときにCIを失敗させることができます。

      - name: Run tests with coverage
        run: |
          pytest tests/ \
            --cov=src \
            --cov-report=term-missing \
            --cov-report=xml \
            --cov-fail-under=80

この例だとカバレッジが80%を下回ると CI が落ちます。チームで開発しているときにPRの品質ゲートとして機能させるイメージです。個人プロジェクトでもこれを設定しておくと「テスト書き忘れてた」に気づくきっかけになります。

ただ、最初から高い閾値を設定しすぎると既存コードで詰まるので、最初は60〜70%あたりから始めて徐々に上げていくのが現実的だと思います(自分はそうしてます)。

複数バージョンのPythonでテストする(matrix)

ライブラリを作ってたり、複数環境での動作確認が必要な場合は strategy.matrix を使うと一度に複数バージョンのテストが走らせられます。

jobs:
  test:
    runs-on: ubuntu-latest
    strategy:
      matrix:
        python-version: ["3.11", "3.12", "3.13"]

    steps:
      - uses: actions/checkout@v4

      - name: Set up Python ${{ matrix.python-version }}
        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
        run: pytest tests/ --cov=src --cov-report=term-missing

matrix 構文を使うと、指定したPythonバージョンを並列で一度にテストできます。個人のスクリプトなら正直オーバーエンジニアリング気味ですが、パッケージを公開するなら入れておいた方が安心感が違います。

まとめ

pytest + pytest-cov の組み合わせでカバレッジ計測まで含めた自動テストをGitHub Actionsに乗せるのは、意外とシンプルなコードで実現できます。今回カバーした内容を整理すると:

  • push / PR のタイミングで pytest を自動実行するワークフローの基本形
  • pytest-cov を追加してカバレッジを計測・レポート出力
  • upload-artifact でHTMLレポートをCI上に保存
  • --cov-fail-under でカバレッジ閾値を設けてCIの品質ゲートに
  • strategy.matrix で複数Pythonバージョンに対応

--cov-fail-under によるカバレッジ閾値の設定が個人的にはいちばん実用度が高かったです。この段階まで作ると、逆に「テストがないと落ち着かない」みたいな気持ちになってくるのが不思議です。自分だけかな…

タイトルとURLをコピーしました