@einja/dev-cli 0.1.6
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +179 -0
- package/dist/cli.d.ts +2 -0
- package/dist/cli.d.ts.map +1 -0
- package/dist/cli.js +49 -0
- package/dist/cli.js.map +1 -0
- package/dist/commands/init.d.ts +3 -0
- package/dist/commands/init.d.ts.map +1 -0
- package/dist/commands/init.js +243 -0
- package/dist/commands/init.js.map +1 -0
- package/dist/commands/list.d.ts +2 -0
- package/dist/commands/list.d.ts.map +1 -0
- package/dist/commands/list.js +23 -0
- package/dist/commands/list.js.map +1 -0
- package/dist/commands/sync.d.ts +7 -0
- package/dist/commands/sync.d.ts.map +1 -0
- package/dist/commands/sync.js +294 -0
- package/dist/commands/sync.js.map +1 -0
- package/dist/commands/sync.test.d.ts +2 -0
- package/dist/commands/sync.test.d.ts.map +1 -0
- package/dist/commands/sync.test.js +593 -0
- package/dist/commands/sync.test.js.map +1 -0
- package/dist/commands/task-loop.d.ts +11 -0
- package/dist/commands/task-loop.d.ts.map +1 -0
- package/dist/commands/task-loop.js +81 -0
- package/dist/commands/task-loop.js.map +1 -0
- package/dist/index.d.ts +4 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +3 -0
- package/dist/index.js.map +1 -0
- package/dist/lib/file-system.d.ts +39 -0
- package/dist/lib/file-system.d.ts.map +1 -0
- package/dist/lib/file-system.js +79 -0
- package/dist/lib/file-system.js.map +1 -0
- package/dist/lib/mcp-config.d.ts +43 -0
- package/dist/lib/mcp-config.d.ts.map +1 -0
- package/dist/lib/mcp-config.js +109 -0
- package/dist/lib/mcp-config.js.map +1 -0
- package/dist/lib/mcp-config.test.d.ts +2 -0
- package/dist/lib/mcp-config.test.d.ts.map +1 -0
- package/dist/lib/mcp-config.test.js +285 -0
- package/dist/lib/mcp-config.test.js.map +1 -0
- package/dist/lib/merger.d.ts +41 -0
- package/dist/lib/merger.d.ts.map +1 -0
- package/dist/lib/merger.js +164 -0
- package/dist/lib/merger.js.map +1 -0
- package/dist/lib/preset-update/cli-repo-detector.d.ts +35 -0
- package/dist/lib/preset-update/cli-repo-detector.d.ts.map +1 -0
- package/dist/lib/preset-update/cli-repo-detector.js +83 -0
- package/dist/lib/preset-update/cli-repo-detector.js.map +1 -0
- package/dist/lib/preset-update/cli-repo-detector.test.d.ts +2 -0
- package/dist/lib/preset-update/cli-repo-detector.test.d.ts.map +1 -0
- package/dist/lib/preset-update/cli-repo-detector.test.js +120 -0
- package/dist/lib/preset-update/cli-repo-detector.test.js.map +1 -0
- package/dist/lib/preset-update/file-copier.d.ts +59 -0
- package/dist/lib/preset-update/file-copier.d.ts.map +1 -0
- package/dist/lib/preset-update/file-copier.js +220 -0
- package/dist/lib/preset-update/file-copier.js.map +1 -0
- package/dist/lib/preset-update/file-copier.test.d.ts +2 -0
- package/dist/lib/preset-update/file-copier.test.d.ts.map +1 -0
- package/dist/lib/preset-update/file-copier.test.js +297 -0
- package/dist/lib/preset-update/file-copier.test.js.map +1 -0
- package/dist/lib/preset-update/preset-finder.d.ts +39 -0
- package/dist/lib/preset-update/preset-finder.d.ts.map +1 -0
- package/dist/lib/preset-update/preset-finder.js +92 -0
- package/dist/lib/preset-update/preset-finder.js.map +1 -0
- package/dist/lib/preset-update/preset-finder.test.d.ts +2 -0
- package/dist/lib/preset-update/preset-finder.test.d.ts.map +1 -0
- package/dist/lib/preset-update/preset-finder.test.js +128 -0
- package/dist/lib/preset-update/preset-finder.test.js.map +1 -0
- package/dist/lib/preset.d.ts +14 -0
- package/dist/lib/preset.d.ts.map +1 -0
- package/dist/lib/preset.js +52 -0
- package/dist/lib/preset.js.map +1 -0
- package/dist/lib/sync/backup-manager.d.ts +50 -0
- package/dist/lib/sync/backup-manager.d.ts.map +1 -0
- package/dist/lib/sync/backup-manager.js +117 -0
- package/dist/lib/sync/backup-manager.js.map +1 -0
- package/dist/lib/sync/backup-manager.test.d.ts +2 -0
- package/dist/lib/sync/backup-manager.test.d.ts.map +1 -0
- package/dist/lib/sync/backup-manager.test.js +155 -0
- package/dist/lib/sync/backup-manager.test.js.map +1 -0
- package/dist/lib/sync/batch-processor.d.ts +27 -0
- package/dist/lib/sync/batch-processor.d.ts.map +1 -0
- package/dist/lib/sync/batch-processor.js +46 -0
- package/dist/lib/sync/batch-processor.js.map +1 -0
- package/dist/lib/sync/batch-processor.test.d.ts +2 -0
- package/dist/lib/sync/batch-processor.test.d.ts.map +1 -0
- package/dist/lib/sync/batch-processor.test.js +110 -0
- package/dist/lib/sync/batch-processor.test.js.map +1 -0
- package/dist/lib/sync/category-validator.d.ts +36 -0
- package/dist/lib/sync/category-validator.d.ts.map +1 -0
- package/dist/lib/sync/category-validator.js +46 -0
- package/dist/lib/sync/category-validator.js.map +1 -0
- package/dist/lib/sync/category-validator.test.d.ts +2 -0
- package/dist/lib/sync/category-validator.test.d.ts.map +1 -0
- package/dist/lib/sync/category-validator.test.js +89 -0
- package/dist/lib/sync/category-validator.test.js.map +1 -0
- package/dist/lib/sync/conflict-reporter.d.ts +57 -0
- package/dist/lib/sync/conflict-reporter.d.ts.map +1 -0
- package/dist/lib/sync/conflict-reporter.js +81 -0
- package/dist/lib/sync/conflict-reporter.js.map +1 -0
- package/dist/lib/sync/conflict-reporter.test.d.ts +2 -0
- package/dist/lib/sync/conflict-reporter.test.d.ts.map +1 -0
- package/dist/lib/sync/conflict-reporter.test.js +132 -0
- package/dist/lib/sync/conflict-reporter.test.js.map +1 -0
- package/dist/lib/sync/diff-engine.d.ts +28 -0
- package/dist/lib/sync/diff-engine.d.ts.map +1 -0
- package/dist/lib/sync/diff-engine.js +118 -0
- package/dist/lib/sync/diff-engine.js.map +1 -0
- package/dist/lib/sync/diff-engine.test.d.ts +2 -0
- package/dist/lib/sync/diff-engine.test.d.ts.map +1 -0
- package/dist/lib/sync/diff-engine.test.js +133 -0
- package/dist/lib/sync/diff-engine.test.js.map +1 -0
- package/dist/lib/sync/file-filter.d.ts +40 -0
- package/dist/lib/sync/file-filter.d.ts.map +1 -0
- package/dist/lib/sync/file-filter.js +171 -0
- package/dist/lib/sync/file-filter.js.map +1 -0
- package/dist/lib/sync/file-filter.test.d.ts +2 -0
- package/dist/lib/sync/file-filter.test.d.ts.map +1 -0
- package/dist/lib/sync/file-filter.test.js +179 -0
- package/dist/lib/sync/file-filter.test.js.map +1 -0
- package/dist/lib/sync/hash-cache.d.ts +34 -0
- package/dist/lib/sync/hash-cache.d.ts.map +1 -0
- package/dist/lib/sync/hash-cache.js +51 -0
- package/dist/lib/sync/hash-cache.js.map +1 -0
- package/dist/lib/sync/hash-cache.test.d.ts +2 -0
- package/dist/lib/sync/hash-cache.test.d.ts.map +1 -0
- package/dist/lib/sync/hash-cache.test.js +110 -0
- package/dist/lib/sync/hash-cache.test.js.map +1 -0
- package/dist/lib/sync/integration.test.d.ts +2 -0
- package/dist/lib/sync/integration.test.d.ts.map +1 -0
- package/dist/lib/sync/integration.test.js +317 -0
- package/dist/lib/sync/integration.test.js.map +1 -0
- package/dist/lib/sync/marker-processor.d.ts +54 -0
- package/dist/lib/sync/marker-processor.d.ts.map +1 -0
- package/dist/lib/sync/marker-processor.js +208 -0
- package/dist/lib/sync/marker-processor.js.map +1 -0
- package/dist/lib/sync/marker-processor.test.d.ts +2 -0
- package/dist/lib/sync/marker-processor.test.d.ts.map +1 -0
- package/dist/lib/sync/marker-processor.test.js +245 -0
- package/dist/lib/sync/marker-processor.test.js.map +1 -0
- package/dist/lib/sync/metadata-manager.d.ts +46 -0
- package/dist/lib/sync/metadata-manager.d.ts.map +1 -0
- package/dist/lib/sync/metadata-manager.js +129 -0
- package/dist/lib/sync/metadata-manager.js.map +1 -0
- package/dist/lib/sync/metadata-manager.test.d.ts +2 -0
- package/dist/lib/sync/metadata-manager.test.d.ts.map +1 -0
- package/dist/lib/sync/metadata-manager.test.js +137 -0
- package/dist/lib/sync/metadata-manager.test.js.map +1 -0
- package/dist/lib/sync/performance.test.d.ts +2 -0
- package/dist/lib/sync/performance.test.d.ts.map +1 -0
- package/dist/lib/sync/performance.test.js +126 -0
- package/dist/lib/sync/performance.test.js.map +1 -0
- package/dist/types/index.d.ts +59 -0
- package/dist/types/index.d.ts.map +1 -0
- package/dist/types/index.js +2 -0
- package/dist/types/index.js.map +1 -0
- package/dist/types/preset-update.d.ts +106 -0
- package/dist/types/preset-update.d.ts.map +1 -0
- package/dist/types/preset-update.js +5 -0
- package/dist/types/preset-update.js.map +1 -0
- package/dist/types/sync.d.ts +169 -0
- package/dist/types/sync.d.ts.map +1 -0
- package/dist/types/sync.js +19 -0
- package/dist/types/sync.js.map +1 -0
- package/package.json +72 -0
- package/presets/minimal/.claude/agents/einja/docs/docs-updater.md +161 -0
- package/presets/minimal/.claude/agents/einja/frontend/design-engineer.md +685 -0
- package/presets/minimal/.claude/agents/einja/frontend/frontend-architect.md +747 -0
- package/presets/minimal/.claude/agents/einja/frontend/frontend-coder.md +441 -0
- package/presets/minimal/.claude/agents/einja/git/conflict-resolver.md +148 -0
- package/presets/minimal/.claude/agents/einja/specs/spec-design-generator.md +462 -0
- package/presets/minimal/.claude/agents/einja/specs/spec-qa-generator.md +466 -0
- package/presets/minimal/.claude/agents/einja/specs/spec-requirements-generator.md +416 -0
- package/presets/minimal/.claude/agents/einja/specs/spec-tasks-generator.md +608 -0
- package/presets/minimal/.claude/agents/einja/task/task-committer.md +82 -0
- package/presets/minimal/.claude/agents/einja/task/task-executer.md +352 -0
- package/presets/minimal/.claude/agents/einja/task/task-modification-analyzer.md +369 -0
- package/presets/minimal/.claude/agents/einja/task/task-qa.md +74 -0
- package/presets/minimal/.claude/agents/einja/task/task-reviewer.md +169 -0
- package/presets/minimal/.claude/commands/einja/frontend-implement.md +322 -0
- package/presets/minimal/.claude/commands/einja/spec-create.md +254 -0
- package/presets/minimal/.claude/commands/einja/start-dev.md +98 -0
- package/presets/minimal/.claude/commands/einja/sync-cursor-commands.md +203 -0
- package/presets/minimal/.claude/commands/einja/task-exec.md +390 -0
- package/presets/minimal/.claude/commands/einja/update-docs-by-task-specs.md +448 -0
- package/presets/minimal/.claude/hooks/einja/biome-format.sh +49 -0
- package/presets/minimal/.claude/hooks/einja/design-doc-check.sh +61 -0
- package/presets/minimal/.claude/hooks/einja/detect-secrets.sh +62 -0
- package/presets/minimal/.claude/hooks/einja/large-file-warning.sh +42 -0
- package/presets/minimal/.claude/hooks/einja/playwright-resize.sh +36 -0
- package/presets/minimal/.claude/hooks/einja/typecheck.sh +37 -0
- package/presets/minimal/.claude/hooks/einja/unset-volta-recursion.sh +32 -0
- package/presets/minimal/.claude/hooks/einja/validate-git-commit.sh +239 -0
- package/presets/minimal/.claude/hooks/einja/warn-index-ts.sh +34 -0
- package/presets/minimal/.claude/hooks/einja/warn-relative-import.sh +48 -0
- package/presets/minimal/.claude/settings.json +174 -0
- package/presets/minimal/.claude/skills/einja/api-development/SKILL.md +14 -0
- package/presets/minimal/.claude/skills/einja/backend-architecture/SKILL.md +14 -0
- package/presets/minimal/.claude/skills/einja/coding-standards/SKILL.md +120 -0
- package/presets/minimal/.claude/skills/einja/coding-standards/reference/naming-conventions.md +107 -0
- package/presets/minimal/.claude/skills/einja/coding-standards/reference/prohibited-patterns.md +169 -0
- package/presets/minimal/.claude/skills/einja/coding-standards/reference/typescript-rules.md +247 -0
- package/presets/minimal/.claude/skills/einja/component-design/SKILL.md +109 -0
- package/presets/minimal/.claude/skills/einja/component-design/reference/directory-structure.md +117 -0
- package/presets/minimal/.claude/skills/einja/component-design/reference/props-patterns.md +159 -0
- package/presets/minimal/.claude/skills/einja/component-design/reference/styling-guide.md +200 -0
- package/presets/minimal/.claude/skills/einja/conflict-resolver/SKILL.md +190 -0
- package/presets/minimal/.claude/skills/einja/frontend-development/SKILL.md +14 -0
- package/presets/minimal/.claude/skills/einja/general-context-loader/SKILL.md +254 -0
- package/presets/minimal/.claude/skills/einja/output-format/SKILL.md +137 -0
- package/presets/minimal/.claude/skills/einja/spec-context-loader/SKILL.md +177 -0
- package/presets/minimal/.claude/skills/einja/task-commit/SKILL.md +269 -0
- package/presets/minimal/.claude/skills/einja/task-qa/SKILL.md +306 -0
- package/presets/minimal/.claude/skills/einja/task-qa/reference/failure-patterns.md +69 -0
- package/presets/minimal/.claude/skills/einja/task-qa/reference/troubleshooting.md +65 -0
- package/presets/minimal/.claude/skills/einja/task-qa/reference/usage-patterns.md +52 -0
- package/presets/minimal/.claude/skills/einja/task-qa/templates/qa-test-template.md +128 -0
- package/presets/minimal/preset.yaml +111 -0
- package/presets/minimal/symlinks.json +45 -0
- package/scaffolds/.mcp.json +45 -0
- package/scaffolds/CLAUDE.md.template +318 -0
- package/scaffolds/steering/README.md +170 -0
- package/scaffolds/steering/acceptance-criteria-and-qa-guide.md +415 -0
- package/scaffolds/steering/architecture.md +481 -0
- package/scaffolds/steering/branch-strategy.md +362 -0
- package/scaffolds/steering/commit-rules.md +217 -0
- package/scaffolds/steering/db-schema-design.md +609 -0
- package/scaffolds/steering/development/api-development.md +783 -0
- package/scaffolds/steering/development/backend-architecture.md +731 -0
- package/scaffolds/steering/development/frontend-development.md +1537 -0
- package/scaffolds/steering/development/review-guidelines.md +365 -0
- package/scaffolds/steering/development/testing-strategy.md +819 -0
- package/scaffolds/steering/development-workflow.md +429 -0
- package/scaffolds/steering/infrastructure/deployment.md +277 -0
- package/scaffolds/steering/infrastructure/environment-variables.md +298 -0
- package/scaffolds/steering/product.md +540 -0
- package/scaffolds/steering/task-management.md +367 -0
- package/templates/README.md +159 -0
- package/templates/design-simple.md.template +172 -0
- package/templates/design.md.template +327 -0
- package/templates/qa-test.md.template +125 -0
- package/templates/requirements.md.template +254 -0
|
@@ -0,0 +1,819 @@
|
|
|
1
|
+
# テスト戦略
|
|
2
|
+
|
|
3
|
+
## 概要
|
|
4
|
+
|
|
5
|
+
このドキュメントでは、ユニットテスト、統合テスト、E2Eテスト(Playwrightコード)の戦略と実装ガイドラインを説明します。
|
|
6
|
+
|
|
7
|
+
テストカバレッジ目標を達成し、高品質なコードを維持するためのベストプラクティスを提供します。
|
|
8
|
+
|
|
9
|
+
> **Note**: このドキュメントは開発者向けの自動テスト戦略を扱います。QAエージェント(task-qa)が実行するBrowserテスト(Playwright MCP)については、`docs/einja/steering/terminology.md` および `docs/einja/steering/acceptance-criteria-and-qa-guide.md` を参照してください。
|
|
10
|
+
|
|
11
|
+
---
|
|
12
|
+
|
|
13
|
+
## 目次
|
|
14
|
+
|
|
15
|
+
1. [テスト戦略の全体像](#1-テスト戦略の全体像)
|
|
16
|
+
2. [ユニットテスト](#2-ユニットテスト)
|
|
17
|
+
3. [統合テスト](#3-統合テスト)
|
|
18
|
+
4. [E2Eテスト](#4-e2eテスト)
|
|
19
|
+
5. [テストカバレッジ](#5-テストカバレッジ)
|
|
20
|
+
6. [テストツール](#6-テストツール)
|
|
21
|
+
7. [テストの実行](#7-テストの実行)
|
|
22
|
+
8. [モックとスタブ](#8-モックとスタブ)
|
|
23
|
+
|
|
24
|
+
---
|
|
25
|
+
|
|
26
|
+
## 1. テスト戦略の全体像
|
|
27
|
+
|
|
28
|
+
### テストピラミッド
|
|
29
|
+
|
|
30
|
+
```mermaid
|
|
31
|
+
graph TB
|
|
32
|
+
subgraph "テストピラミッド"
|
|
33
|
+
E2E[E2Eテスト<br/>完全なユーザーフロー<br/>少ない]
|
|
34
|
+
Integration[統合テスト<br/>パッケージ間連携<br/>中程度]
|
|
35
|
+
Unit[ユニットテスト<br/>個別の関数・クラス<br/>多い]
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
E2E --> Integration
|
|
39
|
+
Integration --> Unit
|
|
40
|
+
|
|
41
|
+
style E2E fill:#ffebee
|
|
42
|
+
style Integration fill:#fff3e0
|
|
43
|
+
style Unit fill:#e8f5e9
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
### テスト種別と目的
|
|
47
|
+
|
|
48
|
+
| テスト種別 | 目的 | 対象 | 実行頻度 |
|
|
49
|
+
|-----------|------|------|---------|
|
|
50
|
+
| ユニットテスト | 個別の関数・クラスの動作確認 | Repository, UseCase, Entity, Validator | 毎コミット |
|
|
51
|
+
| 統合テスト | パッケージ間の連携確認 | @repo/server-core ⇔ apps/* | 毎PR |
|
|
52
|
+
| E2Eテスト | 完全なユーザーフローの確認 | Web, Admin, Cron Worker | デプロイ前 |
|
|
53
|
+
|
|
54
|
+
### TDDワークフロー(Red-Green-Refactor)
|
|
55
|
+
|
|
56
|
+
TDD採用が推奨される場合(ビジネスロジック、データ変換、外部API連携、金銭・認証処理など)、以下のサイクルを実施:
|
|
57
|
+
|
|
58
|
+
1. **Red**: 失敗するテストを書く(Given-When-Then形式で記述)
|
|
59
|
+
- 期待する振る舞いをテストとして記述
|
|
60
|
+
- テストを実行し、失敗することを確認
|
|
61
|
+
|
|
62
|
+
2. **Green**: テストを通す最小限の実装
|
|
63
|
+
- テストが通る最小限のコードを書く
|
|
64
|
+
- この段階では美しさより動作を優先
|
|
65
|
+
|
|
66
|
+
3. **Refactor**: コードを改善
|
|
67
|
+
- テストが通ったまま、コードを改善
|
|
68
|
+
- 重複削除、命名改善、構造改善
|
|
69
|
+
|
|
70
|
+
**補足**: Given-When-ThenとRed-Green-Refactorは**補完関係**:
|
|
71
|
+
- **Given-When-Then**: テストの記述形式(どう書くか)
|
|
72
|
+
- **Red-Green-Refactor**: TDDの実施サイクル(どう進めるか)
|
|
73
|
+
- 組み合わせ: 「Red-Green-RefactorサイクルでGiven-When-Then形式のテストを書く」
|
|
74
|
+
|
|
75
|
+
---
|
|
76
|
+
|
|
77
|
+
## 2. ユニットテスト
|
|
78
|
+
|
|
79
|
+
### 対象コンポーネント
|
|
80
|
+
|
|
81
|
+
1. **Repository**(Infrastructure層)
|
|
82
|
+
2. **UseCase**(Application層)
|
|
83
|
+
3. **Entity**(Domain層)
|
|
84
|
+
4. **Validator**(Domain層)
|
|
85
|
+
5. **Mapper**(Infrastructure層)
|
|
86
|
+
|
|
87
|
+
### 正常系テストケース
|
|
88
|
+
|
|
89
|
+
#### Repositoryのテスト
|
|
90
|
+
|
|
91
|
+
**Given-When-Then形式**:
|
|
92
|
+
|
|
93
|
+
```typescript
|
|
94
|
+
// packages/server-core/src/infrastructure/database/repositories/__tests__/UserRepository.test.ts
|
|
95
|
+
import { userRepository } from '../UserRepository'
|
|
96
|
+
import { prisma } from '../../client'
|
|
97
|
+
|
|
98
|
+
describe('UserRepository', () => {
|
|
99
|
+
beforeEach(async () => {
|
|
100
|
+
// テストデータのクリーンアップ
|
|
101
|
+
await prisma.user.deleteMany()
|
|
102
|
+
})
|
|
103
|
+
|
|
104
|
+
describe('create', () => {
|
|
105
|
+
it('有効なユーザーデータを渡すと、データベースにユーザーが作成され、ドメインエンティティが返る', async () => {
|
|
106
|
+
// Given: 有効なユーザーデータ
|
|
107
|
+
const userData = {
|
|
108
|
+
email: 'test@example.com',
|
|
109
|
+
name: 'Test User',
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
// When: createメソッドを呼び出す
|
|
113
|
+
const result = await userRepository.create(userData)
|
|
114
|
+
|
|
115
|
+
// Then: 成功結果が返り、ユーザーが作成される
|
|
116
|
+
expect(result.isSuccess).toBe(true)
|
|
117
|
+
if (result.isSuccess) {
|
|
118
|
+
expect(result.value.email).toBe('test@example.com')
|
|
119
|
+
expect(result.value.name).toBe('Test User')
|
|
120
|
+
expect(result.value.id).toBeDefined()
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
// データベースに保存されているか確認
|
|
124
|
+
const savedUser = await prisma.user.findUnique({
|
|
125
|
+
where: { email: 'test@example.com' }
|
|
126
|
+
})
|
|
127
|
+
expect(savedUser).toBeDefined()
|
|
128
|
+
})
|
|
129
|
+
})
|
|
130
|
+
|
|
131
|
+
describe('find', () => {
|
|
132
|
+
it('存在するユーザーのメールアドレスで検索すると、ユーザーが返る', async () => {
|
|
133
|
+
// Given: ユーザーが存在する
|
|
134
|
+
await prisma.user.create({
|
|
135
|
+
data: {
|
|
136
|
+
email: 'existing@example.com',
|
|
137
|
+
name: 'Existing User',
|
|
138
|
+
}
|
|
139
|
+
})
|
|
140
|
+
|
|
141
|
+
// When: メールアドレスで検索
|
|
142
|
+
const result = await userRepository.find({ email: 'existing@example.com' })
|
|
143
|
+
|
|
144
|
+
// Then: ユーザーが見つかる
|
|
145
|
+
expect(result.isSuccess).toBe(true)
|
|
146
|
+
if (result.isSuccess) {
|
|
147
|
+
expect(result.value).not.toBeNull()
|
|
148
|
+
expect(result.value?.email).toBe('existing@example.com')
|
|
149
|
+
}
|
|
150
|
+
})
|
|
151
|
+
})
|
|
152
|
+
})
|
|
153
|
+
```
|
|
154
|
+
|
|
155
|
+
#### バリデーションのテスト
|
|
156
|
+
|
|
157
|
+
```typescript
|
|
158
|
+
// packages/server-core/src/domain/validators/__tests__/user.test.ts
|
|
159
|
+
import { createUserSchema, updateUserSchema } from '../user'
|
|
160
|
+
|
|
161
|
+
describe('User Validators', () => {
|
|
162
|
+
describe('createUserSchema', () => {
|
|
163
|
+
it('有効なメールアドレスと名前が渡されると、バリデーションが成功する', () => {
|
|
164
|
+
// Given: 有効なユーザーデータ
|
|
165
|
+
const validData = {
|
|
166
|
+
email: 'test@example.com',
|
|
167
|
+
name: 'Test User',
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
// When: バリデーションを実行
|
|
171
|
+
const result = createUserSchema.safeParse(validData)
|
|
172
|
+
|
|
173
|
+
// Then: バリデーション成功
|
|
174
|
+
expect(result.success).toBe(true)
|
|
175
|
+
if (result.success) {
|
|
176
|
+
expect(result.data.email).toBe('test@example.com')
|
|
177
|
+
expect(result.data.name).toBe('Test User')
|
|
178
|
+
}
|
|
179
|
+
})
|
|
180
|
+
|
|
181
|
+
it('不正な形式のメールアドレスが渡されると、バリデーションが失敗する', () => {
|
|
182
|
+
// Given: 不正なメールアドレス
|
|
183
|
+
const invalidData = {
|
|
184
|
+
email: 'invalid-email',
|
|
185
|
+
name: 'Test User',
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
// When: バリデーションを実行
|
|
189
|
+
const result = createUserSchema.safeParse(invalidData)
|
|
190
|
+
|
|
191
|
+
// Then: バリデーション失敗
|
|
192
|
+
expect(result.success).toBe(false)
|
|
193
|
+
if (!result.success) {
|
|
194
|
+
expect(result.error.issues[0].path).toContain('email')
|
|
195
|
+
}
|
|
196
|
+
})
|
|
197
|
+
})
|
|
198
|
+
})
|
|
199
|
+
```
|
|
200
|
+
|
|
201
|
+
#### エンティティのテスト
|
|
202
|
+
|
|
203
|
+
```typescript
|
|
204
|
+
// packages/server-core/src/domain/entities/__tests__/Post.test.ts
|
|
205
|
+
import { Post } from '../Post'
|
|
206
|
+
|
|
207
|
+
describe('Post Entity', () => {
|
|
208
|
+
describe('publish', () => {
|
|
209
|
+
it('下書き状態の投稿をpublishすると、公開状態になり、公開日時が設定される', () => {
|
|
210
|
+
// Given: 下書き状態の投稿
|
|
211
|
+
const post = new Post({
|
|
212
|
+
id: '1',
|
|
213
|
+
userId: 'user1',
|
|
214
|
+
title: 'Test Post',
|
|
215
|
+
content: 'Content',
|
|
216
|
+
status: 'draft',
|
|
217
|
+
publishedAt: null,
|
|
218
|
+
createdAt: new Date(),
|
|
219
|
+
updatedAt: new Date(),
|
|
220
|
+
})
|
|
221
|
+
|
|
222
|
+
// When: publish メソッドを呼び出す
|
|
223
|
+
post.publish()
|
|
224
|
+
|
|
225
|
+
// Then: 公開状態になる
|
|
226
|
+
expect(post.status).toBe('published')
|
|
227
|
+
expect(post.publishedAt).toBeInstanceOf(Date)
|
|
228
|
+
expect(post.isPublished()).toBe(true)
|
|
229
|
+
})
|
|
230
|
+
})
|
|
231
|
+
|
|
232
|
+
describe('isOwnedBy', () => {
|
|
233
|
+
it('投稿の所有者IDと一致するユーザーIDを渡すと、trueが返る', () => {
|
|
234
|
+
// Given: ユーザー'user1'が作成した投稿
|
|
235
|
+
const post = new Post({
|
|
236
|
+
id: '1',
|
|
237
|
+
userId: 'user1',
|
|
238
|
+
title: 'Test Post',
|
|
239
|
+
content: 'Content',
|
|
240
|
+
status: 'draft',
|
|
241
|
+
publishedAt: null,
|
|
242
|
+
createdAt: new Date(),
|
|
243
|
+
updatedAt: new Date(),
|
|
244
|
+
})
|
|
245
|
+
|
|
246
|
+
// When: 所有者チェック
|
|
247
|
+
const isOwned = post.isOwnedBy('user1')
|
|
248
|
+
|
|
249
|
+
// Then: trueが返る
|
|
250
|
+
expect(isOwned).toBe(true)
|
|
251
|
+
})
|
|
252
|
+
|
|
253
|
+
it('投稿の所有者IDと異なるユーザーIDを渡すと、falseが返る', () => {
|
|
254
|
+
// Given: ユーザー'user1'が作成した投稿
|
|
255
|
+
const post = new Post({
|
|
256
|
+
id: '1',
|
|
257
|
+
userId: 'user1',
|
|
258
|
+
title: 'Test Post',
|
|
259
|
+
content: 'Content',
|
|
260
|
+
status: 'draft',
|
|
261
|
+
publishedAt: null,
|
|
262
|
+
createdAt: new Date(),
|
|
263
|
+
updatedAt: new Date(),
|
|
264
|
+
})
|
|
265
|
+
|
|
266
|
+
// When: 別のユーザーIDで所有者チェック
|
|
267
|
+
const isOwned = post.isOwnedBy('user2')
|
|
268
|
+
|
|
269
|
+
// Then: falseが返る
|
|
270
|
+
expect(isOwned).toBe(false)
|
|
271
|
+
})
|
|
272
|
+
})
|
|
273
|
+
})
|
|
274
|
+
```
|
|
275
|
+
|
|
276
|
+
### 異常系テストケース
|
|
277
|
+
|
|
278
|
+
#### 無効な入力値のテスト
|
|
279
|
+
|
|
280
|
+
```typescript
|
|
281
|
+
describe('createUserSchema - 異常系', () => {
|
|
282
|
+
it('メールアドレスが空文字の場合、バリデーションエラーが発生する', () => {
|
|
283
|
+
// Given: 空のメールアドレス
|
|
284
|
+
const invalidData = {
|
|
285
|
+
email: '',
|
|
286
|
+
name: 'Test User',
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
// When: バリデーション実行
|
|
290
|
+
const result = createUserSchema.safeParse(invalidData)
|
|
291
|
+
|
|
292
|
+
// Then: エラーが返る
|
|
293
|
+
expect(result.success).toBe(false)
|
|
294
|
+
if (!result.success) {
|
|
295
|
+
expect(result.error.issues[0].message).toContain('email')
|
|
296
|
+
}
|
|
297
|
+
})
|
|
298
|
+
|
|
299
|
+
it('名前が100文字を超える場合、バリデーションエラーが発生する', () => {
|
|
300
|
+
// Given: 100文字を超える名前
|
|
301
|
+
const invalidData = {
|
|
302
|
+
email: 'test@example.com',
|
|
303
|
+
name: 'a'.repeat(101),
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
// When: バリデーション実行
|
|
307
|
+
const result = createUserSchema.safeParse(invalidData)
|
|
308
|
+
|
|
309
|
+
// Then: エラーが返る
|
|
310
|
+
expect(result.success).toBe(false)
|
|
311
|
+
})
|
|
312
|
+
})
|
|
313
|
+
```
|
|
314
|
+
|
|
315
|
+
#### データベースエラーのテスト
|
|
316
|
+
|
|
317
|
+
```typescript
|
|
318
|
+
describe('UserRepository - データベースエラー', () => {
|
|
319
|
+
it('重複したメールアドレスでユーザーを作成すると、エラーが返る', async () => {
|
|
320
|
+
// Given: 既に存在するメールアドレス
|
|
321
|
+
await prisma.user.create({
|
|
322
|
+
data: {
|
|
323
|
+
email: 'duplicate@example.com',
|
|
324
|
+
name: 'Existing User',
|
|
325
|
+
}
|
|
326
|
+
})
|
|
327
|
+
|
|
328
|
+
// When: 同じメールアドレスでユーザー作成
|
|
329
|
+
const result = await userRepository.create({
|
|
330
|
+
email: 'duplicate@example.com',
|
|
331
|
+
name: 'New User',
|
|
332
|
+
})
|
|
333
|
+
|
|
334
|
+
// Then: エラーが返る
|
|
335
|
+
expect(result.isSuccess).toBe(false)
|
|
336
|
+
if (!result.isSuccess) {
|
|
337
|
+
expect(result.error.code).toBe('CONFLICT')
|
|
338
|
+
}
|
|
339
|
+
})
|
|
340
|
+
})
|
|
341
|
+
```
|
|
342
|
+
|
|
343
|
+
---
|
|
344
|
+
|
|
345
|
+
## 3. 統合テスト
|
|
346
|
+
|
|
347
|
+
### パッケージ間の連携テスト
|
|
348
|
+
|
|
349
|
+
#### webから@repo/server-coreの使用
|
|
350
|
+
|
|
351
|
+
```typescript
|
|
352
|
+
// apps/web/__tests__/integration/post-api.test.ts
|
|
353
|
+
import { apiClient } from '@/lib/api-client'
|
|
354
|
+
import { prisma } from '@repo/server-core/infrastructure/database/client'
|
|
355
|
+
|
|
356
|
+
describe('Post API統合テスト', () => {
|
|
357
|
+
beforeEach(async () => {
|
|
358
|
+
// テストデータのクリーンアップ
|
|
359
|
+
await prisma.post.deleteMany()
|
|
360
|
+
await prisma.user.deleteMany()
|
|
361
|
+
})
|
|
362
|
+
|
|
363
|
+
it('投稿一覧APIを呼び出すと、@repo/server-coreのRepositoryが使用され、データが取得できる', async () => {
|
|
364
|
+
// Given: ユーザーと投稿が存在する
|
|
365
|
+
const user = await prisma.user.create({
|
|
366
|
+
data: {
|
|
367
|
+
email: 'test@example.com',
|
|
368
|
+
name: 'Test User',
|
|
369
|
+
}
|
|
370
|
+
})
|
|
371
|
+
|
|
372
|
+
await prisma.post.create({
|
|
373
|
+
data: {
|
|
374
|
+
userId: user.id,
|
|
375
|
+
title: 'Test Post',
|
|
376
|
+
content: 'Content',
|
|
377
|
+
status: 'published',
|
|
378
|
+
}
|
|
379
|
+
})
|
|
380
|
+
|
|
381
|
+
// When: API呼び出し
|
|
382
|
+
const response = await apiClient.posts.$get({
|
|
383
|
+
query: { page: '1', limit: '10' }
|
|
384
|
+
})
|
|
385
|
+
|
|
386
|
+
// Then: データが取得できる
|
|
387
|
+
expect(response.ok).toBe(true)
|
|
388
|
+
const data = await response.json()
|
|
389
|
+
expect(data.posts).toHaveLength(1)
|
|
390
|
+
expect(data.posts[0].title).toBe('Test Post')
|
|
391
|
+
})
|
|
392
|
+
|
|
393
|
+
it('Repositoryパターンが正しく動作し、型定義が適切に共有される', async () => {
|
|
394
|
+
// Given: ユーザーが存在する
|
|
395
|
+
const user = await prisma.user.create({
|
|
396
|
+
data: {
|
|
397
|
+
email: 'test@example.com',
|
|
398
|
+
name: 'Test User',
|
|
399
|
+
}
|
|
400
|
+
})
|
|
401
|
+
|
|
402
|
+
// When: 投稿作成APIを呼び出し
|
|
403
|
+
const response = await apiClient.posts.$post({
|
|
404
|
+
json: {
|
|
405
|
+
title: 'New Post',
|
|
406
|
+
content: 'Content',
|
|
407
|
+
status: 'draft',
|
|
408
|
+
}
|
|
409
|
+
})
|
|
410
|
+
|
|
411
|
+
// Then: 作成成功
|
|
412
|
+
expect(response.ok).toBe(true)
|
|
413
|
+
const data = await response.json()
|
|
414
|
+
expect(data.post.title).toBe('New Post')
|
|
415
|
+
expect(data.post.status).toBe('draft')
|
|
416
|
+
|
|
417
|
+
// 型推論が正しく機能しているか確認(TypeScriptコンパイルエラーが出ないこと)
|
|
418
|
+
const title: string = data.post.title
|
|
419
|
+
const status: 'draft' | 'published' | 'archived' = data.post.status
|
|
420
|
+
expect(title).toBeDefined()
|
|
421
|
+
expect(status).toBeDefined()
|
|
422
|
+
})
|
|
423
|
+
})
|
|
424
|
+
```
|
|
425
|
+
|
|
426
|
+
### ビルドパイプラインのテスト
|
|
427
|
+
|
|
428
|
+
```typescript
|
|
429
|
+
// __tests__/integration/build.test.ts
|
|
430
|
+
import { execSync } from 'child_process'
|
|
431
|
+
|
|
432
|
+
describe('ビルドパイプライン統合テスト', () => {
|
|
433
|
+
it('turbo run buildで全アプリケーションがビルド成功する', () => {
|
|
434
|
+
// When: ビルド実行
|
|
435
|
+
expect(() => {
|
|
436
|
+
execSync('pnpm turbo run build', { stdio: 'inherit' })
|
|
437
|
+
}).not.toThrow()
|
|
438
|
+
})
|
|
439
|
+
|
|
440
|
+
it('依存関係の順序が正しく解決される', () => {
|
|
441
|
+
// Given: @repo/server-coreがwebより先にビルドされる必要がある
|
|
442
|
+
// When: ビルド実行
|
|
443
|
+
const output = execSync('pnpm turbo run build --dry-run', { encoding: 'utf-8' })
|
|
444
|
+
|
|
445
|
+
// Then: 依存関係の順序が正しい
|
|
446
|
+
expect(output).toContain('@repo/server-core')
|
|
447
|
+
expect(output.indexOf('@repo/server-core')).toBeLessThan(output.indexOf('web'))
|
|
448
|
+
})
|
|
449
|
+
})
|
|
450
|
+
```
|
|
451
|
+
|
|
452
|
+
---
|
|
453
|
+
|
|
454
|
+
## 4. E2Eテスト(Playwrightコード)
|
|
455
|
+
|
|
456
|
+
> **用語の明確化**: このセクションで説明する「E2Eテスト」は、`pnpm test:e2e` で実行されるPlaywrightコードベースの自動テストを指します。QAエージェントがPlaywright MCPを使用して実行する「Browserテスト」とは異なります。詳細は `docs/einja/steering/terminology.md` を参照してください。
|
|
457
|
+
|
|
458
|
+
### Playwright設定
|
|
459
|
+
|
|
460
|
+
**playwright.config.ts**:
|
|
461
|
+
|
|
462
|
+
```typescript
|
|
463
|
+
import { defineConfig, devices } from '@playwright/test'
|
|
464
|
+
|
|
465
|
+
export default defineConfig({
|
|
466
|
+
testDir: './e2e',
|
|
467
|
+
fullyParallel: true,
|
|
468
|
+
forbidOnly: !!process.env.CI,
|
|
469
|
+
retries: process.env.CI ? 2 : 0,
|
|
470
|
+
workers: process.env.CI ? 1 : undefined,
|
|
471
|
+
reporter: 'html',
|
|
472
|
+
use: {
|
|
473
|
+
baseURL: 'http://localhost:3000',
|
|
474
|
+
trace: 'on-first-retry',
|
|
475
|
+
},
|
|
476
|
+
|
|
477
|
+
projects: [
|
|
478
|
+
{
|
|
479
|
+
name: 'chromium',
|
|
480
|
+
use: { ...devices['Desktop Chrome'] },
|
|
481
|
+
},
|
|
482
|
+
{
|
|
483
|
+
name: 'firefox',
|
|
484
|
+
use: { ...devices['Desktop Firefox'] },
|
|
485
|
+
},
|
|
486
|
+
],
|
|
487
|
+
|
|
488
|
+
webServer: {
|
|
489
|
+
command: 'pnpm dev',
|
|
490
|
+
url: 'http://localhost:3000',
|
|
491
|
+
reuseExistingServer: !process.env.CI,
|
|
492
|
+
},
|
|
493
|
+
})
|
|
494
|
+
```
|
|
495
|
+
|
|
496
|
+
### 完全なユーザーフロー
|
|
497
|
+
|
|
498
|
+
```typescript
|
|
499
|
+
// e2e/post-creation.spec.ts
|
|
500
|
+
import { test, expect } from '@playwright/test'
|
|
501
|
+
|
|
502
|
+
test.describe('投稿作成フロー', () => {
|
|
503
|
+
test('ユーザーがwebにアクセスし、ログインして投稿を作成すると、データベースに保存され、一覧ページに表示される', async ({ page }) => {
|
|
504
|
+
// Given: ユーザーがログインページにアクセス
|
|
505
|
+
await page.goto('/auth/login')
|
|
506
|
+
|
|
507
|
+
// When: ログイン
|
|
508
|
+
await page.fill('[data-testid="email-input"]', 'test@example.com')
|
|
509
|
+
await page.fill('[data-testid="password-input"]', 'password123')
|
|
510
|
+
await page.click('[data-testid="login-button"]')
|
|
511
|
+
|
|
512
|
+
// Then: ダッシュボードにリダイレクト
|
|
513
|
+
await expect(page).toHaveURL('/dashboard')
|
|
514
|
+
|
|
515
|
+
// When: 投稿作成ページに移動
|
|
516
|
+
await page.click('[data-testid="create-post-button"]')
|
|
517
|
+
await expect(page).toHaveURL('/posts/new')
|
|
518
|
+
|
|
519
|
+
// When: 投稿フォームを入力
|
|
520
|
+
await page.fill('[data-testid="title-input"]', 'E2E Test Post')
|
|
521
|
+
await page.fill('[data-testid="content-input"]', 'This is E2E test content')
|
|
522
|
+
await page.selectOption('[data-testid="status-select"]', 'published')
|
|
523
|
+
await page.click('[data-testid="submit-button"]')
|
|
524
|
+
|
|
525
|
+
// Then: 投稿一覧ページにリダイレクト
|
|
526
|
+
await expect(page).toHaveURL('/posts')
|
|
527
|
+
|
|
528
|
+
// Then: 作成した投稿が一覧に表示される
|
|
529
|
+
await expect(page.locator('text=E2E Test Post')).toBeVisible()
|
|
530
|
+
})
|
|
531
|
+
})
|
|
532
|
+
```
|
|
533
|
+
|
|
534
|
+
### 管理画面フロー
|
|
535
|
+
|
|
536
|
+
```typescript
|
|
537
|
+
// e2e/admin-user-management.spec.ts
|
|
538
|
+
import { test, expect } from '@playwright/test'
|
|
539
|
+
|
|
540
|
+
test.describe('管理画面ユーザー管理フロー', () => {
|
|
541
|
+
test('管理者がadminにログインし、ユーザー一覧を表示し、ユーザーを削除すると、データベースから削除され、webに反映される', async ({ page, context }) => {
|
|
542
|
+
// Given: 管理者がログイン
|
|
543
|
+
await page.goto('/admin/login')
|
|
544
|
+
await page.fill('[data-testid="email-input"]', 'admin@example.com')
|
|
545
|
+
await page.fill('[data-testid="password-input"]', 'admin123')
|
|
546
|
+
await page.click('[data-testid="login-button"]')
|
|
547
|
+
|
|
548
|
+
// Then: 管理画面ダッシュボードにリダイレクト
|
|
549
|
+
await expect(page).toHaveURL('/admin/dashboard')
|
|
550
|
+
|
|
551
|
+
// When: ユーザー一覧ページに移動
|
|
552
|
+
await page.click('[data-testid="users-link"]')
|
|
553
|
+
await expect(page).toHaveURL('/admin/users')
|
|
554
|
+
|
|
555
|
+
// Then: ユーザー一覧が表示される
|
|
556
|
+
await expect(page.locator('table[data-testid="users-table"]')).toBeVisible()
|
|
557
|
+
|
|
558
|
+
// When: ユーザーを削除
|
|
559
|
+
await page.click('[data-testid="delete-user-button-1"]')
|
|
560
|
+
await page.click('[data-testid="confirm-delete-button"]')
|
|
561
|
+
|
|
562
|
+
// Then: ユーザーが一覧から削除される
|
|
563
|
+
await expect(page.locator('[data-testid="user-row-1"]')).not.toBeVisible()
|
|
564
|
+
|
|
565
|
+
// When: webアプリで確認(別タブ)
|
|
566
|
+
const webPage = await context.newPage()
|
|
567
|
+
await webPage.goto('http://localhost:3000/users')
|
|
568
|
+
|
|
569
|
+
// Then: 削除されたユーザーが表示されない
|
|
570
|
+
await expect(webPage.locator('[data-testid="user-1"]')).not.toBeVisible()
|
|
571
|
+
})
|
|
572
|
+
})
|
|
573
|
+
```
|
|
574
|
+
|
|
575
|
+
### Cronジョブフロー
|
|
576
|
+
|
|
577
|
+
```typescript
|
|
578
|
+
// e2e/cron-cleanup.spec.ts
|
|
579
|
+
import { test, expect } from '@playwright/test'
|
|
580
|
+
import { execSync } from 'child_process'
|
|
581
|
+
import { prisma } from '@repo/server-core/infrastructure/database/client'
|
|
582
|
+
|
|
583
|
+
test.describe('Cronジョブフロー', () => {
|
|
584
|
+
test('クリーンアップジョブが実行されると、期限切れセッションが削除される', async () => {
|
|
585
|
+
// Given: 期限切れセッションが存在する
|
|
586
|
+
const expiredSession = await prisma.session.create({
|
|
587
|
+
data: {
|
|
588
|
+
userId: 'user1',
|
|
589
|
+
token: 'expired-token',
|
|
590
|
+
expiresAt: new Date(Date.now() - 1000 * 60 * 60), // 1時間前
|
|
591
|
+
}
|
|
592
|
+
})
|
|
593
|
+
|
|
594
|
+
// When: クリーンアップジョブを実行
|
|
595
|
+
execSync('pnpm job:cleanup', { stdio: 'inherit' })
|
|
596
|
+
|
|
597
|
+
// Then: 期限切れセッションが削除される
|
|
598
|
+
const deletedSession = await prisma.session.findUnique({
|
|
599
|
+
where: { id: expiredSession.id }
|
|
600
|
+
})
|
|
601
|
+
expect(deletedSession).toBeNull()
|
|
602
|
+
})
|
|
603
|
+
})
|
|
604
|
+
```
|
|
605
|
+
|
|
606
|
+
---
|
|
607
|
+
|
|
608
|
+
## 5. テストカバレッジ
|
|
609
|
+
|
|
610
|
+
### カバレッジ目標
|
|
611
|
+
|
|
612
|
+
| 対象 | カバレッジ目標 | 優先度 |
|
|
613
|
+
|------|--------------|--------|
|
|
614
|
+
| 共有パッケージ (@repo/server-core) | 80%以上 | 最優先 |
|
|
615
|
+
| アプリケーション (web, admin, cron-worker) | 70%以上 | 高 |
|
|
616
|
+
| 統合テスト | 主要フロー100% | 高 |
|
|
617
|
+
| E2Eテスト | クリティカルパス100% | 中 |
|
|
618
|
+
|
|
619
|
+
### カバレッジレポートの生成
|
|
620
|
+
|
|
621
|
+
```bash
|
|
622
|
+
# カバレッジレポート生成
|
|
623
|
+
pnpm test:coverage
|
|
624
|
+
|
|
625
|
+
# カバレッジレポートの確認
|
|
626
|
+
open coverage/index.html
|
|
627
|
+
```
|
|
628
|
+
|
|
629
|
+
### カバレッジ確認コマンド
|
|
630
|
+
|
|
631
|
+
```json
|
|
632
|
+
{
|
|
633
|
+
"scripts": {
|
|
634
|
+
"test": "vitest",
|
|
635
|
+
"test:coverage": "vitest run --coverage",
|
|
636
|
+
"test:watch": "vitest watch"
|
|
637
|
+
}
|
|
638
|
+
}
|
|
639
|
+
```
|
|
640
|
+
|
|
641
|
+
---
|
|
642
|
+
|
|
643
|
+
## 6. テストツール
|
|
644
|
+
|
|
645
|
+
### テストフレームワーク
|
|
646
|
+
|
|
647
|
+
| ツール | 用途 | 対象 |
|
|
648
|
+
|--------|------|------|
|
|
649
|
+
| Vitest | ユニットテスト、統合テスト | @repo/server-core, apps/* |
|
|
650
|
+
| Playwright (コード) | E2Eテスト(`pnpm test:e2e`で実行) | web, admin |
|
|
651
|
+
| Playwright MCP | Browserテスト(task-qaが実行) | 画面フロー確認 |
|
|
652
|
+
| Testing Library | Reactコンポーネントテスト | web, admin |
|
|
653
|
+
|
|
654
|
+
### Vitest設定
|
|
655
|
+
|
|
656
|
+
**vitest.config.ts**:
|
|
657
|
+
|
|
658
|
+
```typescript
|
|
659
|
+
import { defineConfig } from 'vitest/config'
|
|
660
|
+
|
|
661
|
+
export default defineConfig({
|
|
662
|
+
test: {
|
|
663
|
+
globals: true,
|
|
664
|
+
environment: 'node',
|
|
665
|
+
setupFiles: ['./vitest.setup.ts'],
|
|
666
|
+
coverage: {
|
|
667
|
+
provider: 'v8',
|
|
668
|
+
reporter: ['text', 'html', 'json'],
|
|
669
|
+
exclude: [
|
|
670
|
+
'node_modules/',
|
|
671
|
+
'**/*.test.ts',
|
|
672
|
+
'**/*.spec.ts',
|
|
673
|
+
'**/dist/**',
|
|
674
|
+
'**/.next/**',
|
|
675
|
+
],
|
|
676
|
+
},
|
|
677
|
+
},
|
|
678
|
+
})
|
|
679
|
+
```
|
|
680
|
+
|
|
681
|
+
**vitest.setup.ts**:
|
|
682
|
+
|
|
683
|
+
```typescript
|
|
684
|
+
import { beforeEach } from 'vitest'
|
|
685
|
+
import { prisma } from '@repo/server-core/infrastructure/database/client'
|
|
686
|
+
|
|
687
|
+
// 各テスト前にデータベースをクリーンアップ
|
|
688
|
+
beforeEach(async () => {
|
|
689
|
+
await prisma.post.deleteMany()
|
|
690
|
+
await prisma.session.deleteMany()
|
|
691
|
+
await prisma.account.deleteMany()
|
|
692
|
+
await prisma.user.deleteMany()
|
|
693
|
+
})
|
|
694
|
+
```
|
|
695
|
+
|
|
696
|
+
---
|
|
697
|
+
|
|
698
|
+
## 7. テストの実行
|
|
699
|
+
|
|
700
|
+
### ローカルでのテスト実行
|
|
701
|
+
|
|
702
|
+
```bash
|
|
703
|
+
# すべてのユニットテストを実行
|
|
704
|
+
pnpm test
|
|
705
|
+
|
|
706
|
+
# 特定のファイルのみテスト
|
|
707
|
+
pnpm test UserRepository.test.ts
|
|
708
|
+
|
|
709
|
+
# ウォッチモード
|
|
710
|
+
pnpm test:watch
|
|
711
|
+
|
|
712
|
+
# カバレッジ付きでテスト
|
|
713
|
+
pnpm test:coverage
|
|
714
|
+
|
|
715
|
+
# E2Eテスト実行
|
|
716
|
+
pnpm test:e2e
|
|
717
|
+
|
|
718
|
+
# E2Eテスト(UI付き)
|
|
719
|
+
pnpm test:e2e:ui
|
|
720
|
+
```
|
|
721
|
+
|
|
722
|
+
### CI/CDでのテスト実行
|
|
723
|
+
|
|
724
|
+
```yaml
|
|
725
|
+
# .github/workflows/ci.yml
|
|
726
|
+
- name: Run tests
|
|
727
|
+
run: pnpm turbo run test
|
|
728
|
+
env:
|
|
729
|
+
DATABASE_URL: ${{ secrets.TEST_DATABASE_URL }}
|
|
730
|
+
|
|
731
|
+
- name: Run E2E tests
|
|
732
|
+
run: pnpm test:e2e
|
|
733
|
+
env:
|
|
734
|
+
DATABASE_URL: ${{ secrets.TEST_DATABASE_URL }}
|
|
735
|
+
|
|
736
|
+
- name: Upload coverage
|
|
737
|
+
uses: codecov/codecov-action@v3
|
|
738
|
+
with:
|
|
739
|
+
files: ./coverage/coverage-final.json
|
|
740
|
+
```
|
|
741
|
+
|
|
742
|
+
---
|
|
743
|
+
|
|
744
|
+
## 8. モックとスタブ
|
|
745
|
+
|
|
746
|
+
### Repositoryのモック
|
|
747
|
+
|
|
748
|
+
```typescript
|
|
749
|
+
// __tests__/mocks/userRepository.mock.ts
|
|
750
|
+
import type { IUserRepository } from '@repo/server-core/domain/repository-interfaces/IUserRepository'
|
|
751
|
+
import type { User } from '@repo/server-core/domain/entities/User'
|
|
752
|
+
|
|
753
|
+
export const createMockUserRepository = (): IUserRepository => ({
|
|
754
|
+
find: vi.fn().mockResolvedValue({
|
|
755
|
+
isSuccess: true,
|
|
756
|
+
value: {
|
|
757
|
+
id: '1',
|
|
758
|
+
email: 'test@example.com',
|
|
759
|
+
name: 'Test User',
|
|
760
|
+
createdAt: new Date(),
|
|
761
|
+
updatedAt: new Date(),
|
|
762
|
+
} as User,
|
|
763
|
+
}),
|
|
764
|
+
|
|
765
|
+
create: vi.fn().mockResolvedValue({
|
|
766
|
+
isSuccess: true,
|
|
767
|
+
value: {
|
|
768
|
+
id: '1',
|
|
769
|
+
email: 'test@example.com',
|
|
770
|
+
name: 'Test User',
|
|
771
|
+
createdAt: new Date(),
|
|
772
|
+
updatedAt: new Date(),
|
|
773
|
+
} as User,
|
|
774
|
+
}),
|
|
775
|
+
|
|
776
|
+
// 他のメソッドも同様にモック
|
|
777
|
+
})
|
|
778
|
+
```
|
|
779
|
+
|
|
780
|
+
### UseCaseでのモック使用
|
|
781
|
+
|
|
782
|
+
```typescript
|
|
783
|
+
// __tests__/use-cases/UserUseCase.test.ts
|
|
784
|
+
import { createMockUserRepository } from '../mocks/userRepository.mock'
|
|
785
|
+
import { userUseCase } from '@repo/server-core/application/use-cases/UserUseCase'
|
|
786
|
+
|
|
787
|
+
describe('UserUseCase', () => {
|
|
788
|
+
it('createメソッドが正しく動作する', async () => {
|
|
789
|
+
// Given: モックRepository
|
|
790
|
+
const mockRepo = createMockUserRepository()
|
|
791
|
+
|
|
792
|
+
// When: UseCase実行(依存性注入)
|
|
793
|
+
const result = await userUseCase.create({
|
|
794
|
+
email: 'test@example.com',
|
|
795
|
+
name: 'Test User',
|
|
796
|
+
})
|
|
797
|
+
|
|
798
|
+
// Then: Repository呼び出しを確認
|
|
799
|
+
expect(mockRepo.create).toHaveBeenCalledWith({
|
|
800
|
+
email: 'test@example.com',
|
|
801
|
+
name: 'Test User',
|
|
802
|
+
})
|
|
803
|
+
})
|
|
804
|
+
})
|
|
805
|
+
```
|
|
806
|
+
|
|
807
|
+
---
|
|
808
|
+
|
|
809
|
+
## まとめ
|
|
810
|
+
|
|
811
|
+
このテスト戦略に従うことで、以下を実現できます:
|
|
812
|
+
|
|
813
|
+
1. **高品質**: カバレッジ目標達成により、バグの早期発見
|
|
814
|
+
2. **自動化**: CI/CDパイプラインでの自動テスト実行
|
|
815
|
+
3. **保守性**: モックとスタブによるテストの独立性確保
|
|
816
|
+
4. **信頼性**: E2Eテストによる完全なユーザーフロー検証
|
|
817
|
+
5. **効率性**: Vitestによる高速なテスト実行
|
|
818
|
+
|
|
819
|
+
すべてのテストは、このガイドラインに従って実装してください。
|