From tsumiki
Reverse-generates comprehensive test cases and specification documents from existing code by analyzing business logic, API behavior, and UI components, identifying missing coverage, and outputting test specs and plans.
How this command is triggered — by the user, by Claude, or both
Slash command
/tsumiki:rev-specsFiles this command reads when invoked
The summary Claude sees in its command listing — used to decide when to auto-load this command
# rev-specs
## 目的
既存のコードベースから包括的なテストケースと仕様書を逆生成する。実装されたビジネスロジック、API動作、UI コンポーネントの動作を分析し、不足しているテストケースを特定・生成し、仕様書として文書化する。
## 前提条件
- 分析対象のコードベースが存在する
- `docs/spec/{要件名}/` ディレクトリが存在する(なければ作成)
- 可能であれば事前に `/tsumiki:rev-requirements`, `/tsumiki:rev-design` を実行済み
## 実行内容
1. **既存テストの分析**
- 単体テスト(Unit Test)の実装状況確認
- 統合テスト(Integration Test)の実装状況確認
- E2Eテスト(End-to-End Test)の実装状況確認
- テストカバレッジの測定
2. **実装コードからテストケースの逆生成**
- 関数・メソッドの引数・戻り値からのテストケース生成
- 条件分岐からの境界値テスト生成
- エラーハンドリングからの異常系テスト生成
- データベース操作からのデータテスト生成
3. **API仕様からテストケースの生成**
- 各エンドポイントの正常系テスト
- 認証・認可テスト
...既存のコードベースから包括的なテストケースと仕様書を逆生成する。実装されたビジネスロジック、API動作、UI コンポーネントの動作を分析し、不足しているテストケースを特定・生成し、仕様書として文書化する。
docs/spec/{要件名}/ ディレクトリが存在する(なければ作成)/tsumiki:rev-requirements, /tsumiki:rev-design を実行済み既存テストの分析
実装コードからテストケースの逆生成
API仕様からテストケースの生成
UI コンポーネントからテストケースの生成
パフォーマンス・セキュリティテストケースの生成
テスト仕様書の生成
ファイルの作成
docs/spec/{要件名}/test-specs.md - テスト仕様書docs/spec/{要件名}/test-cases.md - テストケース一覧docs/spec/{要件名}/tests/ - 生成されたテストコード# {要件名} テスト仕様書(逆生成)
## 分析概要
**分析日時**: {実行日時}
**対象コードベース**: {パス}
**テストカバレッジ**: {現在のカバレッジ}%
**生成テストケース数**: {生成数}個
**実装推奨テスト数**: {推奨数}個
## 現在のテスト実装状況
### テストフレームワーク
- **単体テスト**: {Jest/Vitest/pytest等}
- **統合テスト**: {Supertest/TestContainers等}
- **E2Eテスト**: {Cypress/Playwright等}
- **コードカバレッジ**: {istanbul/c8等}
### テストカバレッジ詳細
| ファイル/ディレクトリ | 行カバレッジ | 分岐カバレッジ | 関数カバレッジ |
|---------------------|-------------|-------------|-------------|
| src/auth/ | 85% | 75% | 90% |
| src/users/ | 60% | 45% | 70% |
| src/components/ | 40% | 30% | 50% |
| **全体** | **65%** | **55%** | **75%** |
### テストカテゴリ別実装状況
#### 単体テスト
- [x] **認証サービス**: auth.service.spec.ts
- [x] **ユーザーサービス**: user.service.spec.ts
- [ ] **データ変換ユーティリティ**: 未実装
- [ ] **バリデーションヘルパー**: 未実装
#### 統合テスト
- [x] **認証API**: auth.controller.spec.ts
- [ ] **ユーザー管理API**: 未実装
- [ ] **データベース操作**: 未実装
#### E2Eテスト
- [ ] **ユーザーログインフロー**: 未実装
- [ ] **データ操作フロー**: 未実装
- [ ] **エラーハンドリング**: 未実装
## 生成されたテストケース
### API テストケース
#### POST /auth/login - ログイン認証
**正常系テスト**
```typescript
describe('POST /auth/login', () => {
it('有効な認証情報でログイン成功', async () => {
const response = await request(app)
.post('/auth/login')
.send({
email: '[email protected]',
password: 'password123'
});
expect(response.status).toBe(200);
expect(response.body.success).toBe(true);
expect(response.body.data.token).toBeDefined();
expect(response.body.data.user.email).toBe('[email protected]');
});
it('JWTトークンが正しい形式で返される', async () => {
const response = await request(app)
.post('/auth/login')
.send(validCredentials);
const token = response.body.data.token;
expect(token).toMatch(/^[A-Za-z0-9-_]+\.[A-Za-z0-9-_]+\.[A-Za-z0-9-_]+$/);
});
});
異常系テスト
describe('POST /auth/login - 異常系', () => {
it('無効なメールアドレスでエラー', async () => {
const response = await request(app)
.post('/auth/login')
.send({
email: 'invalid-email',
password: 'password123'
});
expect(response.status).toBe(400);
expect(response.body.success).toBe(false);
expect(response.body.error.code).toBe('VALIDATION_ERROR');
});
it('存在しないユーザーでエラー', async () => {
const response = await request(app)
.post('/auth/login')
.send({
email: '[email protected]',
password: 'password123'
});
expect(response.status).toBe(401);
expect(response.body.error.code).toBe('INVALID_CREDENTIALS');
});
it('パスワード間違いでエラー', async () => {
const response = await request(app)
.post('/auth/login')
.send({
email: '[email protected]',
password: 'wrongpassword'
});
expect(response.status).toBe(401);
expect(response.body.error.code).toBe('INVALID_CREDENTIALS');
});
});
境界値テスト
describe('POST /auth/login - 境界値', () => {
it('最小文字数パスワードでテスト', async () => {
// 8文字(最小要件)
const response = await request(app)
.post('/auth/login')
.send({
email: '[email protected]',
password: '12345678'
});
expect(response.status).toBe(200);
});
it('最大文字数メールアドレスでテスト', async () => {
// 255文字(最大要件)
const longEmail = 'a'.repeat(243) + '@example.com';
const response = await request(app)
.post('/auth/login')
.send({
email: longEmail,
password: 'password123'
});
expect(response.status).toBe(400);
});
});
レンダリングテスト
import { render, screen } from '@testing-library/react';
import { LoginForm } from './LoginForm';
describe('LoginForm', () => {
it('必要な要素が表示される', () => {
render(<LoginForm onSubmit={jest.fn()} />);
expect(screen.getByLabelText('メールアドレス')).toBeInTheDocument();
expect(screen.getByLabelText('パスワード')).toBeInTheDocument();
expect(screen.getByRole('button', { name: 'ログイン' })).toBeInTheDocument();
});
it('初期状態でエラーメッセージが非表示', () => {
render(<LoginForm onSubmit={jest.fn()} />);
expect(screen.queryByText(/エラー/)).not.toBeInTheDocument();
});
});
ユーザーインタラクションテスト
import { render, screen, fireEvent, waitFor } from '@testing-library/react';
import userEvent from '@testing-library/user-event';
describe('LoginForm - ユーザーインタラクション', () => {
it('フォーム送信時にonSubmitが呼ばれる', async () => {
const mockSubmit = jest.fn();
render(<LoginForm onSubmit={mockSubmit} />);
await userEvent.type(screen.getByLabelText('メールアドレス'), '[email protected]');
await userEvent.type(screen.getByLabelText('パスワード'), 'password123');
await userEvent.click(screen.getByRole('button', { name: 'ログイン' }));
expect(mockSubmit).toHaveBeenCalledWith({
email: '[email protected]',
password: 'password123'
});
});
it('バリデーションエラー時に送信されない', async () => {
const mockSubmit = jest.fn();
render(<LoginForm onSubmit={mockSubmit} />);
await userEvent.click(screen.getByRole('button', { name: 'ログイン' }));
expect(mockSubmit).not.toHaveBeenCalled();
expect(screen.getByText('メールアドレスは必須です')).toBeInTheDocument();
});
});
import { AuthService } from './auth.service';
import { UserRepository } from './user.repository';
jest.mock('./user.repository');
describe('AuthService', () => {
let authService: AuthService;
let mockUserRepository: jest.Mocked<UserRepository>;
beforeEach(() => {
mockUserRepository = new UserRepository() as jest.Mocked<UserRepository>;
authService = new AuthService(mockUserRepository);
});
describe('login', () => {
it('有効な認証情報でユーザー情報とトークンを返す', async () => {
const mockUser = {
id: '1',
email: '[email protected]',
hashedPassword: 'hashed_password'
};
mockUserRepository.findByEmail.mockResolvedValue(mockUser);
jest.spyOn(authService, 'verifyPassword').mockResolvedValue(true);
jest.spyOn(authService, 'generateToken').mockReturnValue('mock_token');
const result = await authService.login('[email protected]', 'password');
expect(result).toEqual({
user: { id: '1', email: '[email protected]' },
token: 'mock_token'
});
});
it('存在しないユーザーでエラーをスロー', async () => {
mockUserRepository.findByEmail.mockResolvedValue(null);
await expect(
authService.login('[email protected]', 'password')
).rejects.toThrow('Invalid credentials');
});
});
});
describe('パフォーマンステスト', () => {
it('ログインAPI - 100同時接続テスト', async () => {
const promises = Array.from({ length: 100 }, () =>
request(app).post('/auth/login').send(validCredentials)
);
const startTime = Date.now();
const responses = await Promise.all(promises);
const endTime = Date.now();
// 全てのリクエストが成功
responses.forEach(response => {
expect(response.status).toBe(200);
});
// 応答時間が5秒以内
expect(endTime - startTime).toBeLessThan(5000);
});
it('データベース - 大量データ検索性能', async () => {
// 1000件のテストデータを作成
await createTestData(1000);
const startTime = Date.now();
const response = await request(app)
.get('/users')
.query({ limit: 100, offset: 0 });
const endTime = Date.now();
expect(response.status).toBe(200);
expect(endTime - startTime).toBeLessThan(1000); // 1秒以内
});
});
describe('セキュリティテスト', () => {
it('SQLインジェクション対策', async () => {
const maliciousInput = "'; DROP TABLE users; --";
const response = await request(app)
.post('/auth/login')
.send({
email: maliciousInput,
password: 'password'
});
// システムが正常に動作し、データベースが破損していない
expect(response.status).toBe(400);
// ユーザーテーブルが依然として存在することを確認
const usersResponse = await request(app)
.get('/users')
.set('Authorization', 'Bearer ' + validToken);
expect(usersResponse.status).not.toBe(500);
});
it('XSS対策', async () => {
const xssPayload = '<script>alert("XSS")</script>';
const response = await request(app)
.post('/users')
.set('Authorization', 'Bearer ' + validToken)
.send({
name: xssPayload,
email: '[email protected]'
});
// レスポンスでスクリプトがエスケープされている
expect(response.body.data.name).not.toContain('<script>');
expect(response.body.data.name).toContain('<script>');
});
});
// ユーザーログインフロー E2Eテスト
describe('ユーザーログインフロー', () => {
it('正常なログインからダッシュボード表示まで', async () => {
await page.goto('/login');
// ログインフォーム入力
await page.fill('[data-testid="email-input"]', '[email protected]');
await page.fill('[data-testid="password-input"]', 'password123');
await page.click('[data-testid="login-button"]');
// ダッシュボードへリダイレクト
await page.waitForURL('/dashboard');
// ユーザー情報表示確認
await expect(page.locator('[data-testid="user-name"]')).toContainText('テストユーザー');
// ログアウト機能確認
await page.click('[data-testid="logout-button"]');
await page.waitForURL('/login');
});
it('ログイン失敗時のエラー表示', async () => {
await page.goto('/login');
await page.fill('[data-testid="email-input"]', '[email protected]');
await page.fill('[data-testid="password-input"]', 'wrongpassword');
await page.click('[data-testid="login-button"]');
// エラーメッセージ表示確認
await expect(page.locator('[data-testid="error-message"]'))
.toContainText('認証情報が正しくありません');
});
});
// テスト用データベース設定
beforeAll(async () => {
// テスト用データベース接続
await setupTestDatabase();
// マイグレーション実行
await runMigrations();
});
beforeEach(async () => {
// 各テスト前にデータをクリーンアップ
await cleanupDatabase();
// 基本テストデータ投入
await seedTestData();
});
afterAll(async () => {
// テスト用データベース切断
await teardownTestDatabase();
});
// 外部サービスのモック
jest.mock('./email.service', () => ({
EmailService: jest.fn().mockImplementation(() => ({
sendEmail: jest.fn().mockResolvedValue(true)
}))
}));
// 環境変数のモック
process.env.JWT_SECRET = 'test-secret';
process.env.NODE_ENV = 'test';
### test-cases.md
```markdown
# {要件名} テストケース一覧(逆生成)
## テストケース概要
| ID | テスト名 | カテゴリ | 優先度 | 実装状況 | 推定工数 |
|----|----------|----------|--------|----------|----------|
| TC-001 | ログイン正常系 | API | 高 | ✅ | 2h |
| TC-002 | ログイン異常系 | API | 高 | ✅ | 3h |
| TC-003 | E2Eログインフロー | E2E | 高 | ❌ | 4h |
| TC-004 | パフォーマンス負荷テスト | パフォーマンス | 中 | ❌ | 6h |
## 詳細テストケース
### TC-001: ログインAPI正常系テスト
**テスト目的**: 有効な認証情報でのログイン機能を検証
**事前条件**:
- テストユーザーがデータベースに存在する
- パスワードが正しくハッシュ化されている
**テスト手順**:
1. POST /auth/login にリクエスト送信
2. 有効なemail, passwordを含むJSONを送信
3. レスポンスを確認
**期待結果**:
- HTTPステータス: 200
- success: true
- data.token: JWT形式のトークン
- data.user: ユーザー情報
**実装ファイル**: `auth.controller.spec.ts`
### TC-002: ログインAPI異常系テスト
**テスト目的**: 無効な認証情報での適切なエラーハンドリングを検証
**テストケース**:
1. 存在しないメールアドレス
2. 無効なパスワード
3. 不正なメール形式
4. 空文字・null値
5. SQLインジェクション攻撃
**期待結果**:
- 適切なHTTPステータスコード
- 統一されたエラーレスポンス形式
- セキュリティ脆弱性がない
**実装状況**: ✅ 部分的実装
1. 関数シグネチャ解析 → 引数・戻り値のテストケース
2. 条件分岐解析 → 分岐網羅テストケース
3. 例外処理解析 → 異常系テストケース
4. データベースアクセス解析 → データテストケース
1. API呼び出しログ → 実際の使用パターンテスト
2. ユーザー操作ログ → E2Eテストシナリオ
3. パフォーマンスログ → 負荷テストシナリオ
1. 現在のカバレッジ測定
2. 未テスト行・分岐の特定
3. クリティカルパスの特定
4. リスクベース優先順位付け
# フル分析(全テストケース生成)
claude code rev-specs
# 特定のテストカテゴリのみ生成
claude code rev-specs --type unit
claude code rev-specs --type integration
claude code rev-specs --type e2e
# 特定のファイル/ディレクトリを対象
claude code rev-specs --path ./src/auth
# テストコードの実際の生成と出力
claude code rev-specs --generate-code
# カバレッジレポートと合わせて分析
claude code rev-specs --with-coverage
# 優先度フィルタリング
claude code rev-specs --priority high
npx claudepluginhub classmethod/tsumiki --plugin tsumiki/generate-test-casesGenerates unit, integration, edge case, performance, and error-handling test cases for target code or functions, including mocks, stubs, data-driven tests, and coverage analysis.
/coverGenerates and runs unit, integration (docker-compose/testcontainers), and Playwright E2E test suites for existing code. Analyzes coverage gaps, spawns parallel agents per tier, executes tests, and heals failures up to 3 times.
/testWrites and runs unit, integration, or E2E tests for provided code, acceptance criteria, and critical flows. Ensures coverage of happy paths, errors, edge cases; documents gaps.
/test-planCreates a structured test plan from feature artifacts — queries test types, environment, and credentials, auto-detects the test framework, generates test cases mapped to user stories, and saves them under features/<name>/testing/.