0
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

【初心者向け】Claude Codeでテストコードを自動生成する方法

0
Posted at

はじめに

「テスト書かなきゃいけないのはわかってるけど、面倒...」

多くのエンジニアが感じている悩みですよね。テストコードの作成は重要だけど、時間がかかる地味な作業です。

Claude Codeなら 既存コードを分析して、適切なテストを自動生成 してくれます。この記事で、テストの苦手意識を克服しましょう。

テスト生成の基本フロー

1. テスト対象のファイルを指定
2. テストの種類・フレームワークを伝える
3. Claude Codeがテストを生成
4. テストを実行して確認
5. 足りないケースを追加

実践1:ユーティリティ関数のユニットテスト

テスト対象

// src/utils/validator.ts
export function isValidEmail(email: string): boolean {
  const regex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
  return regex.test(email);
}

export function isValidPassword(password: string): boolean {
  return password.length >= 8
    && /[A-Z]/.test(password)
    && /[a-z]/.test(password)
    && /[0-9]/.test(password);
}

export function sanitizeInput(input: string): string {
  return input.replace(/[<>&"']/g, (char) => {
    const entities: Record<string, string> = {
      '<': '&lt;', '>': '&gt;', '&': '&amp;',
      '"': '&quot;', "'": '&#39;'
    };
    return entities[char];
  });
}

プロンプト

> @src/utils/validator.ts のユニットテストを書いて。
> フレームワークはJest。正常系・異常系・境界値を網羅して。

生成されるテスト

// src/utils/__tests__/validator.test.ts
import { isValidEmail, isValidPassword, sanitizeInput } from '../validator';

describe('isValidEmail', () => {
  // 正常系
  it('標準的なメールアドレスを有効と判定する', () => {
    expect(isValidEmail('user@example.com')).toBe(true);
  });

  it('サブドメイン付きアドレスを有効と判定する', () => {
    expect(isValidEmail('user@mail.example.com')).toBe(true);
  });

  it('プラス記号付きアドレスを有効と判定する', () => {
    expect(isValidEmail('user+tag@example.com')).toBe(true);
  });

  // 異常系
  it('@がないアドレスを無効と判定する', () => {
    expect(isValidEmail('userexample.com')).toBe(false);
  });

  it('ドメインがないアドレスを無効と判定する', () => {
    expect(isValidEmail('user@')).toBe(false);
  });

  it('空文字を無効と判定する', () => {
    expect(isValidEmail('')).toBe(false);
  });

  it('スペースを含むアドレスを無効と判定する', () => {
    expect(isValidEmail('user @example.com')).toBe(false);
  });
});

describe('isValidPassword', () => {
  // 正常系
  it('条件を満たすパスワードを有効と判定する', () => {
    expect(isValidPassword('Password1')).toBe(true);
  });

  // 異常系
  it('7文字以下を無効と判定する', () => {
    expect(isValidPassword('Pass1')).toBe(false);
  });

  it('大文字なしを無効と判定する', () => {
    expect(isValidPassword('password1')).toBe(false);
  });

  it('小文字なしを無効と判定する', () => {
    expect(isValidPassword('PASSWORD1')).toBe(false);
  });

  it('数字なしを無効と判定する', () => {
    expect(isValidPassword('Password')).toBe(false);
  });

  // 境界値
  it('ちょうど8文字で条件を満たせば有効', () => {
    expect(isValidPassword('Abcdefg1')).toBe(true);
  });
});

describe('sanitizeInput', () => {
  it('HTMLタグをエスケープする', () => {
    expect(sanitizeInput('<script>')).toBe('&lt;script&gt;');
  });

  it('アンパサンドをエスケープする', () => {
    expect(sanitizeInput('A&B')).toBe('A&amp;B');
  });

  it('引用符をエスケープする', () => {
    expect(sanitizeInput('"hello"')).toBe('&quot;hello&quot;');
  });

  it('特殊文字がない文字列はそのまま返す', () => {
    expect(sanitizeInput('hello world')).toBe('hello world');
  });

  it('複数の特殊文字を同時にエスケープする', () => {
    expect(sanitizeInput('<div class="test">')).toBe('&lt;div class=&quot;test&quot;&gt;');
  });
});

正常系・異常系・境界値が網羅されたテストが一発で生成されます。

実践2:APIエンドポイントのテスト

プロンプト

> @src/routes/users.ts のAPIエンドポイントのテストを書いて。
> supertestを使って。モックも適切に設定して。
> GET /users、POST /users、GET /users/:id をテストして。

生成されるテスト(例)

import request from 'supertest';
import app from '../app';
import { db } from '../database';

// DBをモック
jest.mock('../database');

describe('Users API', () => {
  beforeEach(() => {
    jest.clearAllMocks();
  });

  describe('GET /users', () => {
    it('ユーザー一覧を返す', async () => {
      const mockUsers = [
        { id: 1, name: 'Alice', email: 'alice@example.com' },
        { id: 2, name: 'Bob', email: 'bob@example.com' },
      ];
      (db.query as jest.Mock).mockResolvedValue(mockUsers);

      const response = await request(app).get('/users');

      expect(response.status).toBe(200);
      expect(response.body).toEqual(mockUsers);
      expect(response.body).toHaveLength(2);
    });

    it('ユーザーが0件の場合、空配列を返す', async () => {
      (db.query as jest.Mock).mockResolvedValue([]);

      const response = await request(app).get('/users');

      expect(response.status).toBe(200);
      expect(response.body).toEqual([]);
    });
  });

  describe('POST /users', () => {
    it('新規ユーザーを作成する', async () => {
      const newUser = { name: 'Charlie', email: 'charlie@example.com' };
      (db.query as jest.Mock).mockResolvedValue({ id: 3, ...newUser });

      const response = await request(app)
        .post('/users')
        .send(newUser);

      expect(response.status).toBe(201);
      expect(response.body.name).toBe('Charlie');
    });

    it('名前が空の場合、400エラーを返す', async () => {
      const response = await request(app)
        .post('/users')
        .send({ name: '', email: 'test@example.com' });

      expect(response.status).toBe(400);
    });
  });

  describe('GET /users/:id', () => {
    it('指定IDのユーザーを返す', async () => {
      const mockUser = { id: 1, name: 'Alice', email: 'alice@example.com' };
      (db.query as jest.Mock).mockResolvedValue(mockUser);

      const response = await request(app).get('/users/1');

      expect(response.status).toBe(200);
      expect(response.body.name).toBe('Alice');
    });

    it('存在しないIDの場合、404を返す', async () => {
      (db.query as jest.Mock).mockResolvedValue(null);

      const response = await request(app).get('/users/999');

      expect(response.status).toBe(404);
    });
  });
});

実践3:Reactコンポーネントのテスト

プロンプト

> @src/components/LoginForm.tsx のテストを書いて。
> React Testing Libraryを使って。
> ユーザー操作のシミュレーションも含めて。

生成されるテスト(例)

import { render, screen, fireEvent, waitFor } from '@testing-library/react';
import userEvent from '@testing-library/user-event';
import { LoginForm } from '../LoginForm';

describe('LoginForm', () => {
  const mockOnSubmit = jest.fn();

  beforeEach(() => {
    mockOnSubmit.mockClear();
    render(<LoginForm onSubmit={mockOnSubmit} />);
  });

  it('メールとパスワードの入力欄が表示される', () => {
    expect(screen.getByLabelText('メールアドレス')).toBeInTheDocument();
    expect(screen.getByLabelText('パスワード')).toBeInTheDocument();
  });

  it('ログインボタンが表示される', () => {
    expect(screen.getByRole('button', { name: 'ログイン' })).toBeInTheDocument();
  });

  it('入力してsubmitすると、onSubmitが呼ばれる', async () => {
    const user = userEvent.setup();

    await user.type(screen.getByLabelText('メールアドレス'), 'test@example.com');
    await user.type(screen.getByLabelText('パスワード'), 'Password1');
    await user.click(screen.getByRole('button', { name: 'ログイン' }));

    await waitFor(() => {
      expect(mockOnSubmit).toHaveBeenCalledWith({
        email: 'test@example.com',
        password: 'Password1',
      });
    });
  });

  it('メール未入力でsubmitすると、エラーが表示される', async () => {
    const user = userEvent.setup();

    await user.click(screen.getByRole('button', { name: 'ログイン' }));

    expect(await screen.findByText('メールアドレスを入力してください')).toBeInTheDocument();
    expect(mockOnSubmit).not.toHaveBeenCalled();
  });
});

実践4:テストカバレッジを上げる

プロンプト

> テストカバレッジを確認して、カバーされていない箇所のテストを追加して

Claude Codeの動き

  1. npx jest --coverage を実行
  2. カバレッジレポートを分析
  3. カバーされていないブランチ・行を特定
  4. 不足しているテストケースを生成
カバレッジレポート:
- validator.ts: 85% → 100% に改善(elseブランチ追加)
- auth.ts: 62% → 91% に改善(エラーケース追加)
- user.ts: 78% → 95% に改善(境界値テスト追加)

実践5:テストを実行して失敗を修正

プロンプト

> テストを全部実行して。失敗しているものがあれば原因を特定して修正して。
> テストコードかプロダクションコードのどちらに問題があるか判断して。

Claude Codeは以下を自動で行います:

  1. テスト実行
  2. 失敗テストの分析
  3. 原因がテストコード側かプロダクション側か判断
  4. 適切な方を修正
  5. 再テストで確認

テスト生成のプロンプト集

種類別

> ユニットテストを書いて
> 統合テストを書いて
> E2Eテストをplaywrightで書いて
> スナップショットテストを書いて

フレームワーク指定

> Jestでテストを書いて
> Vitestでテストを書いて
> pytestでテストを書いて
> RSpecでテストを書いて

網羅性の指定

> 正常系・異常系・境界値を全て網羅して
> エッジケースも含めて
> エラーハンドリングのテストも書いて
> 並行処理のテストも含めて

既存テストの改善

> 既存のテストをレビューして、抜けているケースを追加して
> テストの可読性を改善して
> テストの実行速度を改善して
> フレイキーなテストを安定化させて

テスト作成のコツ

1. テスト対象を明確に

# 良い例:ファイルと関数を指定
> @src/utils/validator.ts の isValidEmail 関数のテストを書いて

# 悪い例:曖昧
> テストを書いて

2. フレームワークを指定する

プロジェクトで使っているテストフレームワークを伝えましょう。

> Jest + React Testing Library でテストを書いて

3. モックの方針を伝える

> DBはモックして、外部APIもモックして。
> ファイルシステムはモックしなくていい。

4. テストファースト(TDD)もできる

> まずテストを書いて。次に、そのテストが通る実装を書いて。

Claude CodeでTDD(テスト駆動開発)を実践することもできます。

まとめ

テストの種類 プロンプト例
ユニットテスト 「この関数のテストをJestで書いて」
APIテスト 「supertestでエンドポイントのテストを書いて」
コンポーネントテスト 「React Testing Libraryでテストを書いて」
カバレッジ向上 「カバレッジを確認して不足分を追加して」
失敗テスト修正 「テストを実行して失敗を修正して」

テストはAIに任せるのが最も効率的な作業の1つです。 「テスト書くのが面倒」という悩みは、Claude Codeで解決しましょう。

次回予告

次の記事では、Claude Codeでgit操作を効率化する 方法を解説します。

コミット、ブランチ、PR作成まで自然言語で!


シリーズ一覧

  1. Claude Codeとは?概要・できること
  2. Claude Codeのインストールと初期設定
  3. 基本的な使い方・コマンド一覧
  4. Claude Codeでコード生成してみよう
  5. Claude Codeでバグ修正・デバッグ
  6. Claude Codeでリファクタリング
  7. 👉 Claude Codeでテストコード作成(本記事)
  8. Claude Codeでgit操作を効率化
  9. CLAUDE.mdを活用したプロジェクト設定
  10. Claude Code活用のベストプラクティス

著者: @kotaro_ai_lab
AI駆動開発やテック情報を毎日発信しています。フォローお気軽にどうぞ!

0
1
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?