pytestでgit diffの差分があるファイルだけをテストしてみる

git diffでファイル名を取得し、重複を省いてpytestを実行します。
2024.04.30

PythonでLambdaを書いているとき、pytestを利用してUnit TestやAPIに対するE2Eテストを実施しています。 Lambdaが多くなるほど、実施するテストも増えます。 実施するテストが増えると、1回あたりのCI/CDの稼働時間も増えます。

このCI/CDの稼働時間を減らすために、差分があるLambdaのテストだけを実施する仕組みを考えてみました。 (ALLのテストはデイリーで実施する想定です。)

おすすめの方

  • pytestでgit diffの差分があるファイルだけをテストしてみたい方

前提

次のフォルダ構成とします。「handlers/xxx」とペアになるe2eテストファイルがあります。

├── src
│   └── handlers
│       ├── xxx
│       │   └── app.py
│       └── yyy
│           └── app.py
└── tests
    └── e2e
        ├── test_xxx.py
        └── test_yyy.py

まずは、普通にE2E Testを実行する

Pythonコード(src)

用意はしますが、ダミーです。

src/handlers/xxx/app.py

src/handlers/xxx/app.py

def message():
    return "this is xxx/app.py"

src/handlers/yyy/app.py

src/handlers/yyy/app.py

def message():
    return "this is yyy/app.py"

Pythonコード(e2e test)

実際はデプロイしたAPIのエンドポイントなどを叩く想定ですが、ここでは「assert True」としておきます。

tests/e2e/test_xxx.py

tests/e2e/test_xxx.py

def test_message():
    assert True

tests/e2e/test_yyy.py

tests/e2e/test_yyy.py

def test_message():
    assert True

E2E Testを実行する

次のコマンドで実行すると、2つのE2E Testが実行されます。(結果は一部抜粋しています)

pytest tests/e2e
collected 2 items

tests/e2e/test_xxx.py .                                                  [ 50%]
tests/e2e/test_yyy.py .                                                  [100%]

============================== 2 passed in 0.01s ===============================

git diffで差分のあるファイルだけテストする

テスト用のスクリプト

git diffで「src/handlers配下」と「tests/e2e配下」の差分ファイル名を取得し、テストファイルを特定し、重複を削除して、pytestを実行します。 スクリプト力が高くないので散らかってますが……。

unit-test-diff.sh

#!/bin/bash

git diff main --name-only
echo ---

# src/handlers配下の差分ファイル名を取得する
src_files=$(git diff main --name-only | grep "^src/handlers/.*\.py$")

# 対応するテストファイル名を取得する
src_test_files=$(for file in $src_files; do echo $file | sed -E 's|src/handlers/([^/]+)/([^/]+\.py)$|tests/e2e/test_\1.py|'; done)

# tests/e2e配下の差分ファイル名を取得する
e2e_test_files=$(git diff main --name-only | grep "^tests/e2e/.*\.py$")

# 重複を削除する
target_files=$((echo $src_test_files; echo $e2e_test_files) | tr ' ' '\n' | sort -u)

# テストファイルがない場合は終了する
if [ -z "$target_files" ]; then
  echo "No test files"
  exit 0
fi

pytest $target_files

テストを実行する

diffなし

  • src/handlers/xxx/app.py: 差分なし
  • src/handlers/yyy/app.py: 差分なし
  • tests/e2e/test_xxx.py: 差分なし
  • tests/e2e/test_yyy.py: 差分なし
No test files

diffあり(src配下)

  • src/handlers/xxx/app.py: 差分あり
  • src/handlers/yyy/app.py: 差分なし
  • tests/e2e/test_xxx.py: 差分なし
  • tests/e2e/test_yyy.py: 差分なし
collected 1 item                                                               

tests/e2e/test_xxx.py .                                                  [100%]

diffあり(src配下)

  • src/handlers/xxx/app.py: 差分なし
  • src/handlers/yyy/app.py: 差分あり
  • tests/e2e/test_xxx.py: 差分なし
  • tests/e2e/test_yyy.py: 差分なし
collected 1 item                                                               

tests/e2e/test_yyy.py .                                                  [100%]

diffあり(src配下)

  • src/handlers/xxx/app.py: 差分あり
  • src/handlers/yyy/app.py: 差分あり
  • tests/e2e/test_xxx.py: 差分なし
  • tests/e2e/test_yyy.py: 差分なし
collected 2 items                                                              

tests/e2e/test_xxx.py .                                                  [ 50%]
tests/e2e/test_yyy.py .                                                  [100%]

diffあり(tests配下)

  • src/handlers/xxx/app.py: 差分なし
  • src/handlers/yyy/app.py: 差分なし
  • tests/e2e/test_xxx.py: 差分あり
  • tests/e2e/test_yyy.py: 差分なし
collected 1 item                                                               

tests/e2e/test_xxx.py .                                                  [100%]

diffあり(tests配下)

  • src/handlers/xxx/app.py: 差分なし
  • src/handlers/yyy/app.py: 差分なし
  • tests/e2e/test_xxx.py: 差分なし
  • tests/e2e/test_yyy.py: 差分あり
collected 1 item                                                               

tests/e2e/test_yyy.py .                                                  [100%]

diffあり(tests配下)

  • src/handlers/xxx/app.py: 差分なし
  • src/handlers/yyy/app.py: 差分なし
  • tests/e2e/test_xxx.py: 差分あり
  • tests/e2e/test_yyy.py: 差分あり
collected 2 items                                                              

tests/e2e/test_xxx.py .                                                  [ 50%]
tests/e2e/test_yyy.py .                                                  [100%]

diffあり(src&tests配下)

  • src/handlers/xxx/app.py: 差分あり
  • src/handlers/yyy/app.py: 差分なし
  • tests/e2e/test_xxx.py: 差分あり
  • tests/e2e/test_yyy.py: 差分なし
collected 1 item                                                               

tests/e2e/test_xxx.py .                                                  [100%]

diffあり(src&tests配下)

  • src/handlers/xxx/app.py: 差分あり
  • src/handlers/yyy/app.py: 差分なし
  • tests/e2e/test_xxx.py: 差分なし
  • tests/e2e/test_yyy.py: 差分あり
collected 2 items                                                              

tests/e2e/test_xxx.py .                                                  [ 50%]
tests/e2e/test_yyy.py .                                                  [100%]

diffあり(src&tests配下)

  • src/handlers/xxx/app.py: 差分あり
  • src/handlers/yyy/app.py: 差分あり
  • tests/e2e/test_xxx.py: 差分あり
  • tests/e2e/test_yyy.py: 差分あり
collected 2 items                                                              

tests/e2e/test_xxx.py .                                                  [ 50%]
tests/e2e/test_yyy.py .                                                  [100%]

さいごに

git diffコマンドで差分があるファイルのテストを実行してみました。 命名の前提があったりで小回りは効きませんが、ここからカスタマイズしてみるのも良いですね。