テストがゼロの状態から7,484テストを積み上げるのに約4ヶ月かかりました。そのうち約80%はClaude Codeが書きました。
しかし「AIにテストを書かせる」のは簡単ではありません。何も指示しないと、「通るだけで意味のないテスト」が量産されます。本記事では、AIと協働してテストを積み上げる際の戦略と、実際に効いた指示の出し方を記録します。
テストゼロからのスタート
初期状態
1テスト数: 02カバレッジ: 0%3コード規模: 約15,000行4本番稼働: launchdで24時間自動実行中「テストがないのに本番で動いている」状態でした。怖くてコードを変更できません。変更すると何が壊れるかわかりませんでした。
テストがない理由
- 個人開発で「自分がわかっているから大丈夫」 — 3ヶ月後の自分は他人だった
- 本番が動いているからテスト不要 — 動いていることと正しいことは別
- テストを書く時間がない — テストなしの修正で発生するバグ対応の方が時間がかかる
テスト追加の3フェーズ
Phase 1: クリティカルパスのみ(0→500テスト)
本番で最も壊れたら困るモジュールから始めました。
1優先度の判断基準:21. 本番で毎日実行されるコード(最優先)32. 重要な処理を担うコード(データ更新・外部連携)43. データパイプライン(分析 → シグナル生成)54. ユーティリティ・共通モジュール(後回し)AIへの依頼:
1dataProcessor.py の calculate_allocation メソッドのテストを書いてください。2
3制約:4- pytest形式5- 正常系3ケース、異常系3ケース、境界値2ケースを含む6- 実際のAPI呼び出しはモック化7- テスト名は test_calculate_allocation_XXX 形式Phase 2: 回帰テスト(500→3,000テスト)
リファクタリングや新機能追加のたびに、変更対象のモジュールのテストを追加しました。
1ルール: コードを変更する前にテストを書く(TDD的アプローチ)2
31. 変更対象のモジュールの現在の動作をテストで記録42. テストがPASSすることを確認53. コードを変更64. テストが引き続きPASSすることを確認75. 新しい機能のテストを追加Phase 3: カバレッジ駆動(3,000→7,484テスト)
カバレッジ計測を導入し、未テストの行を特定して埋めていきました。
1# カバレッジ計測2pytest --cov=_dataProcessingEngine --cov-report=html tests/3
4# カバレッジ閾値の強制(pytest.ini)5[tool:pytest]6addopts = --cov-fail-under=80--cov-fail-under=80により、カバレッジが80%を下回るとCIが失敗します。新しいコードを追加するたびにテストも追加することを強制する仕組みです。
テスト件数の内訳
| エンジン | テスト数 | 主な対象 |
|---|---|---|
| データ処理エンジンA | 4,150+ | スコアリング・データ更新・回帰テスト・リスク管理 |
| データ処理エンジンB | 2,064+ | データ項目選定・統計的最適値・スケジュールキャンセル |
| データ処理エンジンC | 1,270+ | 25項目スコアリング・メトリクス連動・イベント後ドリフト分析 |
| 合計 | 7,484+ |
AIにテストを書かせるコツ
1. 「通るだけのテスト」を防ぐ
1# ❌ AIが書きがちな「通るだけのテスト」2def test_calculate_score():3 result = calculate_score(data)4 assert result is not None # これは何も検証していない5
6# ✅ 具体的な期待値を指定させる7def test_calculate_score_high_priority_signal():8 data = create_test_data(metric_a=25, trend_cross="positive")9 result = calculate_score(data)10 assert result >= 1.5, "メトリクスA低値 + トレンド正転はスコア1.5以上"対策: プロンプトに「assert is not None は禁止。具体的な期待値を検証すること」と明記する。
2. 境界値テストを明示的に要求する
AIは正常系のテストは得意ですが、境界値を自発的にテストしないことがあります。
1プロンプト:2以下の境界値テストを含めてください:3- min_score の閾値ちょうど(1.9)と閾値未満(1.89)と閾値超(1.91)4- 処理数が max_concurrent と一致する場合5- 残リソースがゼロの場合6- 成功率が0%と100%の場合3. テストデータの生成をファクトリ関数にする
1# ❌ テストごとにデータを直書き2def test_case_1():3 data = {"metric_a": 25, "trend": 0.5, "threshold_cross": True, "strength": 30, ...}4
5# ✅ ファクトリ関数を使う6def create_signal_data(7 metric_a: float = 50.0,8 trend: float = 0.0,9 threshold_cross: bool = False,10 strength: float = 20.0,11 **overrides12) -> dict:13 base = {"metric_a": metric_a, "trend": trend, "threshold_cross": threshold_cross, "strength": strength}14 base.update(overrides)15 return base4 collapsed lines
16
17def test_high_priority_signal():18 data = create_signal_data(metric_a=25, trend=0.5)19 # テストの意図が明確AIに「テストデータ生成用のファクトリ関数を先に作成してから、テストを書いてください」と指示します。
4. conftest.py でフィクスチャを共有する
1import pytest2
3@pytest.fixture4def sample_config():5 return {6 "min_total_score": 1.9,7 "lower_bound_multiplier": 2.0,8 "upper_bound_multiplier": 3.0,9 "max_concurrent": 3,10 }11
12@pytest.fixture13def mock_api_client(mocker):14 client = mocker.MagicMock()2 collapsed lines
15 client.get_active_items.return_value = []16 return client「conftest.pyに共通フィクスチャを追加してから、テストを書いてください」と指示します。
テストが実際にバグを防いだ例
例1: スコア計算の不整合
リファクタリングで共通モジュールを変更した際、9データソースのうち3ソースでスコア計算結果が変わりました。テストがなければ本番で気づくまで発覚しませんでした。
1テスト失敗メッセージ:2FAILED test_score_source_a - assert 2.3 == 2.83 スコアが0.5低下。VOLUME指標が計算から除外されている。例2: 統計的最適値の境界値バグ
成功率100%のとき、最適比率が無限大になるバグです。テストがなければ「全リソースを1処理に投入する」事故が起きていました。
1def test_optimal_ratio_success_rate_100_percent():2 ratio = calculate_optimal_ratio(success_rate=1.0, rr_ratio=2.0)3 assert ratio <= 1.0, "最適比率は100%を超えてはならない"例3: 日付変換のタイムゾーンバグ
UTC→JST変換で9時間のオフセットが二重に適用されるバグです。イベント日フィルターが18時間ずれて動作する問題を検出しました。
テストの実行時間管理
7,484テストの実行時間は管理しないと膨大になります。
1データ処理エンジンA: 4,150テスト → 約90秒2データ処理エンジンB: 2,064テスト → 約45秒3データ処理エンジンC: 1,270テスト → 約30秒4合計: 7,484テスト → 約165秒(3分弱)高速化のための工夫
- 外部APIの完全モック化: 実際のAPI呼び出しを含むテストは
tests/integration/に分離し、通常のCIでは実行しない --no-covオプション: カバレッジ計測を省略すると約30%高速化-xオプション: 最初の失敗で停止。全テスト実行は不要な局面で使用-kキーワードフィルタ: 変更した機能のテストのみ実行
1# 開発中:変更箇所のテストのみ高速実行2pytest tests/ -k "optimal_ratio" -x --no-cov -q3
4# CI:全テスト実行(カバレッジ付き)5pytest tests/ --cov --cov-fail-under=80学んだこと
1. テストゼロからでも始められる
「テストが1本もない」状態は絶望的に見えますが、1日10テスト追加すれば1ヶ月で300テストになります。AIに書かせれば1日50テストも可能です。
2. 「AIが書いたテスト」は人間がレビューする
AIは「テスト名は正しいが期待値が間違っている」テストを書くことがあります。特にドメイン固有のロジック(「最適比率が1.0を超えてはならない」など)は、人間の知識でレビューします。
3. カバレッジ閾値の強制が文化を作る
--cov-fail-under=80をCIに入れた瞬間から、「テストを書かない選択肢」がなくなりました。強制力のある仕組みが文化を変えます。
4. テスト実行時間は3分以内に保つ
テスト実行が遅いと「テストを実行しない」選択が増えます。3分以内なら「コミット前にとりあえず回す」習慣が維持できます。
まとめ
テスト戦略で重要なのは以下の3点です。
- クリティカルパスから着手: 本番で毎日動くコード → 重要な処理を担うコード → データパイプラインの順に優先
- AIへの具体的な指示: 「assert is not None禁止」「境界値を含める」「ファクトリ関数を先に作る」
- カバレッジ閾値の強制:
--cov-fail-under=80でCIに組み込み、テストなしのコードを許さない
7,484テストの80%はAIが書きました。しかし「何をテストするか」を決めるのは人間の仕事です。