はじめに
モノレポで複数の機能を管理していると、CI/CDの実行時間が課題になります。すべての機能のテストを毎回実行していると、開発効率が下がってしまいます。
今回は、変更された機能だけを自動検出してテストを実行するGitHub Actionsワークフローを実装した事例を紹介します。この仕組みにより、CIの実行時間を大幅に短縮できました。
また、approve時などに全テストを実行することで、品質の担保を行うことも可能です。
課題と解決策
課題
- 5つの機能(function-a、function-b、function-c、function-d、function-e)を持つ統合システム
- 従来は全機能のテストを毎回実行(約15-20分)
- 変更していない機能のテストも実行される無駄
解決策
- 変更されたファイルを自動検出
- 影響を受けた機能のみテストを実行
- 堅牢な変更検出ロジックでエッジケースにも対応
実装の詳細
1. 変更検出ジョブの設計
まず、変更されたファイルを検出するジョブを作成します:
detect_changed_files:
name: Detect changed files
runs-on: normal
container: /ci-images/golang:latest
outputs:
function-a: ${{ steps.changes.outputs.function-a }}
function-b: ${{ steps.changes.outputs.function-b }}
function-c: ${{ steps.changes.outputs.function-c }}
function-d: ${{ steps.changes.outputs.function-d }}
function-e: ${{ steps.changes.outputs.function-e }}
2. 堅牢な変更検出ロジック
# より堅牢な変更検出ロジック
if git rev-parse --verify origin/main >/dev/null 2>&1; then
CHANGED_FILES=$(git diff --name-only origin/main...HEAD)
echo "✅ Comparing against origin/main"
else
# 既存のフォールバックロジック
if [ "${{ github.event.before }}" = "0000000000000000000000000000000000000000" ]; then
CHANGED_FILES=$(git ls-tree --name-only HEAD)
echo "⚠️ New branch: listing all files"
else
CHANGED_FILES=$(git diff --name-only "${{ github.event.before }}" "${{ github.sha }}")
echo "⚠️ Fallback: comparing against previous commit"
fi
fi
このロジックのポイント:
- origin/mainとの比較を優先: 最も確実な比較方法
- フォールバック機能: 新規ブランチや特殊なケースにも対応
- エラーハンドリング: 各段階で適切な処理を実行
3. 機能別の変更検出
function-a_changed=false
function-b_changed=false
function-c_changed=false
function-d_changed=false
function-e_changed=false
while read -r file; do
if [[ "$file" == function-a/* ]]; then
function-a_changed=true
elif [[ "$file" == function-b/* ]]; then
function-b_changed=true
elif [[ "$file" == function-c/* ]]; then
function-c_changed=true
elif [[ "$file" == function-d/* ]]; then
function-d_changed=true
elif [[ "$file" == function-e/* ]]; then
function-e_changed=true
fi
done <<< "$CHANGED_FILES"
4. 構造化ログ出力
デバッグとモニタリングのために、JSON形式でログを出力:
echo "{"
echo " \"workflow\": \"selective_function_test\","
echo " \"event\": \"${{ github.event_name }}\","
echo " \"branch\": \"${{ github.ref_name }}\","
echo " \"functions_affected\": {"
echo " \"function-a\": $function-a_changed,"
echo " \"function-b\": $function-b_changed,"
echo " \"function-c\": $function-c_changed,"
echo " \"function-d\": $function-d_changed,"
echo " \"function-e\": $function-e_changed"
echo " }"
echo "}"
5. 条件付きテスト実行
各機能のテストジョブは、変更検出の結果に基づいて条件付きで実行されます:
test_function-a:
name: Run Unit test and Integration (function-a)
runs-on: normal
needs: [detect_changed_files]
if: needs.detect_changed_files.outputs.function-a == 'true'
container:
image: /ci-images/golang:latest
functions:
sales_db:
image: /databases/sales-db:latest
env:
MYSQL_ALLOW_EMPTY_PASSWORD: true
options: >-
--health-cmd "mysqladmin ping -h localhost"
--health-interval 20s
--health-timeout 10s
--health-retries 10
steps:
- name: Test
run: make test/function-a
実際の効果
Before(改善前)
- 全機能のテスト実行: 15-20分
- 変更していない機能もテスト実行
- リソースの無駄遣い
After(改善後)
- 変更された機能のみテスト実行: 3-8分
- 実行時間を60-70%短縮
- 開発者の待ち時間を大幅削減
実装時の注意点
1. 依存関係の考慮
機能間の依存関係がある場合は、追加のロジックが必要です:
# 例:function-dが変更された場合、function-eもテストする
if [[ "$file" == function-d/* ]]; then
function-d_changed=true
function-e_changed=true # 依存機能も含める
fi
2. 共通ライブラリの変更
共通ライブラリが変更された場合は、全機能をテストする必要があります:
# 共通ライブラリの変更検出
if [[ "$file" == cmd/* ]] || [[ "$file" == go.mod ]] || [[ "$file" == go.sum ]]; then
function-a_changed=true
function-b_changed=true
function-c_changed=true
function-d_changed=true
function-e_changed=true
fi
注意: この記事では基本的な変更検出ロジックを紹介しています。実際の実装では、共通ライブラリの変更検出や機能間の依存関係の考慮など、追加のロジックが必要になる場合があります。
3. テスト結果の集約
最後に、テスト結果を集約してビルドと通知を行います:
build:
name: Build
runs-on: normal
needs:
- test_function-a
- test_function-b
- test_function-c
- test_function-d
if: >-
always() && (
needs.test_function-a.result == 'success' ||
needs.test_function-b.result == 'success' ||
needs.test_function-c.result == 'success' ||
needs.test_function-d.result == 'success'
)
応用可能なパターン
このパターンは他のプロジェクトでも応用できます:
- マイクロ機能: 各機能を独立してテスト
- フロントエンド: コンポーネント別のテスト実行
- インフラ: 環境別のデプロイメント
- ドキュメント: 変更されたドキュメントのみビルド
まとめ
変更された機能だけをテストするCI/CD設計により、以下の効果を実現しました:
- ✅ 実行時間の大幅短縮(60-70%削減)
- ✅ 開発効率の向上
- ✅ リソースコストの削減
- ✅ 堅牢な変更検出ロジック
ぜひ皆さんのプロジェクトでも試してみてください!