testing-builder について
Testing Builderは、テスト作成の手作業を減らしたい開発者向けのテスト自動生成スキルです。コードを分析し、保存済みのテスト設定を反映して、境界条件を考慮したテストを生成し、パターンを次回のために保存します。
主な機能
- 01コード分析とテスト種別の特定
- 02保存済みのテスト設定やパターンの参照
- 03ユニット・統合・E2E・回帰テストの生成
- 04カバレッジと境界条件の確認
- 05再利用に向けたテストパターンの保存
活用シーン
- 01新しい関数のユニットテスト生成
- 02バグ修正後の回帰テスト作成
- 03プロジェクトの書式に沿ったAPI統合テスト
- 04重要なユーザーフローのE2Eテスト生成
インストール
コマンドをコピーしてターミナルで実行します。
npx skills add https:/ / github.com/ jackspace/ claudeskillz --skill testing-builderインストール後、Claude Code で/testing-builderを入力して読み込まれたか確認します。
同梱ファイルSKILL.json
詳細
テストビルダー
目的
テストスタイルを学習する自動テスト生成機能です。テストを手間なく行えるようにし、ADHDによるテストのハードルを取り除きます。
- コードを分析して、何をテストする必要があるかを把握します
- メモリからテストパターンを呼び出します
- 包括的なテストスイートを生成します
- すべてのエッジケースが網羅されていることを確認します
- 今後のためにテストパターンを保存します
ADHDのあるユーザー向け:手間はゼロです。手作業なしでテストをすぐに作成できます。 すべてのユーザー向け:面倒な作業をせずに、包括的なカバレッジを得られます。 学習システム:時間とともに、ユーザーのテストスタイルへの適合度が高まります。
起動トリガー
- ユーザーが「テストを書いて」「これをテストして」「テストを追加して」「カバレッジ」と言ったとき
- バグ修正後:「回帰テストを作成して」
- コードレビュー時:「テストが必要」
- ユーザーが「ユニットテスト」「統合テスト」「E2Eテスト」に言及したとき
基本ワークフロー
1. コードを分析する
ステップ1:テスト対象を特定します
// Example: Analyzing a function
function calculateDiscount(price, discountPercent, customerType) {
if (customerType === 'premium') {
discountPercent += 10;
}
return price * (1 - discountPercent / 100);
}
// Identify:
- Function name: calculateDiscount
- Parameters: price, discountPercent, customerType
- Logic branches: premium vs non-premium
- Edge cases: negative values, zero, boundary conditions
- Return type: number
ステップ2:必要なテストの種類を判断します
- ユニットテスト:純粋関数、ユーティリティ、コンポーネント
- 統合テスト:複数のコンポーネントが連携する機能、APIエンドポイント
- E2Eテスト:ユーザーの一連のワークフロー、重要な経路
- 回帰テスト:修正した特定のバグ
2. テストパターンを呼び出す
context-manager に問い合わせます:
search memories:
- Type: PREFERENCE, PROCEDURE
- Tags: testing, test-framework, test-style
- Project: current project
呼び出す内容:
- テストフレームワーク(Jest、Vitest、Pytest など)
- アサーションのスタイル(expect、assert、should)
- テストの構造(describe/it、test ブロック)
- モックのパターン(jest.mock、vi.mock)
- カバレッジ要件
- ファイルの命名規則
メモリの例:
PREFERENCE: Testing style for BOOSTBOX
- Framework: Vitest
- Assertion: expect()
- Structure: describe/it blocks
- Coverage: Minimum 80%
- File naming: {filename}.test.js
3. テストスイートを生成する
ユニットテストのテンプレート:
import { describe, it, expect } from 'vitest';
import { calculateDiscount } from './discount';
describe('calculateDiscount', () => {
describe('basic calculations', () => {
it('should calculate discount correctly for regular customers', () => {
const result = calculateDiscount(100, 10, 'regular');
expect(result).toBe(90);
});
it('should add bonus discount for premium customers', () => {
const result = calculateDiscount(100, 10, 'premium');
expect(result).toBe(80); // 10% + 10% bonus
});
});
describe('edge cases', () => {
it('should handle zero discount', () => {
const result = calculateDiscount(100, 0, 'regular');
expect(result).toBe(100);
});
it('should handle 100% discount', () => {
const result = calculateDiscount(100, 100, 'regular');
expect(result).toBe(0);
});
it('should handle zero price', () => {
const result = calculateDiscount(0, 10, 'regular');
expect(result).toBe(0);
});
});
describe('invalid inputs', () => {
it('should handle negative price', () => {
const result = calculateDiscount(-100, 10, 'regular');
expect(result).toBeLessThan(0);
});
it('should handle invalid customer type', () => {
const result = calculateDiscount(100, 10, 'unknown');
expect(result).toBe(90); // Falls back to regular
});
});
});
統合テストのテンプレート(APIエンドポイント):
import { describe, it, expect, beforeAll, afterAll } from 'vitest';
import request from 'supertest';
import { app } from './app';
import { db } from './db';
describe('POST /api/users', () => {
beforeAll(async () => {
await db.connect();
});
afterAll(async () => {
await db.disconnect();
});
it('should create a new user', async () => {
const response = await request(app)
.post('/api/users')
.send({
email: '[email protected]',
name: 'Test User'
});
expect(response.status).toBe(201);
expect(response.body).toHaveProperty('id');
expect(response.body.email).toBe('[email protected]');
});
it('should reject duplicate email', async () => {
await request(app).post('/api/users').send({
email: '[email protected]',
name: 'User 1'
});
const response = await request(app)
.post('/api/users')
.send({
email: '[email protected]',
name: 'User 2'
});
expect(response.status).toBe(409);
expect(response.body.error).toContain('already exists');
});
it('should validate required fields', async () => {
const response = await request(app)
.post('/api/users')
.send({
name: 'No Email'
});
expect(response.status).toBe(400);
expect(response.body.error).toContain('email');
});
});
E2Eテストのテンプレート(Playwright):
import { test, expect } from '@playwright/test';
test.describe('User Login Flow', () => {
test('should login successfully with valid credentials', async ({ page }) => {
await page.goto('/login');
await page.fill('[name="email"]', '[email protected]');
await page.fill('[name="password"]', 'password123');
await page.click('button[type="submit"]');
await expect(page).toHaveURL('/dashboard');
await expect(page.locator('.welcome-message')).toContainText('Welcome');
});
test('should show error with invalid credentials', async ({ page }) => {
await page.goto('/login');
await page.fill('[name="email"]', '[email protected]');
await page.fill('[name="password"]', 'wrongpass');
await page.click('button[type="submit"]');
await expect(page.locator('.error-message')).toContainText('Invalid');
await expect(page).toHaveURL('/login');
});
test('should validate required fields', async ({ page }) => {
await page.goto('/login');
await page.click('button[type="submit"]');
await expect(page.locator('[name="email"]:invalid')).toBeVisible();
await expect(page.locator('[name="password"]:invalid')).toBeVisible();
});
});
4. カバレッジを分析する
テスト済みの項目を確認します:
// Coverage requirements (from memory or defaults)
const requirements = {
statements: 80,
branches: 75,
functions: 90,
lines: 80
};
// Identify untested scenarios
const missing = [
'Error handling for network failures',
'Loading state transitions',
'Concurrent operations',
'Cleanup on unmount'
];
// Generate additional tests for missing coverage
5. テストパターンを保存する
テストを生成した後:
# Save testing style to memory
remember: Testing pattern for authentication
Type: PROCEDURE
Tags: testing, authentication, integration
Content: For auth endpoints, always test:
- Valid credentials → success
- Invalid credentials → 401
- Missing fields → 400
- Expired token → 401
- Token refresh flow
フレームワーク固有のパターン
Jest/Vitest(JavaScript/TypeScript)
import { describe, it, expect, vi, beforeEach } from 'vitest';
describe('Component', () => {
beforeEach(() => {
vi.clearAllMocks();
});
it('should do something', () => {
expect(result).toBe(expected);
});
});
モック:
// Mock module
vi.mock('./api', () => ({
fetchData: vi.fn(() => Promise.resolve({ data: [] }))
}));
// Mock implementation
const mockFn = vi.fn().mockImplementation(() => 'value');
Pytest(Python)
import pytest
from mymodule import calculate_discount
class TestCalculateDiscount:
def test_regular_customer(self):
result = calculate_discount(100, 10, 'regular')
assert result == 90
def test_premium_customer(self):
result = calculate_discount(100, 10, 'premium')
assert result == 80
@pytest.mark.parametrize("price,discount,expected", [
(100, 0, 100),
(100, 100, 0),
(0, 10, 0),
])
def test_edge_cases(self, price, discount, expected):
result = calculate_discount(price, discount, 'regular')
assert result == expected
フィクスチャ:
@pytest.fixture
def sample_user():
return {
'id': 1,
'email': '[email protected]',
'name': 'Test User'
}
def test_user_creation(sample_user):
assert sample_user['email'] == '[email protected]'
React Testing Library
import { render, screen, fireEvent } from '@testing-library/react';
import { expect, test } from 'vitest';
import { UserList } from './UserList';
test('renders user list', () => {
const users = [
{ id: 1, name: 'Alice' },
{ id: 2, name: 'Bob' }
];
render(<UserList users={users} />);
expect(screen.getByText('Alice')).toBeInTheDocument();
expect(screen.getByText('Bob')).toBeInTheDocument();
});
test('handles empty user list', () => {
render(<UserList users={[]} />);
expect(screen.getByText('No users found')).toBeInTheDocument();
});
test('handles user click', () => {
const handleClick = vi.fn();
const users = [{ id: 1, name: 'Alice' }];
render(<UserList users={users} onUserClick={handleClick} />);
fireEvent.click(screen.getByText('Alice'));
expect(handleClick).toHaveBeenCalledWith(users[0]);
});
テストカテゴリ
ユニットテスト
テスト対象:
- 純粋関数
- ユーティリティ関数
- コンポーネントのロジック
- クラスのメソッド
重点項目:
- 入力と出力の正確性
- エッジケース
- エラー条件
- 境界値
統合テスト
テスト対象:
- APIエンドポイント
- データベース操作
- 複数コンポーネントの連携
- 外部サービスとの統合
重点項目:
- コンポーネント間のやり取り
- データフロー
- エラーの伝播
- トランザクション処理
E2Eテスト
テスト対象:
- 重要なユーザーフロー
- 完成した機能
- 複数ステップのプロセス
- ページをまたぐワークフロー
重点項目:
- ユーザーの視点
- 実際のブラウザー操作
- フルスタックの統合
- 本番環境に近い環境
回帰テスト
作成するタイミング:
- バグの修正後
- 既知の問題の再発防止
- 過去に発生した問題への対応
構成:
describe('Regression: Bug #123 - User map undefined', () => {
it('should handle undefined users array', () => {
// Test that previously failed
const result = renderUserList(undefined);
expect(result).not.toThrow();
});
});
コンテキストとの統合
テスト設定を呼び出す
テストを生成する前に:
// Query context-manager
const preferences = searchMemories({
type: 'PREFERENCE',
tags: ['testing', project],
recent: true
});
// Apply preferences
const testConfig = {
framework: preferences.framework || 'vitest',
style: preferences.style || 'describe/it',
coverage: preferences.coverage || 80,
naming: preferences.naming || '{name}.test.js'
};
テストパターンを保存する
テストを生成した後:
remember: Testing pattern for API endpoints
Type: PROCEDURE
Tags: testing, api, integration
Content: For API endpoints, always include:
1. Happy path (200/201)
2. Validation errors (400)
3. Auth errors (401/403)
4. Not found (404)
5. Server errors (500)
6. Rate limiting (429)
フィードバックから学習する
ユーザーが生成されたテストを変更した場合:
// Analyze what user changed
const changes = diff(generatedTest, userModifiedTest);
// Save as new pattern if significant
if (changes.significant) {
saveMemory({
type: 'PREFERENCE',
content: `User prefers ${changes.pattern}`,
tags: ['testing', 'user-preference']
});
}
他のスキルとの統合
Error Debugger
error-debugger がバグを修正したとき:
Automatically invoke testing-builder
Create regression test for: {bug_scenario}
Ensure test fails before fix, passes after
Context Manager
テスト設定を読み込む:
Query for PREFERENCE with tags: [testing, test-framework]
Apply framework, style, coverage preferences
新しいパターンを保存する:
After generating tests for new scenario
Save pattern as PROCEDURE for future reference
Code Quality Auditor
テストの品質を確認する:
After generating tests:
→ Invoke code-quality-auditor
→ Verify test coverage
→ Check for test smells
→ Ensure meaningful assertions
クイックリファレンス
テスト生成チェックリスト
✅ 正常系を網羅 ✅ エッジケースをテスト ✅ エラー条件に対応 ✅ 無効な入力を検証 ✅ 境界値を確認 ✅ 非同期処理に対応 ✅ モックを適切に設定 ✅ クリーンアップを適切に実施 ✅ アサーションに意味がある
よく使うテストパターン
| シナリオ | パターン |
|---|---|
| 関数 | 入力 → 出力のアサーション |
| コンポーネント | レンダリング → クエリ → アサーション |
| API | リクエスト → レスポンス → アサーション |
| 非同期処理 | await → Promise → 結果 |
| エラー | Try/catch → エラーのアサーション |
| モック | セットアップ → 呼び出し → モックを検証 |
トリガーフレーズ
- 「テストを書いて」
- 「これをテストして」
- 「カバレッジを追加して」
- 「リグレッションテストを作成して」
- 「テストが必要です」
ファイルの場所
- テストパターン:
~/.claude-memories/ (Linux/macOS) or %USERPROFILE%\.claude-memories\ (Windows)procedures/(「testing」タグ付き) - テスト設定:
~/.claude-memories/ (Linux/macOS) or %USERPROFILE%\.claude-memories\ (Windows)preferences/(「testing」タグ付き) - 生成されたテスト:
{filename}.test.{ext}またはtests/{filename}.test.{ext}
成功基準
✅ 30 秒未満でテストを生成 ✅ 包括的なカバレッジ(デフォルトで 80% 以上) ✅ すべてのエッジケースを含む ✅ プロジェクトのスタイルに準拠したテスト ✅ 手作業が不要 ✅ 今後のためにテストパターンを保存