技術

Next.jsフロントエンド開発の最適なテスト戦略:Jest、RTL、Playwrightの使い分け

AI協働による超短期の開発サイクルで変わるテストツール選択の新基準

2025-03-30
35分
Jest
Playwright
React Testing Library
Next.js
テスト駆動開発
AI協働開発
フロントエンドテスト
吉崎 亮介

吉崎 亮介

株式会社和談 代表取締役社長 / 株式会社キカガク創業者

Next.jsフロントエンド開発の最適なテスト戦略:Jest、RTL、Playwrightの使い分け

AI 協働開発で見つけた「1 タスク 15 分」の法則とその衝撃

AI 駆動開発(通称 Vibe Coding)に約 1 ヶ月取り組んできた中で、私が発見した最も驚くべき事実がある。それはAI との協働開発では 1 つのタスクが約 15 分で完了するという現象だ。

これは突拍子もない話に聞こえるかもしれないが、私がこの 1 ヶ月間で一貫して観察してきた経験則である。従来の開発では「画面にボタンを追加して、クリック時に確認ダイアログを表示する」といった単純な機能実装でも 1〜2 時間かかることが普通だった。しかし、AI に同じタスクを指示すると、驚くことに約 15 分でコードが完成する。

従来の開発サイクル(2~3 時間)は以下の通りであった。

図表を生成中...

これが AI との協働開発サイクルで 15 分ほどに短縮されたのである。

図表を生成中...

この現象を最初に観察したのは、投資判断支援システムというプロトタイプ開発の途中だった。投資検討対象の評価情報を表示する UI 画面を実装しようとしていたとき、私は AI に「評価パラメータを表示するコンポーネントを作成してほしい」と依頼した。すると、約 8 分後には基本的なコンポーネントが完成し、さらに 4 分ほどでスタイリングや条件付き表示などの調整も済んでしまった。同様のコンポーネントを自分で作る場合、少なくとも 1 時間はかかっていただろう。

このように劇的に短くなった開発サイクルは、テスト戦略にも大きな影響を与える。従来の開発では「テストの網羅性」や「メンテナンス性」が重視されていたが、15 分サイクルの世界ではテスト実行時間やレビュー効率という要素が格段に重要性を増す。

本記事では、この「1 タスク 15 分」という超短期サイクル開発における、Jest、React Testing Library、Playwright といった主要テストツールの使い分けについて、私の実体験から得た知見を共有したい。

各テストツールとの協働体験:想定外の問題と解決策

3 つのプロトタイプ開発プロジェクトを通じて、それぞれのテストツールが AI 協働開発でどのように機能するのか、そして思わぬ落とし穴は何かを発見した。

Jest での体験:モックの過剰生成問題

Jest は単体テストの定番ツールだが、AI との協働でまず直面した問題は「モックの過剰生成」だった。具体的な例を挙げよう。

あるプロジェクトで、テキスト入力から評価パラメータを計算する機能のテストを AI に依頼した時のことだ。私の想定では「いくつかの入力例でパラメータ計算をテスト」するシンプルなコードを期待していた。しかし、AI が生成したのは次のようなコードだった

// AIが生成した最初のテストコード
describe("calculateEvaluationParameters", () => {
  // あらゆる依存モジュールをモック化
  jest.mock("../services/textAnalysis");
  jest.mock("../api/evaluationApi");
  jest.mock("../utils/parameterNormalizer");
 
  // 各モックに詳細な戻り値を設定(長い)
  beforeEach(() => {
    textAnalysisService.analyzeText.mockReturnValue({
      sentiment: 0.8,
      keywords: ["技術", "革新", "市場"],
      // さらに多数のプロパティ...
    });
    // 他のモック設定も同様に詳細...
  });
 
  test("テキスト分析結果から正しくパラメータを計算する", () => {
    // 30行以上のテストコード
  });
 
  // 他にも10個以上のテストケース
});

このテストコードの問題点は明らかだった

  1. すべての依存関係がモック化されているため、実際の処理が正しく機能しているか検証できない
  2. モックの設定が複雑すぎるため、テストコードが実装より長くなっている
  3. テストケースが多すぎるため、レビューに時間がかかる

これではプロトタイプ開発の目的である「基本機能が動作するか確認する」というシンプルな目的が達成できない。AI は「良いテスト」の原則に忠実すぎるあまり、プロトタイプ段階には不適切な過剰なテストを生成していたのだ。

この問題に対する解決策として、私は次のようなプロンプトパターンを開発した

以下の関数のJestテストを作成してください
calculateEvaluationParameters(text)

テストの条件
1. モックは必要最小限にすること(外部APIのみ)
2. 3つのケースだけをテスト(正常入力、空入力、長文入力)
3. 各テストは5行以内に収めること
4. 統合テストとして書くこと(実際の処理を極力使用)

このように具体的な制約を設けることで、AI は次のようなシンプルで実用的なテストを生成するようになった

// 改善後のテストコード
describe("calculateEvaluationParameters", () => {
  // 外部APIのみモック化
  jest.mock("../api/externalApi");
 
  test("正常な入力テキストから適切なパラメータを計算する", async () => {
    const result = await calculateEvaluationParameters(
      "技術革新による市場の変化"
    );
    expect(result).toHaveProperty("technicalStrength");
    expect(result.technicalStrength).toBeGreaterThan(0);
  });
 
  test("空の入力の場合はデフォルト値を返す", async () => {
    const result = await calculateEvaluationParameters("");
    expect(result).toEqual(DEFAULT_PARAMETERS);
  });
 
  test("長文入力でもパラメータを適切に計算する", async () => {
    const longText = "長い文章...".repeat(100);
    const result = await calculateEvaluationParameters(longText);
    expect(result).toHaveProperty("marketPotential");
  });
});

このテストは、プロトタイプ開発の本質的な目的である「基本機能の確認」にフォーカスしており、実行も高速で、レビューも容易になった。

Jest の教訓として、AI には具体的な制約を与えることが重要であり、それによって実用的なテストコードを効率よく生成できることがわかった。

React Testing Library での発見:セレクタの重要性

React Testing Library(RTL)を使ったコンポーネントテストでは、AI がどのように UI 要素を選択するかが最大の課題だった。

具体例として、評価結果を表示するコンポーネントのテストを AI に依頼した際の出来事を紹介しよう。AI が最初に生成したテストコードはこのようなものだった

// AIの最初のコード(問題あり)
test("評価パラメータが正しく表示される", () => {
  render(<EvaluationResults parameters={testParameters} />);
 
  // CSSクラスに依存したセレクタ
  const items = screen.getAllByTestId(/parameter-item-\d+/);
  expect(items).toHaveLength(5);
 
  // DOMツリー構造に依存
  expect(items[0].querySelector(".parameter-name").textContent).toBe("技術力");
  expect(items[0].querySelector(".parameter-value").textContent).toBe("4.2");
});

このテストコードには重大な問題があった

  1. 実装の詳細(DOM ツリー構造や CSS クラス)に依存している
  2. メンテナンス性が低い(UI が少し変わっただけでテストが失敗する)
  3. RTL の哲学である「ユーザー視点でテストする」に反している

RTL の理念を理解していない開発者がよく陥る罠だが、AI も同様の誤りを犯していた。これを解決するため、私は RTL の哲学を明示的に AI に説明するプロンプトを開発した

React Testing Libraryを使用して、EvaluationResultsコンポーネントのテストを書いてください。

以下の原則に従ってください
1. ユーザーが実際に見るコンテンツ(テキスト、ラベルなど)でクエリすること
2. getByText や getByRole などのユーザー中心のクエリを優先すること
3. テスト ID (data-testid) は最後の手段としてのみ使用すること
4. querySelector などのDOM操作は絶対に使用しないこと
5. コンポーネントの内部実装ではなく、表示される結果をテストすること

このガイダンスにより、AI は次のような良質なテストコードを生成するようになった

// 改善後のテストコード
test("評価パラメータが正しく表示される", () => {
  render(<EvaluationResults parameters={testParameters} />);
 
  // ユーザーが見るテキストでテスト
  expect(screen.getByText("技術力")).toBeInTheDocument();
  expect(screen.getByText("4.2")).toBeInTheDocument();
  expect(screen.getByText("市場性")).toBeInTheDocument();
  expect(screen.getByText("3.8")).toBeInTheDocument();
});
 
test("低信頼度のパラメータには警告アイコンが表示される", () => {
  render(<EvaluationResults parameters={testParameters} />);
 
  // アクセシビリティの観点からアイコンをテスト
  const warningIcon = screen.getByRole("img", { name: /警告/i });
  expect(warningIcon).toBeInTheDocument();
  // アイコンが正しい場所にあることを確認
  const lowConfidenceParam = screen.getByText("市場規模");
  expect(lowConfidenceParam.parentNode).toContainElement(warningIcon);
});

このように RTL の哲学に沿ったテストは、UI の実装詳細に依存せず、ユーザーの視点でコンポーネントが正しく機能するかを検証する。そして意外にも、AI に RTL の哲学を明示的に伝えると、人間の経験豊富な開発者並みのテストコードを生成できることを発見した。

RTL を使ったテストは、単体テストと同様に実行速度が速く、15 分サイクル開発との相性が良い。また、ユーザー視点でテストするというアプローチは、プロトタイプ開発で重要な「基本機能の確認」にも合致している。

Playwright との格闘:相対的実行時間の壁

次に、Playwright を使った E2E テストでの体験を紹介しよう。ここで最も重要な発見は「相対的な実行時間の問題」だった。

まず誤解のないように言っておくと、Playwright は素晴らしいテストツールである。従来の手動テストと比較すれば圧倒的に効率的で、小規模なプロジェクトなら数分で E2E テストが完了する。私自身、これまでの開発では Playwright のテスト実行時間を問題と感じたことはなかった。

しかし、AI 協働開発の 15 分サイクルという文脈では、この「数分」という実行時間が相対的に大きな負担になることに気づいたのだ。具体的な例を挙げよう。

評価システムのログインから結果表示までの基本フローをテストするために、AI に Playwright テストの作成を依頼した。AI は次のようなコードを生成した

// AIが生成したPlaywrightテスト
test("ユーザーが評価システムにログインして結果を閲覧できる", async ({
  page,
}) => {
  // ログインページにアクセス
  await page.goto("http://localhost:3000/login");
 
  // ログイン情報を入力
  await page.fill('input[name="email"]', "test@example.com");
  await page.fill('input[name="password"]', "password123");
  await page.click('button[type="submit"]');
 
  // ダッシュボードに移動したことを確認
  await expect(page.locator('h1:has-text("ダッシュボード")')).toBeVisible();
 
  // 評価ページに移動
  await page.click("text=評価一覧");
 
  // 評価項目が表示されるのを待つ
  await page.waitForSelector(".evaluation-item");
 
  // 各種エラーケース(入力検証、権限エラーなど)のテスト
  // ...10以上のケースが続く
 
  // レスポンシブデザインのテスト(画面サイズ変更など)
  // ...さらに複数のケースが続く
});

このテストの問題点は

  1. 網羅性を追求しすぎている(多数のエラーケースやレスポンシブテスト)
  2. 1 つのテストファイルに複数のシナリオを詰め込みすぎている
  3. 結果として実行時間が長くなる(約 3〜4 分)

従来の開発サイクル(数時間)では、3〜4 分のテスト実行時間はコーヒーを取りに行く程度の短い待ち時間だった。しかし、15 分サイクルの AI 協働開発では、その 3〜4 分は全作業時間の 20〜25%を占める重大な遅延となる。

この問題に対処するため、私は次のようなプロンプトパターンを開発した

以下の条件でPlaywrightテストを作成してください

1. 「ログイン→評価一覧表示→詳細確認」という基本フローのみをテスト
2. エラーケースは一切含めない(正常系のみテスト)
3. 1つのテストファイルに1つのテストシナリオのみ記述
4. waitForSelector などの待機時間を最小限に抑える
5. テストの実行時間が30秒以内に収まることを目標とする

このように明確な制約を設けることで、AI は次のようなシンプルで実行時間の短いテストを生成するようになった

// 改善後のPlaywrightテスト
test("基本的な評価閲覧フロー", async ({ page }) => {
  // ログインページにアクセス
  await page.goto("http://localhost:3000/login");
 
  // ログイン情報を入力して送信
  await page.fill('input[name="email"]', "test@example.com");
  await page.fill('input[name="password"]', "password123");
  await page.click('button[type="submit"]');
 
  // 評価一覧ページに移動
  await page.click("text=評価一覧");
 
  // 最初の評価項目をクリック
  await page.click(".evaluation-item:first-child");
 
  // 詳細ページに評価パラメータが表示されることを確認
  await expect(page.locator("text=技術力")).toBeVisible();
  await expect(page.locator("text=市場性")).toBeVisible();
});

このテストは、必要な機能確認だけに焦点を当て、実行時間を約 30 秒まで短縮できた。

さらに、テスト実行のタイミングも工夫した。具体的には

  1. 開発中は Jest と RTL の単体・コンポーネントテストのみ実行
  2. 一連の機能実装が完了した時点で Playwright の E2E テストを実行
  3. CI/CD パイプラインで自動的に E2E テストを実行

この戦略により、15 分サイクルの開発リズムを維持しながら、重要な E2E テストも定期的に実行できるようになった。

図表を生成中...

この体験から学んだことは、テストツールの価値評価は絶対的ではなく、開発サイクルとの相対関係で決まるということだ。従来の開発サイクルでは問題にならなかったテスト実行時間が、AI 協働開発の超短期サイクルでは重大な課題になりうる。

開発リズムを保つためのテスト実行戦略

15 分サイクル開発でテスト実行時間が与える影響を定量的に見てみよう。従来の開発と AI 協働開発でのテスト実行時間の相対的な影響の違いを図示すると次のようになる

図表を生成中...

従来の開発では 3 分のテスト実行は全体の 1.6%に過ぎないが、AI 協働開発では同じ 3 分が全体の 20%を占める。これは無視できない割合だ。

この課題に対処するため、私は次のような戦略を採用した

1. テストの階層化と実行頻度の最適化

テストを実行時間に基づいて階層化し、実行頻度を調整した

1. 超高速テスト(<100ms):単体テスト、シンプルなコンポーネントテスト
   → 変更のたびに実行

2. 高速テスト(<1s):複雑なコンポーネントテスト、シンプルな統合テスト
   → 機能実装完了時に実行

3. 中速テスト(<3s):DB連携のある統合テスト
   → Pull Request作成時に実行

4. 低速テスト(>3s):E2Eテスト、パフォーマンステスト
   → CIパイプラインでのみ実行

この階層化により、開発中は常に超高速テストだけを実行し、開発リズムを維持できるようになった。

2. テスト実行スクリプトの最適化

npm スクリプトも次のように最適化した

{
  "scripts": {
    "test:ultra-fast": "jest --findRelatedTests",
    "test:fast": "jest --testPathIgnorePatterns=integration,e2e",
    "test:integration": "jest integration",
    "test:e2e": "playwright test",
    "test:ci": "npm run test:fast && npm run test:integration && npm run test:e2e"
  }
}

特に --findRelatedTests フラグは、現在の変更に関連するテストのみを実行するため非常に有効だった。

3. テスト並列実行の最適化

Jest の並列実行設定も調整した

// jest.config.js
module.exports = {
  projects: [
    {
      displayName: "unit",
      testMatch: ["**/__tests__/unit/**/*.test.js"],
      maxWorkers: "50%", // CPUコアの半分を使用
    },
    {
      displayName: "components",
      testMatch: ["**/__tests__/components/**/*.test.js"],
      maxWorkers: "50%",
    },
    {
      displayName: "integration",
      testMatch: ["**/__tests__/integration/**/*.test.js"],
      maxWorkers: 2, // 統合テストは同時実行数を制限
    },
  ],
};

これらの工夫により、テスト実行による開発リズムの中断を最小限に抑えることができた。

テスト内容を事前に検討する「テスト計画先行アプローチ」

AI 協働開発における私のもう 1 つの大きな発見は、テストコードを書く前にテスト計画を立てる重要性だった。AI との協働では「何をテストすべきか」をまず明確にし、その後で実装に移るアプローチが非常に効果的だった。

具体的な流れは次の通りだ

図表を生成中...

この「テスト計画先行アプローチ」の実例を紹介しよう。評価結果表示コンポーネントのテストを作成する際、私はまず AI にテスト計画の作成を依頼した

「評価結果表示コンポーネント(EvaluationResults)」のテスト計画を作成してください。
テストのツール選択と基本的なテストケースのみリストアップし、
実際のコードは含めないでください。

AI は次のような計画を提案してきた


EvaluationResults コンポーネントのテスト計画

RTL によるコンポーネントテスト

  1. すべてのパラメータ名と値が正しく表示される
  2. 信頼度の低いパラメータには警告アイコンが表示される
  3. パラメータをクリックすると詳細情報が表示される
  4. ソート機能が正しく動作する

Jest によるロジックテスト

  1. パラメータのソート関数が正しく動作する
  2. 信頼度によるフィルタリング関数が正しく動作する

Playwright による統合テスト

  1. 評価ページから詳細表示、PDF エクスポートまでの基本フロー

この計画を見たとき、私はいくつかの調整が必要だと判断した。プロトタイプ段階では、詳細表示や PDF エクスポートはまだ優先度が低く、テスト対象から外したかった。そこで次のように計画を調整した

以下の調整をお願いします

1. Playwrightテストは今回は実装しません
2. RTLテストの3番目(クリックで詳細表示)は優先度が低いので省略します
3. 追加で「データがない場合に適切なメッセージが表示される」テストケースを加えてください

AI は調整後の計画を提示し、私はそれを確認した上で次のように実装を依頼した

調整後のテスト計画に基づいて、RTLとJestによるテストコードを実装してください。
テストデータは既存の __tests__/fixtures/evaluationData.js からインポートして使用してください。

このアプローチの利点として

  1. テスト範囲と粒度の事前調整: 実装前に方向性を合わせられる
  2. レビューの効率化: コードより計画の方がレビューしやすい
  3. AI への明確な指針: 適切な制約を事前に設定できる

特に、テストコードは実装コードより複雑になりがちなため、この「計画先行」は大きな時間節約につながった。AI 協働開発では、テストコードのレビューよりも、テスト計画のレビューに時間を使う方が効率的なのだ。

テストデータの一貫性を維持する具体的手法

AI との協働開発で直面したもう 1 つの課題が「テストデータの一貫性維持」だった。AI は各テストファイルで独自のテストデータを生成しがちで、これが保守性の低下につながった。

例えば、最初のプロジェクトでは、AI が次のようにテストごとに似て非なるテストデータを生成していた

// コンポーネントテストでのデータ
test("パラメータが表示される", () => {
  const testData = [
    { name: "技術力", value: 4.2, confidence: 0.8 },
    { name: "チーム力", value: 3.8, confidence: 0.7 },
  ];
  render(<EvaluationResults parameters={testData} />);
  // ...
});
 
// 別のファイルでの類似データ
test("パラメータが計算される", () => {
  const params = [
    { name: "技術力", value: 4.0, confidence: 0.75 },
    { name: "チーム力", value: 3.5, confidence: 0.65 },
  ];
  // ...
});

わずかに異なるデータを使用することで、テスト失敗時のデバッグが難しくなり、データ構造変更の際の修正作業も煩雑になった。

この問題を解決するため、私は「テストデータカタログ」というアプローチを採用した。これは、すべてのテストで使用するデータを一箇所に集約し、テスト間で共有する仕組みだ。

図表を生成中...

具体的な実装は次のようなものだ

// __tests__/fixtures/evaluationData.js
export const standardParameters = [
  { name: "技術力", value: 4.2, confidence: 0.8 },
  { name: "チーム力", value: 3.8, confidence: 0.7 },
  { name: "市場性", value: 4.5, confidence: 0.9 },
];
 
export const lowConfidenceParameters = [
  { name: "独自性", value: 2.8, confidence: 0.3 },
];
 
export const noParameters = [];
 
// モックAPI応答
export const mockApiResponse = {
  success: true,
  data: {
    parameters: standardParameters,
    evaluationId: "eval-123",
  },
};

そして、AI には次のように明示的に指示した

テストを書く際は、必ず __tests__/fixtures/evaluationData.js から
データをインポートして使用してください。
独自のテストデータを定義しないでください。

このアプローチをさらに発展させ、テストロジックも共有化する「テストプリセット」も導入した

// __tests__/presets/evaluationTests.js
import { render, screen } from "@testing-library/react";
import {
  standardParameters,
  lowConfidenceParameters,
} from "../fixtures/evaluationData";
 
// 再利用可能なテスト関数
export function testParameterDisplay(Component) {
  test("すべてのパラメータが正しく表示される", () => {
    render(<Component parameters={standardParameters} />);
 
    standardParameters.forEach((param) => {
      expect(screen.getByText(param.name)).toBeInTheDocument();
      expect(screen.getByText(param.value.toString())).toBeInTheDocument();
    });
  });
 
  test("信頼度の低いパラメータには警告表示がある", () => {
    const testParams = [...standardParameters, ...lowConfidenceParameters];
    render(<Component parameters={testParams} />);
 
    // 警告アイコンの検証
    const warningIcon = screen.getByRole("img", { name: /警告/i });
    expect(warningIcon).toBeInTheDocument();
 
    // 低信頼度パラメータの近くに警告アイコンがあることを検証
    const lowConfParam = screen.getByText(lowConfidenceParameters[0].name);
    expect(lowConfParam.parentNode).toContainElement(warningIcon);
  });
}

これをテストファイルから呼び出すことで、テストコードの重複を大幅に削減できた

// src/components/EvaluationDisplay/EvaluationDisplay.test.js
import { render, screen } from "@testing-library/react";
import { testParameterDisplay } from "../../../__tests__/presets/evaluationTests";
import EvaluationDisplay from "./EvaluationDisplay";
 
describe("EvaluationDisplay", () => {
  // 共通テストを実行
  testParameterDisplay(EvaluationDisplay);
 
  // このコンポーネント固有のテストを追加
  test("データがない場合は適切なメッセージが表示される", () => {
    render(<EvaluationDisplay parameters={[]} />);
    expect(screen.getByText("評価データがありません")).toBeInTheDocument();
  });
});

このアプローチにより得られた利点は

  1. テストデータの一貫性確保: 全テストで同じデータを使用
  2. テストの重複排除: 共通テストロジックを再利用
  3. メンテナンス性の向上: データ構造が変更された場合、修正は一箇所だけでよい

現在のプロジェクトではこのアプローチを標準としており、AI もこの構造をよく理解して効率的にテストを実装できるようになった。

3 つのライブラリを使いこなすための最適なワークフロー

1 ヶ月間の試行錯誤を経て、私は 3 つのテストライブラリを最適に組み合わせるワークフローを確立した。これは従来の開発と異なる、AI との協働に特化したアプローチだ。

人間と AI の役割分担

まず、テスト作成における人間と AI の役割分担を明確にした

図表を生成中...

この役割分担では、人間はテスト戦略の「何を」と「なぜ」を担当し、AI は「どのように」を担当するのがベストだとわかった。特に、テストケースの粒度調整は人間が行い、具体的な実装は AI に任せるこのアプローチが効率的だった。

テストライブラリ選択の明確な基準

各テストライブラリの最適な用途も明確になった。以下は私が採用した選択基準だ

  1. Jest(単体テスト)

    • 最適な用途:
      • ユーティリティ関数
      • データ変換ロジック
      • 複雑な計算処理
    • テスト実行時間: 数ミリ秒〜数百ミリ秒
    • AI へのガイダンス: モックの使用を最小限にし、テストケース数を明示的に制限
  2. React Testing Library(コンポーネントテスト)

    • 最適な用途:
      • UI コンポーネントの表示検証
      • フォーム入力やクリックなどのユーザー操作
      • 状態変化の検証
    • テスト実行時間: 数百ミリ秒〜1 秒
    • AI へのガイダンス: ユーザー視点のクエリ使用を徹底し、表示内容のテストに集中
  3. Playwright(E2E テスト)

    • 最適な用途:
      • 複数画面にまたがるユーザーフロー
      • バックエンドとの統合
      • 実際のブラウザ環境でしか検証できない振る舞い
    • テスト実行時間: 数秒〜数分
    • AI へのガイダンス: 最小限のシナリオに限定し、正常系のみテスト

重要なのは、これらの選択は 15 分サイクル開発という文脈での最適解であり、従来の開発サイクルでは異なる判断になる可能性があることだ。

特に私が経験したのは、「テスト実行時間」が従来の開発と AI 協働開発で持つ意味の大きな違いである。従来の開発では「テストの網羅性」を重視できたが、AI 協働開発では「レビュー効率」と「実行時間」が圧倒的に重要になる。

テスト実行の最適な戦略

最後に、私が採用したテスト実行戦略を紹介しよう。これは開発フェーズと変更の規模に応じて最適化されている

// package.json
{
  "scripts": {
    // 変更ファイルに関連するテストのみ実行(数秒以内)
    "test:related": "jest --findRelatedTests $(git diff --name-only)",
 
    // 単体・コンポーネントテストのみ実行(数十秒以内)
    "test:fast": "jest --testPathIgnorePatterns=integration,e2e",
 
    // 統合テストを実行(1分程度)
    "test:integration": "jest integration",
 
    // E2Eテストを実行(数分)
    "test:e2e": "playwright test",
 
    // すべてのテストを実行(CI/CD用)
    "test:ci": "npm run test:fast && npm run test:integration && npm run test:e2e"
  }
}

この戦略の使い分けは次のようになる

  1. 小規模な変更(1 ファイル程度): test:related で関連テストのみ実行
  2. 中規模な変更(複数ファイル): test:fast で単体・コンポーネントテストを実行
  3. 大規模な変更(機能全体): test:fasttest:integration を実行
  4. リリース前の検証: test:ci ですべてのテストを実行

最も重要なのは、15 分サイクル開発中はテスト実行による中断を最小限に抑えることだ。そのため、開発中は主に test:relatedtest:fast を使用し、E2E テストは CI パイプラインに任せるようにした。

プロトタイプと成熟プロダクトでのテスト戦略の違い

ここで重要な点を強調しておきたい。本記事で紹介したアプローチはプロトタイプ開発のフェーズに特化したものであり、成熟したプロダクトでは異なるテスト戦略が必要になる。

この違いを理解するため、プロジェクトの進化段階とそれに適したテスト戦略を図示してみよう

図表を生成中...

プロトタイプ開発のテスト目的:

  • 核心機能が想定通りに動作するかの検証
  • 仮説の迅速な検証と方向転換の支援
  • 開発速度の最大化

成熟段階のテスト目的:

  • 包括的な品質保証
  • リグレッション(機能退行)の防止
  • 長期的な保守性の確保

私の 1 ヶ月の体験は主にプロトタイプ開発段階でのものであり、そこでの最適解を見つけることに集中していた。成熟したプロダクトへの移行に伴い、テスト戦略も進化させていく必要がある。

具体的には、プロジェクトが成熟するにつれて

  1. テストの網羅性をより重視するようになる
  2. ユニットテストの比率を徐々に増やしていく
  3. エッジケースや異常系のテストを追加していく

AI 協働開発においても、プロジェクトの段階に応じて AI への指示を調整することが重要だ。プロトタイプ段階では「速度優先、基本機能のみテスト」という指示が適切だが、成熟段階では「網羅性重視、エッジケースも含めてテスト」という指示に変えていくべきだろう。

真の教訓:適切な制約が AI の生産性を最大化する

この 1 ヶ月の体験から得た最大の教訓は、AI に適切な制約を与えることで、その生産性を最大化できるということだ。

当初、私は AI にテスト作成を依頼する際に自由度を与えすぎていた。「良いテストを書いてください」というような漠然とした指示だ。その結果、AI は完璧を追求するあまり、過剰に複雑で実行時間の長いテストコードを生成していた。

しかし、次のような具体的な制約を設けることで、AI はより実用的なテストを生成するようになった

  1. テストケース数の制限: 「3 つのケースだけをテストしてください」
  2. テストの焦点の明確化: 「正常系のみテストしてください」
  3. 実行時間の制約: 「テストの実行時間が 30 秒以内に収まるようにしてください」
  4. コードサイズの制限: 「各テストは 5 行以内に収めてください」

これらの制約は、AI の無限の可能性を有用な範囲に絞り込む効果がある。私はこれを「生産的制約の原則」と呼んでいる。

この原則は、デザインシステムに似ている。無限の選択肢よりも、一貫性のある限定されたコンポーネントセットの方が、結果として優れた UI を生み出すのと同じように、AI に適切な制約を与えることで、より質の高い成果物を得られるのだ。

まとめ:15 分サイクル開発における最適なテスト戦略

AI 駆動開発における 15 分サイクルという文脈での、テストツールの使い分けについての私の 1 ヶ月の体験から得た核心的な教訓をまとめよう

  1. 15 分サイクルで相対的重要性が変わる: 従来は気にならなかったテスト実行時間が、AI 協働開発では重大なボトルネックになりうる

  2. テスト計画先行アプローチが効率的: テスト実装前に計画を立て、AI と合意することで大幅な時間節約が可能

  3. テストデータの一貫性を確保すべき: テストデータカタログを作成し、すべてのテストで共有することで保守性が向上する

  4. 各ツールの特性を活かして使い分ける:

    • Jest: ロジックの単体テストに
    • RTL: UI コンポーネントのテストに
    • Playwright: 重要な E2E フローのみに、CI 段階で実行
  5. 開発段階に応じた戦略調整が必要: プロトタイプと成熟製品ではテスト戦略が大きく異なる

  6. AI には具体的な制約を: 自由度を与えすぎると AI は過剰なテストを生成する

この 1 ヶ月の経験を通じて、AI 協働開発におけるテスト戦略は従来の常識と大きく異なることを学んだ。「テストは多いほど良い」という古い常識は、15 分サイクル開発では必ずしも当てはまらない。むしろ「適切な量と質のテスト」こそが新たな基準となる。

テスト戦略はプロジェクトの文脈によって大きく変わるものであり、AI 協働開発という新しい文脈では、テスト実行時間やレビュー効率といった要素が特に重要になる。これを理解し、適切なツールと手法を選択することで、AI 協働開発の真の力を引き出せるだろう。

最後に、ここで紹介した戦略は私自身の限られた経験に基づくものであり、あなたのプロジェクトでは異なるアプローチが最適かもしれない。重要なのは、自分のプロジェクトの文脈に合わせて柔軟に戦略を調整していくことだ。テストは目的ではなく手段であり、その目的は常に「より良いソフトウェアをより効率的に作ること」であることを忘れないでほしい。

吉崎 亮介

吉崎 亮介

株式会社和談 代表取締役社長 / 株式会社キカガク創業者

「知の循環を拓き、自律的な価値創造を駆動する」をミッションに、組織コミュニケーションの構造的変革に取り組んでいます。AI技術と社会ネットワーク分析を活用し、組織内の暗黙知を解放して深い対話を生み出すことで、創造的価値が持続的に生まれる組織の実現を目指しています。

最新のインサイトを受け取る

定期的なニュースレターで、技術とビジネスの境界領域に関する最新の記事や独自のインサイトをお届けします。