Skip to main content

tdd-guidelines

cc-office TDD workflow — Red-Green-Refactor cycle, expect-immutability rule during refactor, test pyramid, naming (AAA), and refactor discipline. Use when writing new code, refactoring, designing test structure, fixing failing tests in a principled way, or onboarding to this project's TDD conventions. Pairs with generic `tdd` skill and `test-doubles` theory.

الانتقال إلى التثبيت

معلومات المصدر

المستودع
recca0120/code-quest
آخر نشاط في المصدر
١٩ أبريل ٢٠٢٦ في ٢٢:٤٦
لغة SKILL.md المكتشفة
الصينية
النجوم
١١
التفرعات
٢

خيارات التثبيت

يُحدَّد Prompt الذي يراجع المصدر أولًا بشكل افتراضي. يمكنك التبديل إلى أمر مباشر أو تنزيل نسخة محلية.

مراجعة ملفات المصدر

اقرأ SKILL.md وأي ملفات مرافقة يعرضها SkillsMP قبل أن تقرر التثبيت.

مستكشف الملفات
2 ملفات

عرض SKILL.md

SKILL.md
تعليمات المصدر · معاينة للقراءة فقط
name
tdd-guidelines
description
cc-office TDD workflow — Red-Green-Refactor cycle, expect-immutability rule during refactor, test pyramid, naming (AAA), and refactor discipline. Use when writing new code, refactoring, designing test structure, fixing failing tests in a principled way, or onboarding to this project's TDD conventions. Pairs with generic `tdd` skill and `test-doubles` theory.
# TDD Guidelines - 測試驅動開發準則 ## 核心原則 Code Quest 採用 **嚴格的 TDD (Test-Driven Development)** 開發流程: > **先寫測試,逼出程式用法,再產生程式。絕不憑空想象。** --- ## TDD 開發循環 ``` ┌─────────────────────────────────────────┐ │ RED → GREEN → REFACTOR │ ├─────────────────────────────────────────┤ │ │ │ 1. 🔴 RED: 寫一個會失敗的測試 │ │ - 描述期望的行為 │ │ - 測試應該失敗 (因為功能還不存在) │ │ │ │ 2. 🟢 GREEN: 寫最少的程式碼讓測試通過 │ │ - 只寫必要的程式碼 │ │ - 不要過度設計 │ │ - 讓測試變綠就停止 │ │ │ │ 3. 🔵 REFACTOR: 重構改善程式碼品質 │ │ - ⚠️ 不可變更 expect │ │ - expect 是我們最重要的結果 │ │ - 只重構實作,不改變行為 │ │ │ └─────────────────────────────────────────┘ ``` --- ## 測試金字塔 (優先順序) ``` ┌─────────────┐ │ Feature │ 最優先 │ Test │ (End-to-End) ├─────────────┤ │ Integration │ 次優先 │ Test │ (Component) ├─────────────┤ │ Unit │ 最後 │ Test │ (Function) └─────────────┘ ``` ### 1. Feature Test (最優先) **目的**: 從使用者角度測試完整功能 **範例**: 測試完整的戰鬥流程 ```typescript // tests/features/battle.feature.test.ts import { test, expect } from 'vitest'; import { render, screen, userEvent } from '@testing-library/react'; import { App } from '@/App'; test('玩家可以啟動戰鬥並看到 AI 施放技能', async () => { // Given: 玩家在城鎮 render(<App />); // When: 玩家點擊「前往野外」 await userEvent.click(screen.getByRole('button', { name: /前往野外/i })); // Then: 觸發戰鬥 expect(screen.getByText(/遭遇敵人/i)).toBeInTheDocument(); // When: AI 施放技能 await screen.findByText(/閱讀卷軸/i, {}, { timeout: 5000 }); // Then: 顯示 MP 消耗 expect(screen.getByText(/MP: 95\/100/i)).toBeInTheDocument(); }); ``` ### 2. Integration Test (次優先) **目的**: 測試多個元件的協作 **範例**: 測試 BattleServer 與 BattleBridge 的整合 ```typescript // tests/integration/battle-server.test.ts import { describe, it, expect, beforeEach } from 'vitest'; import { BattleServer } from '@/server/BattleServer'; import { BattleBridge } from '@/server/BattleBridge'; describe('BattleServer Integration', () => { let server: BattleServer; beforeEach(() => { server = new BattleServer(); }); it('應該啟動戰鬥並接收 PTY 輸出', async () => { // Given: 戰鬥配置 const config = { aiModel: 'haiku', prompt: '列出檔案', workingDir: process.cwd() }; // When: 啟動戰鬥 const battleId = server.startBattle(config); // Then: 應該建立戰鬥 Session expect(server.battles.has(battleId)).toBe(true); // When: 等待 PTY 輸出 const output = await new Promise((resolve) => { server.on('output', (id, data) => { if (id === battleId) resolve(data); }); }); // Then: 應該收到輸出 expect(output).toBeTruthy(); }); }); ``` ### 3. Unit Test (最後) **目的**: 測試單一函數或類別的邏輯 **範例**: 測試 OutputParser ```typescript // tests/unit/output-parser.test.ts import { describe, it, expect } from 'vitest'; import { OutputParser } from '@/server/OutputParser'; describe('OutputParser', () => { const parser = new OutputParser(); it('應該解析 tool_use 事件為 skill_cast', () => { // Given: Claude 輸出 (NDJSON) const input = JSON.stringify({ type: 'tool_use', name: 'Read', input: { file_path: 'src/main.ts' } }); // When: 解析 const events = parser.parse(input); // Then: 應該產生 skill_cast 事件 expect(events).toHaveLength(1); expect(events[0]).toMatchObject({ type: 'skill_cast', skillName: '閱讀卷軸', target: 'src/main.ts', mpCost: 5 }); }); it('應該解析非 JSON 行為 dialogue 事件', () => { // Given: 純文字輸出 const input = 'System initialized'; // When: 解析 const events = parser.parse(input); // Then: 應該產生 dialogue 事件 expect(events).toHaveLength(1); expect(events[0]).toMatchObject({ type: 'dialogue', speaker: 'system', message: 'System initialized' }); }); }); ``` --- ## Test Double 選擇 本 skill 聚焦 TDD 流程。Test double 選擇依情境見專屬 skill: - **經典五型定義**(Dummy / Stub / Fake / Spy / Mock)→ `test-doubles` skill - **Client 測試**(React / socket / Zustand)→ `frontend-testing` skill - **Server 測試**(Node / socket.io / DB / CLI)→ `vitest-testing` skill 核心原則:**優先真實實作;需要隔離再選 double,層級越低越好**。重構時 expect 不變的黃金法則跟 test double 型別無關。 --- ## 前端測試策略 > **React component / hook / store 的細節 patterns** 參考 `frontend-testing` 與 `testing-best-practices` skill。 > 本節只涵蓋 TDD 流程裡的 MSW 設定範例。 > > **ky-based API client 測試** 用 `msw-fetch-mock` 套件(protocol-level intercept),詳見 `msw-fetch-mock` skill。 ### MSW (Mock Service Worker) 設定 ```typescript // tests/mocks/handlers.ts import { http, HttpResponse } from 'msw'; export const handlers = [ // WebSocket 連接 http.get('/ws', () => { return HttpResponse.json({ status: 'connected' }); }), // 啟動戰鬥 http.post('/api/battles', async ({ request }) => { const body = await request.json(); return HttpResponse.json({ battleId: 'test-battle-id', status: 'started' }); }), // 取得戰鬥狀態 http.get('/api/battles/:battleId', ({ params }) => { return HttpResponse.json({ id: params.battleId, hp: 100, mp: 80, status: 'active' }); }) ]; // tests/setup.ts import { setupServer } from 'msw/node'; import { handlers } from './mocks/handlers'; export const server = setupServer(...handlers); beforeAll(() => server.listen()); afterEach(() => server.resetHandlers()); afterAll(() => server.close()); ``` ### MSW Fetch Mock 範例 ```typescript // tests/components/BattleView.test.tsx import { render, screen } from '@testing-library/react'; import { server } from '../setup'; import { http, HttpResponse } from 'msw'; it('應該顯示戰鬥狀態', async () => { // Given: Mock API 回應 server.use( http.get('/api/battles/test-id', () => { return HttpResponse.json({ id: 'test-id', hp: 75, mp: 60, status: 'active' }); }) ); // When: 渲染元件 render(<BattleView battleId="test-id" />); // Then: 應該顯示正確的狀態 await screen.findByText(/HP: 75/i); expect(screen.getByText(/MP: 60/i)).toBeInTheDocument(); }); ``` --- ## 重構規則 ### ⚠️ 黃金法則:不可變更 expect ```typescript // ❌ 錯誤:重構時改變了 expect it('應該計算總 MP 消耗', () => { const result = calculateTotalMP([10, 20, 30]); // 重構前 expect(result).toBe(60); // 重構後 (錯誤!) expect(result).toBeGreaterThan(50); // ❌ 改變了期望 }); // ✅ 正確:只重構實作,不改變 expect it('應該計算總 MP 消耗', () => { const result = calculateTotalMP([10, 20, 30]); // 重構前後 expect 不變 expect(result).toBe(60); // ✅ 期望不變 // 可以重構 calculateTotalMP 的實作 // 但結果必須一樣 }); ``` ### 重構檢查清單 ```typescript // 重構前 function calculateMP(skills: Skill[]): number { let total = 0; for (let i = 0; i < skills.length; i++) { total += skills[i].mpCost; } return total; } // 重構後 function calculateMP(skills: Skill[]): number { return skills.reduce((sum, skill) => sum + skill.mpCost, 0); } // ✅ 檢查清單 // 1. expect 不變? → ✓ // 2. 所有測試還通過? → ✓ // 3. 行為完全一致? → ✓ // 4. 程式碼更簡潔? → ✓ ``` --- ## 詳細範例與常見陷阱 完整 TDD 範例 walkthrough、錯誤/正確對照、常見陷阱與修正在 `references/examples-and-pitfalls.md`。此處只列主要陷阱類別: - 先寫完整實作再補測試(應先寫會失敗的測試) - 一次 Green 太大步(拆成最小可通過實作) - 測試內部實作細節(應測可觀察行為) - 重構時改了 expect(expect 變動 = 行為變更,走另一個 RED → GREEN 循環) 詳見 references 的 ❌/✓ 對照範例。 開始實作前,確認: - [ ] **測試先行** - 是否先寫了失敗的測試? - [ ] **最小實作** - 是否只寫了讓測試通過的最少程式碼? - [ ] **測試金字塔** - 是否優先寫 Feature Test? - [ ] **Test Double** - 是否優先使用 Fake? - [ ] **expect 不變** - 重構時是否保持 expect 不變? - [ ] **避免過度設計** - 是否只實作當前測試需要的功能? 開始實作後,持續檢查: - [ ] **所有測試通過** - 綠燈? - [ ] **程式碼簡潔** - 可以重構嗎? - [ ] **行為一致** - 重構後行為相同嗎? - [ ] **expect 不變** - 重構時 expect 沒變? --- ## 快速參考 ### TDD 循環 ```bash # 1. RED $ npm test ❌ Test failed # 2. GREEN $ npm test ✓ Test passed # 3. REFACTOR $ npm test ✓ Test still passed (expect 不變) ``` ### 測試優先順序 ``` Feature Test (最優先) → Integration Test → Unit Test ``` ### Test Double 優先順序 ``` Fake (最優先) → Spy → Stub → Mock (最後) ``` ### 重構黃金法則 ```typescript // ⚠️ 重構時 expect 絕對不可變更 expect(result).toBe(expectedValue); // 永遠不變 ``` --- ## 相關 skill **TDD 與 test double 理論**: - `tdd` — 通用 TDD(Red-Green-Refactor、命名、金字塔) - `test-doubles` — 五型(Mock / Stub / Fake / Spy / Dummy)理論與選擇順序 **Test harness(具體工具)**: - `fake-summoner-server` — server 端 socket / pipeline / ProcessRunner harness - `fake-summoner-client` — client 端 `renderWithChannel` / `renderWithWorkspace` - `fixture-driven-tdd` — parser / protocol 用真實 CLI JSON fixture 驅動 **各層測試慣例**: - `vitest-testing` — server vitest 專案慣例(5-tier test double) - `frontend-testing` — client RTL 慣例(7-tier test double、query 優先序) - `testing-best-practices` — 共用 Vitest + RTL + MSW 慣例 - `cc-office-review` — cc-office 專案特定測試檢查 **專案脈絡**: - `project-overview` — 當前架構(roadmap 參考 `rpg-roadmap`) **測試框架文檔**: - Vitest: https://vitest.dev/ - Testing Library: https://testing-library.com/ - MSW: https://mswjs.io/ --- 記住:**測試是設計工具,不是驗證工具。先寫測試,讓測試驅動你的設計!**
عرض على GitHub