| name | test-gen |
| description | Analyzes existing production code, identifies untested paths and edge cases, and generates comprehensive test cases using the project's test infrastructure. Supports unit (Jest), component (RTL), and E2E (Playwright) test generation. Triggered by: "ãã¹ã远å ", "ãã¹ãã±ãŒã¹è¿œå ", "ãã¹ãè£å®", "ãã¹ãçæ", "ã«ãã¬ããžæ¹å", "äžè¶³ãã¹ã", "ãã¹ãã®ã£ãã", "add tests", "generate tests", "test gap", "improve coverage", "missing tests", "write tests for".
|
| invocation | automatic |
| allowed-tools | ["Read","Grep","Glob","Bash","Edit","Write"] |
Test Gen ã¹ãã«
ç®ç
æ¢åã³ãŒãã®ãã¹ãã®ã£ãããåæããäžè¶³ããŠãããã¹ãã±ãŒã¹ãçæã»è¿œå ããã¹ãã«ãserver-action-scaffoldïŒæ°èŠã¢ãžã¥ãŒã«é圢çæïŒãšã¯ç°ãªããæ¢ã«ååšããã³ãŒããžã®ãã¹ãè£å®ã«ç¹åããã
å®è¡æé ïŒãã®é çªãå³å®ããããšïŒ
Step 1ïœã¹ã³ãŒããæ±ºå®ãã
ãŠãŒã¶ãŒã®æç€ºãã察象ãç¹å®ããïŒ
| æç€ºäŸ | ã¹ã³ãŒã | ãã¹ãçš®å¥ |
|---|
| ãstore.ts ã®ãã¹ãã远å ããŠã | åäžã¢ãžã¥ãŒã« | ãŠããã |
| ãã³ã³ããŒãã³ãã®ãã¹ããæžããŠã | src/components/ é
äž | ã³ã³ããŒãã³ã |
| ããã§ãã¯ã¢ãŠããããŒã® E2E ã远å ã | ãŠãŒã¶ãŒãã㌠| E2E |
| ãã«ãã¬ããžãæ¹åããŠã | äœã«ãã¬ããžã¢ãžã¥ãŒã«å
šäœ | ãŠããã |
| ãP0 ãã¹ãã远å ããŠã | QA 芳ç¹ã®æåªå
é
ç® | æ··å |
ã¹ã³ãŒããäžæç¢ºãªå Žåã¯ãŠãŒã¶ãŒã«ç¢ºèªããã
Step 2ïœãã¹ãã€ã³ãã©ãèªã¿èŸŒã
以äžã® 4 ãã¡ã€ã«ãå¿
ãèªãã§ããçæã«çæããïŒäŸå€: prisma/seed/** ã¯å¯Ÿè±¡å€ãseed ãã¹ã㯠src/config å
±éã€ã³ãã©ã䜿ãããinline Prisma mock ãŸãã¯å®DB ãçšããæ¹éãæ¡ãïŒïŒ
Read: src/config/test-fixtures.ts # ãã¡ã¯ããªé¢æ°äžèŠ§ãææ¡
Read: src/config/test-helpers.ts # AuthTestHelpers / AssertionHelpers ãææ¡
Read: src/config/test-scenarios.ts # å¢çå€ã»ç¶æ
é·ç§»ãã¿ãŒã³ãææ¡
Read: src/config/test-config.ts # 宿°ã»ãšããžã±ãŒã¹æååãææ¡
å©çšå¯èœãªãã¡ã¯ããªã»ãã«ããŒã»ã·ããªãªããªã¹ãã¢ãããããã¹ãçææã«æå€§éæŽ»çšããã
Step 3ïœæ¬çªã³ãŒããåæãã
察象ãã¡ã€ã«ãèªã¿èŸŒã¿ã以äžãåæããïŒ
| åæé
ç® | æœåºå
容 |
|---|
| export 颿° | å
šå
¬é颿°ã®ã·ã°ãã㣠|
| åå²ãã¹ | if/else, switch, æ©æãªã¿ãŒã³, try/catch |
| ãšã©ãŒãã¹ | throw, { success: false }, console.error |
| å€éšäŸå | Prisma æäœ, Clerk èªèšŒ, Stripe/PayPal API |
| Decimal æŒç® | .add(), .mul(), .sub(), .toNumber() |
| ãã©ã³ã¶ã¯ã·ã§ã³ | db.$transaction ã®äœ¿çšç®æ |
Step 4ïœæ¢åãã¹ããåæãã®ã£ãããç¹å®ãã
察象ã¢ãžã¥ãŒã«ã® *.test.ts ãèªã¿èŸŒã¿ãã«ããŒæžã¿ã±ãŒã¹ããããã³ã°ããã
ã®ã£ããåé¡ïŒåªå
床é ïŒ:
| åªå
床 | ã«ããŽãª | äŸ |
|---|
| P0 | ã»ãã¥ãªãã£ã»ããŒã¿ç Žæ | èªèšŒãã€ãã¹ãIDORãäºé課éãåšåº«äžæŽå |
| P1 | ããžãã¹ããžãã¯äžæŽå | é
éæèª€èšç®ãã¯ãŒãã³æéç¡èŠã泚æã¹ããŒã¿ã¹äžæ£é·ç§» |
| P2 | ãšããžã±ãŒã¹ã»å¢çå€ | 空é
åãnull/undefinedãé·æååãDecimal 粟床 |
| P3 | ãšã©ãŒãã³ããªã³ã° | DB æ¥ç¶å€±æãå€éš API ã¿ã€ã ã¢ãŠã |
QA 芳ç¹ã®è©³çŽ°ã¯ docs/testing/QA_TEST_PERSPECTIVES.md ãåç
§ããã
IDOR ãã¹ã 3 éå±€ãã¿ãŒã³ïŒP0 å¿
é ïŒ
src/lib/auth-guards.ts ã® requireStoreOwner çã䜿ã Server Action / èªåæææš©ãã§ã㯠(deleteProduct ç³») ã察象ãšããå Žåãä»¥äž 3 éå±€ãå¿
ãæãã:
| éå±€ | æ€èšŒå
容 | äŸ |
|---|
| (a) | ã¹ããŒæ€èšŒ | expect(...).rejects.toThrow("Forbidden: store not owned by current user.") |
| (b) | where å¥ã®æ§é æ€èšŒ | expect(mockDb.store.findUnique).toHaveBeenCalledWith({ where: { url, userId } }) â å°æ¥ userId æ¡ä»¶ãå€ããå Žåã«æ€ç¥ |
| (c) | ã¬ãŒã倱ææã®å¯äœçšãªãæ€èšŒ | expect(mockDb.xxx.upsert).not.toHaveBeenCalled() â äžæµã®æå³ããªã I/O ãæ€ç¥ |
(a) ã ãã§ã¯ãæ¡ä»¶ã¯ throw ããã where ãã userId æ¡ä»¶ãå€ãããããthrow åŸã«äžæµåŠçãèµ°ããæ¹ä¿®ãæ€ç¥ã§ããªãã
ãã¿ãŒã³ã®æ ¹æ ãšå®äŸã¯ docs/testing/SECURITY_GAP_REPORT.md §5.2 ãåç
§ã
ã®ã£ããã¬ããŒãããŠãŒã¶ãŒã«æç€ºããŠç¢ºèªãåãïŒ
## ãã¹ãã®ã£ããåæ: `src/queries/XXX.ts`
### ã«ããŒæžã¿ïŒN/M 颿°ïŒ
- â
createXXX: æ£åžžç³», æªèªèšŒ, ããªããŒã·ã§ã³
- â
getXXXList: æ£åžžç³», æªèªèšŒ
### æªã«ããŒïŒM-N 颿°ã»ãã¹ïŒ
- â updateXXX: åå² â ä»äººããŒã¿æŽæ°æåŠ [P0]
- â deleteXXX: å
šã±ãŒã¹æªãã¹ã [P1]
- â createXXX: DB äŸå€æã®ãšã©ãŒãã³ããªã³ã° [P3]
- â getXXXList: ç©ºçµæã®å Žå [P2]
### 远å äºå®ãã¹ãæ°: N ä»¶
Step 5ïœãã¹ããçæãã
ã³ãããç²åºŠã®ååïŒè©³çŽ°ã¯ .claude/rules/02-tdd-step-commit.md ïŒ:
ãã¹ããã¡ã€ã«ã¯ 1 ãã¡ã€ã«ïŒãŸãã¯è«ççã«ãŸãšããããå°ã°ã«ãŒãïŒåäœã§çæãã
ãã®éœåºŠ Red â Green â Refactor ãå®è¡ããŠã³ããããããè€æ°ãã¡ã€ã«ãäžæ¬çæããŠ
äžéã³ããããªãã§é²ããªãããšã
5-A. ãŠããããã¹ãïŒJestïŒ
é
眮å
: 察象ãã¡ã€ã«ãšåé局㮠*.test.ts
å¿
é ãã¿ãŒã³:
jest.mock("@/lib/db", () => ({
db: { },
}));
jest.mock("@clerk/nextjs/server");
import { createMockXXX } from "@/config/test-fixtures";
import { AuthTestHelpers, AssertionHelpers } from "@/config/test-helpers";
describe("颿°å", () => {
beforeEach(() => jest.clearAllMocks());
describe("æ£åžžç³»", () => {
it("æ£åžžç³»: [æåŸ
åäœãèšè¿°]", async () => {
});
});
describe("ç°åžžç³»", () => {
it("ç°åžžç³»: æªèªèšŒã®å Žåãšã©ãŒãã¹ããŒãã", async () => { ... });
it("ç°åžžç³»: æš©éäžè¶³ã®å Žåãšã©ãŒãè¿ã", async () => { ... });
it("ç°åžžç³»: DB æäœå€±ææã«ãšã©ãŒãè¿ã", async () => { ... });
});
describe("ãšããžã±ãŒã¹", () => {
it("ãšããžã±ãŒã¹: 空ã®çµæã»ãããè¿ã", async () => { ... });
it("å¢çå€: Decimal 粟床ãä¿æããã", async () => { ... });
});
});
èªèšŒãã¹ã 3 軞ïŒå
šãµãŒããŒã¢ã¯ã·ã§ã³ã§å¿
é ïŒ:
AuthTestHelpers.mockAuthenticated("USER");
AuthTestHelpers.mockUnauthenticated();
AuthTestHelpers.mockAuthenticated("USER");
Decimal ãã¹ã:
import { Prisma } from "@prisma/client";
const mockSize = createMockSize({
price: new Prisma.Decimal("99.99") as never,
});
expect(result.total.toNumber()).toBeCloseTo(199.98, 2);
5-B. ã³ã³ããŒãã³ããã¹ãïŒJest + RTLïŒ
é
眮å
: tests/component/[ã³ã³ããŒãã³ããã¹].test.tsx
import { render, screen } from "@testing-library/react";
import userEvent from "@testing-library/user-event";
describe("ComponentName", () => {
it("æ£åžžç³»: åæç¶æ
ã§æ£ããæç»ããã", () => {
render(<Component {...defaultProps} />);
expect(screen.getByRole("button", { name: /远å /i })).toBeInTheDocument();
});
it("æ£åžžç³»: ãŠãŒã¶ãŒæäœã§ç¶æ
ãæŽæ°ããã", async () => {
const user = userEvent.setup();
render(<Component {...defaultProps} />);
await user.click(screen.getByRole("button"));
expect(screen.getByText("æŽæ°æžã¿")).toBeInTheDocument();
});
});
5-C. E2E ãã¹ãïŒPlaywrightïŒ
é
眮å
: tests/e2e/[ãããŒå].spec.ts
import { test, expect } from "@playwright/test";
test.describe("ãã§ãã¯ã¢ãŠããããŒ", () => {
test("æ£åžžç³»: ååãã«ãŒãã«è¿œå ããŠæ³šæãå®äºã§ãã", async ({ page }, testInfo) => {
const workerSuffix = `${testInfo.project.name}-w${testInfo.workerIndex}`;
await page.goto("/");
});
});
Step 6ïœãã¹ããå®è¡ããŠæ€èšŒãã
bun run test -- --testPathPattern=<çæãã¡ã€ã«ãã¹>
bun run test
å
šãã¹ãããã¹ããªãå Žå:
- 倱æåå ãåæïŒã¢ãã¯äžè¶³ã»åãšã©ãŒã»å®è£
ãã°ïŒ
- ãã¹ãã³ãŒãã®ä¿®æ£ïŒã¢ãã¯è¿œå ã»åä¿®æ£ïŒ
- å®è£
ãã°ãçºèŠããå Žåã¯ãŠãŒã¶ãŒã«å ±åããä¿®æ£ãææ¡ãã
Step 6.5ïœã³ãããç²åºŠã決å®ããŠã³ããããã
1 ãã¡ã€ã« / 1 æ©èœ / 1 Tier åäœã§ git commit ãæã€ãå
·äœçãªç²åºŠ:
| äœæ¥çš®å¥ | 1 commit ã®åäœ |
|---|
| ãµãŒããŒã¢ã¯ã·ã§ã³ã®ãã¹ã远å | src/queries/<name>.ts + src/queries/<name>.test.ts |
| ã³ã³ããŒãã³ãã®æ¯ãèããã¹ã | tests/component/**/<name>.test.tsx 1 ãã¡ã€ã« |
| ã¹ãããã·ã§ããçæãã¹ã | <name>.test.tsx + 察å¿ãã __snapshots__/<name>.test.tsx.snap |
| E2E ãã¹ã | tests/e2e/<scenario>.spec.ts 1 ãã¡ã€ã«ïŒå¿
èŠã«å¿ããŠã·ãŒãå€æŽ + spec ã 1 commitïŒ |
| é¢é£ lint warning ä¿®æ£ | å¥ commit ã«åãã |
ã³ãããåã«å¿
ã test-complete ã¹ãã«çžåœã®ãã§ãã¯ïŒbun run test + bunx tsc --noEmit + bun run lintïŒãå®è¡ãã3 ç¹ãã¹ãŠééããŠãã commit ããã
詳现㯠.claude/rules/02-tdd-step-commit.md åç
§ã
Step 7ïœã¬ããŒããåºåãã
## ãã¹ã远å ã¬ããŒã
### 察象: `src/queries/XXX.ts`
### 远å ãã¹ã
| # | ãã¹ãå | åªå
床 | ã«ããŽãª |
|---|---------|--------|---------|
| 1 | æ£åžžç³»: XXX ãäœæã§ãã | P1 | ããžãã¹ããžã㯠|
| 2 | ç°åžžç³»: æªèªèšŒã®å Žåãšã©ãŒ | P0 | ã»ãã¥ãªã㣠|
| 3 | ãšããžã±ãŒã¹: 空é
åã®å Žå | P2 | å¢çå€ |
### ãã¹ãçµæ
- 远å : N tests
- å
šäœ: M tests, K suites (all passing)
### 次ã®ã¢ã¯ã·ã§ã³
- [ ] ãã¹ãå
容ã確èª
- [ ] `test-complete` ã¹ãã«ã§ã³ãããåãã§ãã¯ãå®è¡
- [ ] Step 6.5 ã®ã³ãããç²åºŠã§ `git commit` ãå®è¡ïŒ**ããã commit çŠæ¢**ïŒ
- [ ] ãã¹ãæ° / ã¹ã€ãŒãæ° / ã¹ãããã·ã§ããæ°ã®ãããããå€ãã£ãå Žåã
**`spec-sync-after-test` ã¹ãã«ãå¿
ãèµ·å**
ïŒ`docs/coverage-dashboard.html` ã®åçæ + spec / PROGRESS / COVERAGE_REPORT åæ + ã³ãããïŒ
éèŠã«ãŒã«
â çµ¶å¯ŸçŠæ¢
any åã®äœ¿çšïŒunknown + åã¬ãŒãã§ä»£æ¿ããïŒ
- æ¢åãã¹ãã®åé€ã»æžãæãïŒè¿œå ã®ã¿è¡ãïŒ
- ãã¹ãã€ã³ãã©æªèªã§ã®ãã¹ãçæçæ
it.skip() / it.only() ã®ã³ããã
- ãã¹ãå
ã§ã®
console.log() ã®æ®ç
@ts-ignore ã«ããåãšã©ãŒã®ç¡èŠ
- è€æ°ã®ãã¹ããã¡ã€ã« + ããã¥ã¡ã³ããäžéã³ããããªãã§äžæ¬è¿œå ããããš
ïŒ
.claude/rules/02-tdd-step-commit.md éåïŒ
spec-sync-after-test ãåŒã°ãã«ãããšã§ãŸãšããŠåæããããšå€æããããš
ïŒæ¬¡ã»ãã·ã§ã³ãžã®åŒãç¶ãæŒãã®åå ã«ãªãïŒ
â
å¿
é
- ãã¹ãåã¯æ¥æ¬èªã§ãæ£åžžç³»:ããç°åžžç³»:ãããšããžã±ãŒã¹:ããå¢çå€:ãã®æ¥é èŸãä»ãã
- AAA ãã¿ãŒã³ïŒArrange / Act / AssertïŒãå³å®ãã
- æ¢åãã¹ããã¡ã€ã«ã®ã¢ãã¯æ§æã»describe æ§é ã»åœåèŠåãèžè¥²ãã
- éé¡ãã£ãŒã«ãã¯
new Prisma.Decimal("value") ã䜿çšãã
- èªèšŒãå¿
èŠãªé¢æ°ã¯ãèªèšŒæžã¿æ£åžž / æªèªèšŒ / æš©éäžè¶³ãã® 3 軞ã§ãã¹ããã
- catch ãããã¯ã®ãã¹ãã§ã¯
error: unknown + instanceof Error ãã¿ãŒã³ãæ€èšŒãã
- ãã¹ãå®è¡åŸãå
šãã¹ãããã¹ããããšã確èªããŠããã¬ããŒããã
ð¡ æšå¥š
src/config/test-scenarios.ts ã®å¢çå€ã»é·ç§»ãã¿ãŒã³ãç©æ¥µçã«æŽ»çšãã
docs/testing/QA_TEST_PERSPECTIVES.md ã® P0 é
ç®ãåªå
çã«ã«ããŒãã
- æ¢åãã¡ã¯ããªã«äžè¶³ãããå Žåã¯
test-fixtures.ts ã«è¿œå ãææ¡ãã
- ãã©ã³ã¶ã¯ã·ã§ã³ïŒ
db.$transactionïŒã®ãã¹ãã§ã¯ MockPrismaClient ãã¿ãŒã³ãåç
§ãã
åè: ãã¹ãçš®å¥ãšé
眮å
| çš®å¥ | 察象 | é
眮å
| ç°å¢ |
|---|
| ãŠããã | src/queries/*.ts | src/queries/*.test.ts | node |
| ãŠããã | src/lib/*.ts | src/lib/*.test.ts | node |
| ãŠããã | src/cart-store/ | src/cart-store/*.test.ts | node |
| ã³ã³ããŒãã³ã | src/components/**/*.tsx | tests/component/**/*.test.tsx | jsdom |
| API ã«ãŒã | src/app/api/** | src/app/api/**/route.test.ts | node |
| ã·ãŒã | prisma/seed/** | prisma/seed/__tests__/*.test.ts | node |
| E2E | ãŠãŒã¶ãŒãã㌠| tests/e2e/*.spec.ts | Playwright |
åè: äž»èŠãã¡ã€ã«ãã¹
# ãã¹ãå
±éã€ã³ãã©ïŒå¿
é åç
§ïŒ
src/config/test-fixtures.ts ãã¡ã¯ããªé¢æ°ïŒ20+ ãšã³ãã£ãã£ïŒ
src/config/test-helpers.ts èªèšŒã¢ãã¯ã»ã¢ãµãŒã·ã§ã³ãã«ããŒ
src/config/test-scenarios.ts å¢çå€ã»ç¶æ
é·ç§»ã»é
éæãã¿ãŒã³
src/config/test-config.ts ãã¹ã宿°ã»ãšããžã±ãŒã¹æåå
# QA 芳ç¹ïŒã®ã£ããåæçšïŒ
docs/testing/QA_TEST_PERSPECTIVES.md P0/P1/P2 ãªã¹ã¯åªå
床ãã¹ã¿
# ãã¹ãèšèšããã¥ã¡ã³ã
docs/testing/TESTING_DESIGN.md ãã¹ããã©ãããã»ãã£ã¬ã¯ããªæ§æ
# åèãã¹ãïŒãã¿ãŒã³åŠç¿çšïŒ
src/queries/store.test.ts æå€§ãã¹ãã¹ã€ãŒãïŒ$transaction ã¢ãã¯å«ãïŒ
src/queries/product.test.ts Decimal ãã¹ãã»é
éæãã¹ã
src/queries/user.test.ts ã«ãŒãã»ãã§ãã¯ã¢ãŠãã»æ³šæãã¹ã
# é¢é£ã¹ãã«
.claude/skills/server-action-scaffold/SKILL.md æ°èŠã¢ãžã¥ãŒã«çšïŒæ¢åã³ãŒãã«ã¯æ¬ã¹ãã«ã䜿çšïŒ
.claude/skills/test-complete/SKILL.md ãã¹ãå®è¡ã»å質å€å®