Agentive
自動化ラボ

AIでテスト自動化 — Claude Codeにテストを書かせる実践ガイド

約6分で読めます

テストは品質の守護者だが、書くのは面倒で後回しにされがちだ。Claude Codeを使えば、既存コードからのテスト自動生成、TDDワークフロー、E2Eテストの構築まで、テスト工程の大部分をAIに委譲できる。本記事では、pytest・Jest・Playwrightを使った実践的なテスト自動化手法を解説する。

なぜAIにテストを書かせるのか

手動でテストを書く場合、実装時間の30〜50%がテスト作成に費やされる。AIテスト生成を導入することで、以下の改善が見込める。

指標手動テストAIテスト生成
テスト作成時間実装の30〜50%実装の5〜10%
カバレッジ60〜70%が限界85%以上を安定維持
エッジケース検出経験依存網羅的に提案
テスト保守コスト高いAI再生成で低減

AIが特に得意なのは「パターン認識によるエッジケース列挙」だ。人間が見落としがちな境界値、null入力、型不一致などを体系的に洗い出す。

テスト自動化の3つのアプローチ

  1. 既存コードからの逆生成 — 実装済みコードを読ませてテストを生成
  2. TDD(テスト駆動開発) — 仕様からテストを先に書かせ、実装を後追い
  3. E2Eシナリオ生成 — ユーザー操作フローからPlaywrightテストを構築

ユニットテスト自動生成 — pytest編

Pythonプロジェクトでは、Claude Codeに対象ファイルを読ませるだけでpytestテストを生成できる。

# src/calculator.py
class Calculator:
    def divide(self, a: float, b: float) -> float:
        if b == 0:
            raise ValueError("Division by zero")
        return a / b

    def percentage(self, value: float, total: float) -> float:
        if total == 0:
            raise ValueError("Total cannot be zero")
        return (value / total) * 100

Claude Codeへのプロンプト:

src/calculator.py のユニットテストをpytestで書いて。
正常系・異常系・境界値を網羅して、パラメタライズドテストも使って。

生成されるテスト:

# tests/test_calculator.py
import pytest
from src.calculator import Calculator

@pytest.fixture
def calc():
    return Calculator()

class TestDivide:
    @pytest.mark.parametrize("a, b, expected", [
        (10, 2, 5.0),
        (7, 3, 2.3333333333333335),
        (-10, 2, -5.0),
        (0, 5, 0.0),
        (1e10, 1e5, 1e5),
    ])
    def test_divide_normal(self, calc, a, b, expected):
        assert calc.divide(a, b) == pytest.approx(expected)

    def test_divide_by_zero(self, calc):
        with pytest.raises(ValueError, match="Division by zero"):
            calc.divide(10, 0)

    def test_divide_float_precision(self, calc):
        result = calc.divide(1, 3)
        assert abs(result - 0.3333333333333333) < 1e-10

class TestPercentage:
    @pytest.mark.parametrize("value, total, expected", [
        (25, 100, 25.0),
        (1, 3, 33.33333333333333),
        (0, 100, 0.0),
        (100, 100, 100.0),
    ])
    def test_percentage_normal(self, calc, value, total, expected):
        assert calc.percentage(value, total) == pytest.approx(expected)

    def test_percentage_zero_total(self, calc):
        with pytest.raises(ValueError, match="Total cannot be zero"):
            calc.percentage(50, 0)

ポイントは pytest.mark.parametrize の活用だ。AIは入力パターンを大量に列挙するのが得意なので、パラメタライズドテストとの相性が良い。

ユニットテスト自動生成 — Jest編

フロントエンド・Node.jsプロジェクトではJestを使う。TypeScriptの型情報がある場合、AIはより正確なテストを生成できる。

// src/validators.ts
export function validateEmail(email: string): boolean {
  const pattern = /^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$/;
  return pattern.test(email);
}

export function validatePassword(password: string): {
  valid: boolean;
  errors: string[];
} {
  const errors: string[] = [];
  if (password.length < 8) errors.push("8文字以上必要です");
  if (!/[A-Z]/.test(password)) errors.push("大文字を含めてください");
  if (!/[0-9]/.test(password)) errors.push("数字を含めてください");
  return { valid: errors.length === 0, errors };
}
// src/__tests__/validators.test.ts
import { validateEmail, validatePassword } from "../validators";

describe("validateEmail", () => {
  it.each([
    ["user@example.com", true],
    ["test.name+tag@domain.co.jp", true],
    ["invalid-email", false],
    ["@no-local.com", false],
    ["no-domain@", false],
    ["spaces in@email.com", false],
  ])("validateEmail(%s) => %s", (input, expected) => {
    expect(validateEmail(input)).toBe(expected);
  });
});

describe("validatePassword", () => {
  it("8文字以上で大文字・数字を含む場合にvalid", () => {
    const result = validatePassword("SecureP4ss");
    expect(result.valid).toBe(true);
    expect(result.errors).toHaveLength(0);
  });

  it("短すぎるパスワードでエラー", () => {
    const result = validatePassword("Ab1");
    expect(result.valid).toBe(false);
    expect(result.errors).toContain("8文字以上必要です");
  });

  it("大文字なしでエラー", () => {
    const result = validatePassword("lowercase123");
    expect(result.errors).toContain("大文字を含めてください");
  });

  it("数字なしでエラー", () => {
    const result = validatePassword("NoDigitsHere");
    expect(result.errors).toContain("数字を含めてください");
  });
});

E2Eテスト自動生成 — Playwright編

PlaywrightによるE2Eテストは、ユーザー操作のシナリオをそのままコード化する。Claude Codeに画面仕様やユーザーストーリーを伝えるだけで、テストコードが生成される。

// e2e/login-flow.spec.ts
import { test, expect } from "@playwright/test";

test.describe("ログインフロー", () => {
  test("正常ログインでダッシュボードに遷移する", async ({ page }) => {
    await page.goto("/login");
    await page.fill('[data-testid="email"]', "user@example.com");
    await page.fill('[data-testid="password"]', "SecureP4ss");
    await page.click('[data-testid="login-button"]');

    await expect(page).toHaveURL("/dashboard");
    await expect(page.locator("h1")).toContainText("ダッシュボード");
  });

  test("無効な認証情報でエラーメッセージ表示", async ({ page }) => {
    await page.goto("/login");
    await page.fill('[data-testid="email"]', "wrong@example.com");
    await page.fill('[data-testid="password"]', "wrongpass");
    await page.click('[data-testid="login-button"]');

    await expect(
      page.locator('[data-testid="error-message"]')
    ).toBeVisible();
    await expect(page).toHaveURL("/login");
  });

  test("未入力でバリデーションエラー", async ({ page }) => {
    await page.goto("/login");
    await page.click('[data-testid="login-button"]');

    const errors = page.locator(".validation-error");
    await expect(errors).toHaveCount(2);
  });
});

Playwrightテスト生成の詳細はPlaywright実践ガイドも参照のこと。

AIでTDD(テスト駆動開発)を実践する

TDDの「Red → Green → Refactor」サイクルにAIを組み込むと、仕様定義からテスト完成までの時間を大幅に短縮できる。

TDDワークフロー

  1. 仕様を自然言語で書く — 要件をプロンプトとして記述
  2. AIがテストを生成(Red) — 仕様からテストコードを生成。この時点ではテストは失敗する
  3. AIが実装を生成(Green) — テストが通る最小限の実装を生成
  4. AIがリファクタリング提案(Refactor) — コード品質の改善を提案
# TDDプロンプト例
以下の仕様でTDDを実行して。

仕様: ショッピングカート
- 商品を追加できる(名前、価格、数量)
- 同一商品の追加で数量が加算される
- 商品を削除できる
- 合計金額を計算できる(税率10%)
- 数量0以下はエラー

手順:
1. まずpytestのテストコードだけ書いて
2. テストが通る実装を書いて
3. リファクタリングして

このワークフローの利点は、AIが生成したテストが「仕様の実行可能なドキュメント」として機能する点にある。

モック・スタブの自動生成

外部APIやデータベースに依存するコードのテストでは、モック・スタブが不可欠だ。Claude Codeはコードの依存関係を解析して、適切なモックを自動生成する。

# tests/test_user_service.py
from unittest.mock import AsyncMock, patch
import pytest
from src.user_service import UserService

@pytest.fixture
def mock_db():
    db = AsyncMock()
    db.find_one.return_value = {
        "id": "user-123",
        "name": "テストユーザー",
        "email": "test@example.com"
    }
    return db

@pytest.fixture
def mock_email_client():
    client = AsyncMock()
    client.send.return_value = {"status": "sent", "message_id": "msg-456"}
    return client

@pytest.mark.asyncio
async def test_create_user_sends_welcome_email(mock_db, mock_email_client):
    service = UserService(db=mock_db, email_client=mock_email_client)

    result = await service.create_user("新規ユーザー", "new@example.com")

    mock_db.insert_one.assert_called_once()
    mock_email_client.send.assert_called_once_with(
        to="new@example.com",
        subject="ようこそ",
        template="welcome"
    )
    assert result["status"] == "created"

プロンプトのコツは「依存先の振る舞いを明示する」ことだ。「DBは正常応答を返す前提で」「API側はタイムアウトするケースも含めて」のように条件を指定すると、質の高いモックが生成される。

CI/CDパイプラインへの統合

生成したテストをGitHub Actionsに統合し、プッシュごとに自動実行する構成を示す。

# .github/workflows/ai-test.yml
name: AI Test Suite
on:
  push:
    branches: [main, develop]
  pull_request:
    branches: [main]

jobs:
  unit-tests:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - uses: actions/setup-python@v5
        with:
          python-version: "3.12"
      - run: pip install -r requirements.txt
      - run: pytest tests/ --cov=src --cov-report=xml -v
      - uses: codecov/codecov-action@v4
        with:
          file: coverage.xml

  e2e-tests:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - uses: actions/setup-node@v4
        with:
          node-version: "20"
      - run: npm ci
      - run: npx playwright install --with-deps
      - run: npx playwright test
      - uses: actions/upload-artifact@v4
        if: failure()
        with:
          name: playwright-report
          path: playwright-report/

CI/CDパイプラインの詳しい構築方法はAI CI/CDテストパイプラインを参照のこと。

質の高いテストを生成するプロンプト設計

AIが生成するテストの品質は、プロンプトの書き方に大きく依存する。以下のポイントを押さえよう。

効果的なプロンプトの構成要素

  1. 対象ファイルの明示 — テスト対象のパスを具体的に指定する
  2. テストフレームワークの指定 — pytest / Jest / Vitest など明確に伝える
  3. カバレッジ要件 — 正常系・異常系・境界値の網羅を要求する
  4. コーディング規約 — プロジェクト固有のテスト命名規則やパターンを伝える
  5. 既存テストの参照 — 既存テストを読ませてスタイルを揃えさせる

悪いプロンプトと良いプロンプトの比較

悪い例:

このファイルのテストを書いて

良い例:

src/services/payment.ts の単体テストをJestで書いて。
- 正常決済、残高不足、タイムアウトの3パターン
- モックはjest.fnを使い、外部API呼び出しはすべてモック化
- テスト名は日本語で、describe/itのネスト構造を使う
- 既存テストsrc/__tests__/auth.test.tsのスタイルに合わせて

具体的であるほど、生成されるテストの質は上がる。プロンプト設計の詳細はプロンプトエンジニアリング実践ガイドも参考にしてほしい。

まとめ

AIテスト自動化は、テスト作成の負担を劇的に減らしながらカバレッジを向上させる実践的な手法だ。重要なのは「AIが生成したテストを必ず人間がレビューする」こと。テストの意図が正しいか、ビジネスロジックを適切に検証しているかは、最終的に人間が判断すべきだ。

まずは既存コードへのユニットテスト生成から始め、TDDワークフローやE2Eテストへと段階的に拡大していくのがよい。

関連記事

A

Agentive 編集部

AIエージェントを実際に使い倒す個人開発者。サイト制作の自動化を実践しながら、その知見を発信しています。