AIがテストを書くと「実装をコピーしたテスト」になる問題を解決するテンプレート公開!

はじめに

PSSLの佐々木です。

AIコーディングアシスタントの進化により、テストコードの自動生成が身近になりました。しかし、ここに大きな落とし穴があります。

AIが実装コードを見ながらテストを書くと、実装のロジックをそのままテストにコピーしてしまうので仕様の不具合に気づくことができません。

これを「トートロジカルテスト(同義反復テスト)」と呼びます。

// 実装コード
function calculateTax(price: number): number {
  return Math.floor(price * 0.1);
}

// AIが生成した"テスト"(実装を見て書いた場合)
it('税額を計算する', () => {
  const price = 1000;
  const expected = Math.floor(price * 0.1); // 実装と同じロジック!
  expect(calculateTax(price)).toBe(expected);
});

このテストは常に成功します。なぜなら、実装が間違っていてもテストも同じ間違いをするからです。カバレッジは100%でも、バグは検出できません。

解決策:AIに実装コードを「見せない」

この問題を解決するため、仕様書のみを参照してテストを生成するワークフローを構築しました。

Claude Codeのエージェント機能を使い、テスト生成エージェントにはReadツールを与えないという技術的制約を設けます。

# .claude/agents/test-generator.md
---
name: test-generator
description: 仕様書のみに基づいてテストを生成する
tools: Write, Bash  # Readがない = 実装コードを読めない
---

これにより、AIは仕様書(spec.md)だけを頼りにテストを書くしかなくなります。

アーキテクチャ全体像

specs/features/{feature}/
├── spec.md          # 仕様書(Given/When/Then形式)
└── plan.md          # 実装計画

     ↓ 並列実行

┌─────────────────┐     ┌─────────────────┐
│   implementer   │     │  test-generator │
│ (tools: 全て)   │     │ (tools: Write,  │
│                 │     │         Bash)   │
│ 実装コードを    │     │ 仕様書のみ参照  │
│ 自由に読み書き  │     │ src/は読めない  │
└────────┬────────┘     └────────┬────────┘
         │                       │
         ▼                       ▼
    src/lib/*.ts           src/lib/*.test.ts
    src/app/**/*           e2e/*.spec.ts

仕様書のフォーマット

テスト生成の精度を上げるため、仕様書はGiven/When/Then形式で記述します。

## 受け入れ条件

### AC-01: 定額割引の適用
- Given: 価格1000円の商品がある
- When: クーポンコード「SAVE100」を適用する
- Then: 割引後価格は900円になる

### AC-02: パーセント割引の適用
- Given: 価格2000円の商品がある
- When: クーポンコード「OFF10」(10%割引)を適用する
- Then: 割引後価格は1800円になる

この形式により、AIはテストケースを正確に生成できます。

核心となるルール:仕様整合ルール

テストが失敗した場合、どちらを修正するかという判断が重要です。

私たちは「仕様整合ルール」を採用しました:

テストが失敗し、かつテストが仕様書と整合している場合、 実装を修正する(テストを修正しない)

# .claude/rules/src-lib.md
## 仕様整合ルール(Spec-aligned Rule)

テスト失敗時の対応:
1. テストが仕様書(spec.md)と整合しているか確認
2. 整合している → 実装を修正
3. 整合していない → テストを修正

これにより、仕様書が「信頼できる唯一の情報源(Single Source of Truth)」として機能します。

実際のワークフロー

Step 1: 仕様書を作成

# specs/features/discount-calculator/spec.md

## 概要
クーポンコードを適用して割引価格を計算する機能

## 受け入れ条件
### AC-01: 定額割引
- Given: 価格1000円
- When: SAVE100を適用
- Then: 割引後900円

### AC-04: 無効なクーポン
- Given: 価格1000円
- When: INVALIDを適用
- Then: エラー「無効なクーポンコードです」

Step 2: 並列エージェントを実行

# 2つのエージェントを同時に起動
claude --agent implementer "specs/features/discount-calculator/plan.mdに基づいて実装"
claude --agent test-generator "specs/features/discount-calculator/spec.mdに基づいてテスト生成"

Step 3: テストを実行して結果を確認

npm run test:run
npx playwright test

Step 4: 失敗があれば仕様整合ルールに従って修正

実際のプロジェクトでは、以下のような失敗がありました:

FAIL src/lib/discount.test.ts
  ✕ SAVE100で100円割引が適用される
    Expected: 100
    Received: 0

テストは仕様書通り「100円割引」を期待しています。実装側のバグなので、実装を修正しました。

GitHub Actions での自動化

CI/CDパイプラインで品質を担保します:

# .github/workflows/ci.yml
jobs:
  test:
    steps:
      - name: Type check
        run: npx tsc --noEmit
      - name: Run unit tests
        run: npm run test:run
      - name: Run unit tests with coverage
        run: npm run test:coverage

  e2e:
    steps:
      - name: Run E2E tests
        run: npx playwright test

さらに、ミューテーションテストで「テストの質」も検証します:

# .github/workflows/mutation.yml
- name: Run mutation tests
  run: npm run test:mutation  # Stryker

技術スタック

用途ツール
フレームワークNext.js 15 (App Router)
単体テストVitest
E2EテストPlaywright
ミューテーションテストStryker
AIエージェントClaude Code

得られた効果

1. テストが仕様の検証になる

実装を見ずに書かれたテストは、「仕様通りに動くか」を純粋に検証します。

2. バグが確実に検出される

実際に3件の実装バグがテストで検出されました:

  • 割引額の計算ミス
  • クーポン情報の返却漏れ
  • 0円時の割引処理

3. リファクタリングが安全になる

テストが仕様に基づいているため、内部実装を変更しても「振る舞いが変わっていないこと」を確認できます。

注意点とトレードオフ

仕様書のメンテナンスコスト

仕様書が古くなると、テストと実装が乖離します。仕様変更時は必ず spec.md を更新する運用が必要です。

E2Eテストのセレクター問題

仕様書だけではDOM構造がわからないため、E2Eテストでは data-testid の命名規則を事前に決めておく必要があります。

# 仕様書に記載
## UI要素のTestID
- 価格入力: `data-testid="price-input"`
- 計算ボタン: `data-testid="calculate-button"`
- 結果表示: `data-testid="discounted-price"`

初期設定の手間

エージェント設定、ルール定義など初期構築には時間がかかります。ただし、一度作れば再利用可能なテンプレートになります。

まとめ

AIによるテスト生成の最大の罠は、「実装を見てテストを書く」ことです。

これを防ぐため:

  1. テスト生成エージェントからReadツールを剥奪
  2. 仕様書(Given/When/Then)を信頼できる情報源に
  3. 仕様整合ルールでテスト失敗時の判断を明確化

このワークフローにより、AIが生成するテストは「実装の複製」ではなく「仕様の検証」になります。

テンプレートはGitHubで公開しています。ぜひ活用してください。 https://github.com/atomic-kanta-sasaki/-claude-test-driven-template

ご覧いただきありがとうございます! この投稿はお役に立ちましたか?

役に立った 役に立たなかった

0人がこの投稿は役に立ったと言っています。
エンジニア募集中!

コメントを残す

メールアドレスが公開されることはありません。 が付いている欄は必須項目です