ドメイン単位テスト実行
特定のドメインのテストのみを実行し、詳細なレポートとカバレッジを生成します。全体テスト(数分)ではなく、特定ドメインのみ(数秒)で高速フィードバックを提供します。
# 特定のドメインのテストを実行
/test-domain opportunity
# カバレッジ付きで実行
/test-domain user --coverage
# 全てのテストを実行
/test-domain
引数:
$ARGUMENTS[0]: ドメイン名(例: opportunity, user, wallet)--coverage カバレッジレポートを生成$ARGUMENTS が指定された場合、そのドメインのテストファイルを検索:
DOMAIN_NAME="$ARGUMENTS"
# ドメインのテストファイルを検索
find src/application/domain -path "*/${DOMAIN_NAME}/*" -name "*.test.ts"
find __tests__ -path "*/${DOMAIN_NAME}/*" -name "*.test.ts"
検証:
テストファイルが見つからない場合:
⚠️ ドメイン `${DOMAIN_NAME}` のテストファイルが見つかりませんでした
以下を確認してください:
1. ドメイン名が正しいか(例: `opportunity`, `user`, `wallet`)
2. テストファイルが存在するか
- `src/application/domain/**/${DOMAIN_NAME}/**/*.test.ts`
- `__tests__/**/${DOMAIN_NAME}/**/*.test.ts`
見つかったテストファイルを表示:
## テスト対象ファイル
以下のテストファイルを実行します:
- src/application/domain/experience/opportunity/service.test.ts
- src/application/domain/experience/opportunity/data/converter.test.ts
- __tests__/unit/opportunity/usecase.test.ts
- __tests__/integration/opportunity/graphql.test.ts
合計: 4ファイル
基本実行(カバレッジなし):
pnpm test src/application/domain/**/${DOMAIN_NAME}/**/*.test.ts __tests__/**/${DOMAIN_NAME}/**/*.test.ts --runInBand
カバレッジ付き実行:
pnpm test:coverage src/application/domain/**/${DOMAIN_NAME}/**/*.test.ts __tests__/**/${DOMAIN_NAME}/**/*.test.ts --runInBand
重要:
--runInBand フラグを必ず使用(データベーステストのシリアル実行)成功した場合:
## テスト結果
✅ 全てのテストが成功しました
**統計:**
- テストスイート: 4 passed
- テスト数: 24 passed
- 実行時間: 3.2秒
**カバレッジ:**
- Statements: 95.2%
- Branches: 88.7%
- Functions: 92.1%
- Lines: 94.8%
失敗した場合:
## テスト結果
❌ 一部のテストが失敗しました
**統計:**
- テストスイート: 3 passed, 1 failed
- テスト数: 20 passed, 4 failed
- 実行時間: 3.5秒
### 失敗したテスト
1. **OpportunityService.createOpportunity**
- ファイル: `src/application/domain/experience/opportunity/service.test.ts:45`
- エラー: `Expected communityId to be validated`
- 修正案: Serviceでのバリデーションロジックを確認
2. **OpportunityUseCase.managerCreateOpportunity**
- ファイル: `__tests__/unit/opportunity/usecase.test.ts:120`
- エラー: `TypeError: Cannot read property 'id' of null`
- 修正案: モックデータが正しくセットアップされているか確認
### 推奨アクション
1. 失敗したテストファイルを確認
2. モックやフィクスチャデータを見直す
3. テストを修正して再実行: `/test-domain opportunity`
カバレッジレポートを読み取り、分析:
# カバレッジレポートの表示
cat coverage/lcov-report/index.html
カバレッジが低い場合の警告:
## カバレッジ警告
⚠️ 以下のファイルはカバレッジが低いです(< 80%):
| ファイル | Statements | Branches | Functions | Lines |
|---------|-----------|----------|-----------|-------|
| service.ts | 68.2% | 55.3% | 70.0% | 67.8% |
| converter.ts | 75.4% | 62.1% | 80.0% | 74.9% |
### 推奨事項
1. **service.ts** のエッジケースをカバーするテストを追加
2. **converter.ts** のエラーハンドリングをテスト
3. ブランチカバレッジを向上(条件分岐のテスト)
/test-domain opportunity --unit
対象: __tests__/unit/**/${DOMAIN_NAME}/**/*.test.ts
/test-domain opportunity --integration
対象: __tests__/integration/**/${DOMAIN_NAME}/**/*.test.ts
/test-domain opportunity --e2e
対象: __tests__/e2e/**/${DOMAIN_NAME}/**/*.test.ts
対象:
特徴:
例:
describe("OpportunityService", () => {
let service: OpportunityService;
let mockRepository: jest.Mocked<IOpportunityRepository>;
beforeEach(() => {
mockRepository = {
create: jest.fn(),
find: jest.fn(),
} as any;
service = new OpportunityService(mockRepository);
});
it("should create opportunity with valid data", async () => {
const input = { title: "Test", description: "Test" };
mockRepository.create.mockResolvedValue(mockOpportunity);
const result = await service.createOpportunity(ctx, input, "community-id", tx);
expect(result).toBeDefined();
expect(mockRepository.create).toHaveBeenCalledWith(ctx, expect.any(Object), tx);
});
});
対象:
特徴:
例:
describe("OpportunityUseCase Integration", () => {
let usecase: OpportunityUseCase;
let ctx: IContext;
beforeEach(async () => {
// 実際のDIコンテナから解決
usecase = container.resolve(OpportunityUseCase);
ctx = await createTestContext();
});
afterEach(async () => {
// テストデータのクリーンアップ
await cleanupTestData();
});
it("should create opportunity and commit transaction", async () => {
const input: GqlOpportunityCreateInput = {
title: "Test Opportunity",
description: "Test Description",
};
const result = await usecase.managerCreateOpportunity({ input, permission }, ctx);
expect(result.opportunity).toBeDefined();
// データベースで確認
const saved = await prisma.opportunity.findUnique({ where: { id: result.opportunity.id } });
expect(saved).toBeDefined();
});
});
対象:
特徴:
例:
describe("Opportunity GraphQL API", () => {
let server: ApolloServer;
let token: string;
beforeAll(async () => {
server = await createTestServer();
token = await generateTestToken({ role: "manager" });
});
it("should create opportunity via GraphQL mutation", async () => {
const mutation = `
mutation {
opportunityCreate(input: {
title: "Test Opportunity"
description: "Test Description"
}) {
opportunity {
id
title
}
}
}
`;
const response = await server.executeOperation(
{ query: mutation },
{ req: { headers: { authorization: `Bearer ${token}` } } }
);
expect(response.data?.opportunityCreate.opportunity).toBeDefined();
});
});
モックの設定ミス
// ❌ 間違い
mockRepository.create.mockResolvedValue(undefined);
// ✅ 正しい
mockRepository.create.mockResolvedValue(mockOpportunity);
トランザクション関連
// ❌ 間違い: txを渡していない
await service.createOpportunity(ctx, input, communityId);
// ✅ 正しい: txを渡す
await service.createOpportunity(ctx, input, communityId, tx);
非同期処理の待機忘れ
// ❌ 間違い: await なし
service.createOpportunity(ctx, input, communityId, tx);
// ✅ 正しい: await あり
await service.createOpportunity(ctx, input, communityId, tx);
テストデータのクリーンアップ漏れ
afterEach(async () => {
// ✅ 必ずクリーンアップ
await prisma.opportunity.deleteMany({ where: { title: { startsWith: "Test" } } });
});
"should throw error when communityId is invalid""test create opportunity"| レイヤー | 目標カバレッジ | 理由 |
|---|---|---|
| Service | 90%以上 | ビジネスロジックの中核 |
| UseCase | 85%以上 | フロー制御の検証 |
| Converter | 95%以上 | シンプルな変換ロジック |
| Presenter | 95%以上 | シンプルなフォーマッティング |
| Repository | 80%以上 | 統合テストでカバー |
| Resolver | 70%以上 | E2Eテストでカバー |
以下については @CLAUDE.md を参照してください:
pnpm test)