adr-rubber-duck
ADR(Architecture Decision Record)の壁打ち相手としてヒアリングを通じて意思決定を整理し、ADRドラフトを作成する
テスト実行速度を改善するためのリファクタリングパターンとベストプラクティス集
> /plugin marketplace add classmethod/tsumiki > /plugin install tsumiki@tsumiki
How it fires
How this command gets triggered: by you, by Claude, or both.
/test-optimization-patternsContext preview
What this command does when you run it.
テスト実行速度を改善するためのリファクタリングパターンとベストプラクティス集
description: テスト実行速度を改善するためのリファクタリングパターンとベストプラクティス集
テスト実行速度を改善するためのリファクタリングパターンとベストプラクティスをまとめたドキュメントです。
| パターン | 効果 | 難易度 | リスク | |---------|------|--------|--------| | 1. データベーストランザクション | ★★★★★ | 低 | 低 | | 2. ファイルI/Oのモック化 | ★★★★★ | 低 | 低 | | 3. 外部APIのモック化 | ★★★★★ | 中 | 低 | | 4. beforeEach → beforeAll | ★★★☆☆ | 低 | 中 | | 5. 非同期待機の最適化 | ★★★☆☆ | 中 | 低 | | 6. テストデータファクトリー | ★★☆☆☆ | 中 | 低 | | 7. テストの粒度見直し | ★★★☆☆ | 高 | 中 | | 8. 並列実行可能な設計 | ★★★☆☆ | 中 | 中 |
---
各テストでデータベースのクリア・再投入を行うと、I/O待機時間が累積して遅くなります。トランザクションを使ってロールバックすることで、物理的なDELETE/INSERTを避けて高速化できます。
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);
});
});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);
});
});---
実ファイルへの読み書きはディスクI/Oが発生して遅くなります。メモリ上の仮想ファイルシステムやモックを使うことで高速化できます。
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');
});**方法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)
);
});---
外部APIへの実際のHTTPリクエストはネットワーク遅延とサーバー処理時間がかかります。モックを使うことで高速化できます。
it('should fetch user data', async () => {
// 実際のAPI呼び出し(遅い)
const user = await fetchUser(1);
expect(user.name).toBe('John');
});**方法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');
});---
テストデータを変更しない読み取り専用のテストでは、beforeAllでデータを一度だけ準備すれば高速化できます。
describe('User queries', () => {
let testUser;
beforeEach(async () => {TsumikiはAI駆動開発のためのフレームワークです。要件定義から実装まで、AIを活用した効率的な開発プロセスを提供します。 基本的にClaude Codeをサポートしますが、それ以外のツールでも使用できます。Claude Code以外のツールでtsumikiを使用する を参照してください。
Repo: classmethod/tsumiki
ADR(Architecture Decision Record)の壁打ち相手としてヒアリングを通じて意思決定を整理し、ADRドラフトを作成する
テストエラーを解消するための自動デバッグプロセス。全テストケースの確認からエラー原因の調査、修正まで段階的に実行し、テストケースの成功率向上を目指します。
ビルドエラー(コンパイルエラー、型エラー、依存パッケージ未解決)を自動的に分析・修正します。auto-debugから委譲される他、ユーザが直接呼び出すことも可能です。
システムの各レイヤーにおける包括的なエッジケース・エラー状態を詳細に分析し、見落としがちな異常系シナリオを洗い出します