Skip to content
Development
Command

/test-optimization-patterns

テスト実行速度を改善するためのリファクタリングパターンとベストプラクティス集

BOOST
From plugin
tsumiki
97443 skills43 commands
Install
> /plugin marketplace add classmethod/tsumiki
> /plugin install tsumiki@tsumiki

How it fires

How this command gets triggered: by you, by Claude, or both.

  • Fires itselfClaude auto-loads it when your prompt matches the work.
  • You can call itInvoke it directly when you want it.
  • Slash command/test-optimization-patterns

Context preview

What this command does when you run it.

テスト実行速度を改善するためのリファクタリングパターンとベストプラクティス集

Command definition

test-optimization-patterns.md
description: テスト実行速度を改善するためのリファクタリングパターンとベストプラクティス集

テスト最適化パターン集

テスト実行速度を改善するためのリファクタリングパターンとベストプラクティスをまとめたドキュメントです。

パターン一覧

| パターン | 効果 | 難易度 | リスク | |---------|------|--------|--------| | 1. データベーストランザクション | ★★★★★ | 低 | 低 | | 2. ファイルI/Oのモック化 | ★★★★★ | 低 | 低 | | 3. 外部APIのモック化 | ★★★★★ | 中 | 低 | | 4. beforeEach → beforeAll | ★★★☆☆ | 低 | 中 | | 5. 非同期待機の最適化 | ★★★☆☆ | 中 | 低 | | 6. テストデータファクトリー | ★★☆☆☆ | 中 | 低 | | 7. テストの粒度見直し | ★★★☆☆ | 高 | 中 | | 8. 並列実行可能な設計 | ★★★☆☆ | 中 | 中 |

---

パターン1: データベーストランザクション

概要

各テストでデータベースのクリア・再投入を行うと、I/O待機時間が累積して遅くなります。トランザクションを使ってロールバックすることで、物理的なDELETE/INSERTを避けて高速化できます。

適用条件

  • ✅ beforeEach でデータのクリア・再投入を行っている
  • ✅ テストごとにクリーンな状態が必要
  • ✅ データベースがトランザクションをサポート

Before(遅い実装)

describe('User API', () => {
  beforeEach(async () => {
    // 毎回DELETE・INSERT(遅い)
    await db.query('DELETE FROM users');
    await db.query('DELETE FROM posts');
    await db.query('INSERT INTO users (id, name) VALUES (1, "Test")');
    await db.query('INSERT INTO posts (id, user_id, title) VALUES (1, 1, "Post")');
  });

  afterEach(async () => {
    await db.disconnect();
  });

  it('should get user', async () => {
    const user = await getUserById(1);
    expect(user.name).toBe('Test');
  });

  it('should get posts', async () => {
    const posts = await getPostsByUserId(1);
    expect(posts).toHaveLength(1);
  });
});

After(高速実装)

describe('User API', () => {
  let transaction;

  beforeEach(async () => {
    // トランザクション開始
    transaction = await db.transaction();

    // データ準備(トランザクション内)
    await transaction.query('INSERT INTO users (id, name) VALUES (1, "Test")');
    await transaction.query('INSERT INTO posts (id, user_id, title) VALUES (1, 1, "Post")');
  });

  afterEach(async () => {
    // ロールバック(高速)
    await transaction.rollback();
  });

  it('should get user', async () => {
    const user = await getUserById(1);
    expect(user.name).toBe('Test');
  });

  it('should get posts', async () => {
    const posts = await getPostsByUserId(1);
    expect(posts).toHaveLength(1);
  });
});

期待される効果

  • **実行時間**: 80-95%削減
  • **具体例**: 15秒 → 2秒

注意点

  • トランザクション内のデータは他のトランザクションから見えない(分離レベルによる)
  • 外部キー制約などは正しく動作する
  • ロールバックは物理削除より圧倒的に高速

---

パターン2: ファイルI/Oのモック化

概要

実ファイルへの読み書きはディスクI/Oが発生して遅くなります。メモリ上の仮想ファイルシステムやモックを使うことで高速化できます。

適用条件

  • ✅ fs.readFile, fs.writeFile などの実ファイル操作を行っている
  • ✅ ファイル内容の検証が目的(実際のファイルシステムの動作確認が不要)

Before(遅い実装)

it('should save config', async () => {
  const config = { port: 3000, host: 'localhost' };

  // 実ファイルに書き込み(遅い)
  await fs.writeFile('./config.json', JSON.stringify(config));

  // 実ファイルから読み込み(遅い)
  const saved = await loadConfig('./config.json');

  expect(saved.port).toBe(3000);

  // クリーンアップ
  await fs.unlink('./config.json');
});

After(高速実装)

**方法A: mock-fs を使用**

const mockFs = require('mock-fs');

beforeEach(() => {
  // メモリ上に仮想ファイルシステムを作成
  mockFs({
    '/tmp': {},
  });
});

afterEach(() => {
  mockFs.restore();
});

it('should save config', async () => {
  const config = { port: 3000, host: 'localhost' };

  // メモリ上のファイルシステムに書き込み(高速)
  await saveConfig('/tmp/config.json', config);
  const saved = await loadConfig('/tmp/config.json');

  expect(saved.port).toBe(3000);
  // クリーンアップ不要(afterEachで自動)
});

**方法B: jest.mock を使用**

jest.mock('fs/promises', () => ({
  readFile: jest.fn(),
  writeFile: jest.fn(),
}));

const fs = require('fs/promises');

it('should save config', async () => {
  const config = { port: 3000, host: 'localhost' };

  fs.writeFile.mockResolvedValue(undefined);
  fs.readFile.mockResolvedValue(JSON.stringify(config));

  await saveConfig('./config.json', config);

  expect(fs.writeFile).toHaveBeenCalledWith(
    './config.json',
    JSON.stringify(config)
  );
});

期待される効果

  • **実行時間**: 90-99%削減
  • **具体例**: 8秒 → 0.3秒

注意点

  • ファイルシステムの実際の動作(権限、シンボリックリンク等)は検証できない
  • そのような検証が必要な場合はE2Eテストとして分離

---

パターン3: 外部APIのモック化

概要

外部APIへの実際のHTTPリクエストはネットワーク遅延とサーバー処理時間がかかります。モックを使うことで高速化できます。

適用条件

  • ✅ fetch, axios などで外部APIを呼び出している
  • ✅ APIのレスポンス処理ロジックのテストが目的
  • ✅ 実際のAPI動作確認は不要

Before(遅い実装)

it('should fetch user data', async () => {
  // 実際のAPI呼び出し(遅い)
  const user = await fetchUser(1);
  expect(user.name).toBe('John');
});

After(高速実装)

**方法A: jest.mock でfetchをモック**

global.fetch = jest.fn();

beforeEach(() => {
  fetch.mockClear();
});

it('should fetch user data', async () => {
  // モックのレスポンスを設定(高速)
  fetch.mockResolvedValue({
    ok: true,
    json: async () => ({ id: 1, name: 'John' }),
  });

  const user = await fetchUser(1);

  expect(user.name).toBe('John');
  expect(fetch).toHaveBeenCalledWith('https://api.example.com/users/1');
});

**方法B: MSW (Mock Service Worker) を使用**

import { rest } from 'msw';
import { setupServer } from 'msw/node';

const server = setupServer(
  rest.get('https://api.example.com/users/:id', (req, res, ctx) => {
    return res(
      ctx.status(200),
      ctx.json({ id: Number(req.params.id), name: 'John' })
    );
  })
);

beforeAll(() => server.listen());
afterEach(() => server.resetHandlers());
afterAll(() => server.close());

it('should fetch user data', async () => {
  const user = await fetchUser(1);
  expect(user.name).toBe('John');
});

it('should handle API errors', async () => {
  server.use(
    rest.get('https://api.example.com/users/:id', (req, res, ctx) => {
      return res(ctx.status(404));
    })
  );

  await expect(fetchUser(999)).rejects.toThrow('User not found');
});

期待される効果

  • **実行時間**: 90-99%削減
  • **具体例**: 5秒 → 0.1秒

注意点

  • 実際のAPIの動作(認証、レート制限等)は検証できない
  • API統合テストはE2Eテストとして分離

---

パターン4: beforeEach → beforeAll

概要

テストデータを変更しない読み取り専用のテストでは、beforeAllでデータを一度だけ準備すれば高速化できます。

適用条件

  • ✅ 複数のテストで同じデータを使用
  • ✅ データを変更しない(読み取り専用)
  • ✅ テストの独立性が保たれる

Before(遅い実装)

describe('User queries', () => {
  let testUser;

  beforeEach(async () => {
Read more
Ships withtsumiki

TsumikiはAI駆動開発のためのフレームワークです。要件定義から実装まで、AIを活用した効率的な開発プロセスを提供します。 基本的にClaude Codeをサポートしますが、それ以外のツールでも使用できます。Claude Code以外のツールでtsumikiを使用する を参照してください。

Get the whole plugin

Other commands on tsumiki.

auto-debug
Command

auto-debug

テストエラーを解消するための自動デバッグプロセス。全テストケースの確認からエラー原因の調査、修正まで段階的に実行し、テストケースの成功率向上を目指します。

build-fix
Command

build-fix

ビルドエラー(コンパイルエラー、型エラー、依存パッケージ未解決)を自動的に分析・修正します。auto-debugから委譲される他、ユーザが直接呼び出すことも可能です。