⛏️

PRの変更差分に対してミューテーションテストをするPythonプロジェクト用のGitHub Actionsを作った話

2024/04/15に公開

ミューテーションテスト(Mutation test)とは

ソースコードの一部を故意に変更して、バグを混入させた状態で単体テストを動かし、テストが失敗するかどうかを確認するテスト手法のことです。

  • テストが失敗した場合
    • バグを検知できているので、該当箇所はテストで担保されていると言えます。
  • テストが成功した場合
    • バグを検知できていないので、該当箇所はテストで担保されていないと言えます。

ミューテーションテストにおいて、検知できたバグのことをKilled、検知できなかったバグのことをSurvivedと読んでおり、Survivedの数が多いほど、テストで担保できていない箇所が多く、品質に問題がある可能性があります。

ミューテーションテストのメリット

見かけ上のコードカバレッジが高く、テストの網羅率が高かったとしても、本当にテストが効果的に機能しているか(バグを検知できているか)は分からないので、ミューテーションテストによって、検知できないバグがどれくらいあるのか担保すべき箇所を正しく担保できているかを確認することができます。

作ったアクションの概要

PRを出した際、ソースコードで変更した行に対してミューテーションテストを実行し、KilledSurvivedの数や、ファイル内のSurvivedの該当箇所をPRにコメントする、以下のアクションを作りました。

https://github.com/marketplace/actions/python-mutation-report

実際にコメントしているPRの例

↓以下のようにコメントします。

↓開くとKilledSurvivedの数や、ファイル内のSurvivedの該当箇所を見れます。

使い方

以下のように、リポジトリをチェックアウトしてから、プロジェクトのlockファイルのパスや、ソース/テスト/テスト実行場所の各ディレクトリ、Pythonのバージョンを指定して、アクションを実行するだけです。

on:
  pull_request:
    paths:
      - "python-project/src/**"
      - "python-project/tests/**"
      - "python-project/Pipfile.lock"

jobs:
  mutation-testing-report:
    runs-on: ubuntu-latest
    name: Mutation testing report
    timeout-minutes: 15
    permissions:
      pull-requests: write
      contents: read

    steps:
      - uses: actions/checkout@v4

      - uses: Century-ss/python-mutation-report@v2
        with:
          lock-file-path: "python-project/Pipfile.lock"
          src-directory: "python-project/src"
          test-directory: "python-project/tests"
          where-to-run-test: "python-project" # ルートディレクトリでテストを動かす場合は指定不要
          python-version: "3.11" # デフォルトは3.11 

lockファイルは、Pip・Pipenv・Ryeの各プロジェクトに対応しています。
テストランナーはPytestのみに対応しています。

まとめ

PRの変更差分に対してどこがテストで担保できていないかをすぐに把握できるのは、テスト品質を担保するのに役立つと思います。
「コードカバレッジは高いんだけど、最近バグが多いんだよな🤔」という人は、ぜひ使ってみてください。

Discussion