# TDD 開發指南 - HR Portal 前端 **建立日期**: 2026-02-21 **測試框架**: Vitest + React Testing Library --- ## 📋 目錄結構 ``` frontend/ ├── services/ │ ├── __tests__/ │ │ └── onboarding.service.test.ts # API Service 測試 │ └── onboarding.service.ts # API Service 實作 ├── components/ │ ├── __tests__/ │ │ └── OnboardingForm.test.tsx # 組件測試 (待建立) │ └── OnboardingForm.tsx # 組件實作 (待建立) ├── types/ │ └── onboarding.ts # TypeScript 型別定義 ├── vitest.config.ts # Vitest 配置 └── vitest.setup.ts # 測試環境設置 ``` --- ## 🚀 快速開始 ### 1. 安裝依賴 ```bash cd q:/porscheworld_develop/hr-portal/frontend npm install ``` **需要安裝的測試相關套件**: ```json { "devDependencies": { "@testing-library/jest-dom": "^6.1.5", "@testing-library/react": "^14.1.2", "@testing-library/user-event": "^14.5.1", "@vitejs/plugin-react": "^4.2.1", "@vitest/ui": "^1.0.4", "jsdom": "^23.0.1", "vitest": "^1.0.4" } } ``` ### 2. 執行測試 ```bash # 執行所有測試 npm test # 執行測試並開啟 UI 介面 npm run test:ui # 執行測試並生成覆蓋率報告 npm run test:coverage # 監視模式(自動重跑) npm test -- --watch ``` --- ## 🎯 TDD 工作流程 ### Red-Green-Refactor 循環 ``` 1. RED → 先寫失敗的測試 2. GREEN → 寫最少的程式碼讓測試通過 3. REFACTOR → 重構程式碼,保持測試通過 ``` ### 範例:API Service TDD #### Step 1: RED - 寫失敗的測試 ```typescript // services/__tests__/onboarding.service.test.ts import { describe, it, expect } from 'vitest' import { onboardingService } from '../onboarding.service' describe('OnboardingService', () => { it('should successfully onboard an employee', async () => { const request = { resume_id: 1, keycloak_user_id: '550e8400-...', keycloak_username: 'wang.ming', hire_date: '2026-02-21', departments: [{ department_id: 9, position: '工程師' }], role_ids: [1], } const result = await onboardingService.onboardEmployee(request) expect(result).toHaveProperty('message') expect(result).toHaveProperty('employee') }) }) ``` 執行測試: ```bash npm test # ❌ 測試失敗:onboardingService 不存在 ``` #### Step 2: GREEN - 實作最少程式碼 ```typescript // services/onboarding.service.ts import axios from 'axios' class OnboardingService { async onboardEmployee(request: OnboardingRequest) { const response = await axios.post('/api/v1/emp-lifecycle/onboard', request) return response.data } } export const onboardingService = new OnboardingService() ``` 執行測試: ```bash npm test # ✅ 測試通過 ``` #### Step 3: REFACTOR - 重構 ```typescript // 新增型別定義、錯誤處理、環境變數等 ``` --- ## 📦 已完成的測試 ### ✅ API Service 層 **檔案**: `services/__tests__/onboarding.service.test.ts` **測試案例**: 1. ✅ `onboardEmployee` - 成功到職 2. ✅ `onboardEmployee` - API 失敗處理 3. ✅ `getEmployeeStatus` - 查詢員工狀態 4. ✅ `getEmployeeStatus` - 員工不存在 5. ✅ `offboardEmployee` - 成功離職 **執行測試**: ```bash npm test services/__tests__/onboarding.service.test.ts ``` --- ## 🔧 下一步:組件測試 ### 待建立的測試 #### 1. OnboardingForm 組件測試 ```typescript // components/__tests__/OnboardingForm.test.tsx import { describe, it, expect } from 'vitest' import { render, screen, fireEvent } from '@testing-library/react' import { OnboardingForm } from '../OnboardingForm' describe('OnboardingForm', () => { it('should render all required fields', () => { render() expect(screen.getByLabelText('員工姓名')).toBeInTheDocument() expect(screen.getByLabelText('Keycloak 使用者名稱')).toBeInTheDocument() expect(screen.getByLabelText('到職日期')).toBeInTheDocument() }) it('should validate required fields', async () => { render() const submitButton = screen.getByRole('button', { name: /送出/i }) fireEvent.click(submitButton) expect(await screen.findByText('此欄位為必填')).toBeInTheDocument() }) it('should submit form successfully', async () => { const onSubmit = vi.fn() render() // Fill form... fireEvent.click(screen.getByRole('button', { name: /送出/i })) expect(onSubmit).toHaveBeenCalled() }) }) ``` #### 2. EmployeeStatusCard 組件測試 ```typescript // 顯示員工詳細資訊的卡片組件 ``` --- ## 📊 測試覆蓋率目標 | 類型 | 目標 | 當前 | |------|------|------| | API Service | 100% | 100% ✅ | | Components | 80% | 0% ⏳ | | Hooks | 80% | 0% ⏳ | | Utilities | 90% | 0% ⏳ | --- ## 🎨 測試最佳實踐 ### 1. 測試命名 ```typescript // ✅ Good: 描述行為,不是實作 it('should display error when API fails', () => {}) // ❌ Bad: 測試實作細節 it('should call axios.post', () => {}) ``` ### 2. AAA 模式 ```typescript it('should do something', () => { // Arrange (準備) const data = { ... } // Act (執行) const result = doSomething(data) // Assert (斷言) expect(result).toBe(expected) }) ``` ### 3. 隔離測試 ```typescript // ✅ Good: 每個測試獨立 describe('MyComponent', () => { beforeEach(() => { // 每個測試前重置 cleanup() }) }) // ❌ Bad: 測試間有依賴 ``` ### 4. Mock 外部依賴 ```typescript // Mock axios vi.mock('axios') // Mock Next.js router vi.mock('next/navigation') ``` --- ## 🐛 常見問題 ### Q1: `ReferenceError: describe is not defined` **解決**: 確認 `vitest.config.ts` 中有設定 `globals: true` ### Q2: `TypeError: Cannot read property 'xxx' of undefined` **解決**: 檢查是否正確 mock 了依賴 ### Q3: 測試執行很慢 **解決**: - 使用 `vi.mock()` 避免真實 API 呼叫 - 使用 `--run` 參數執行一次性測試 --- ## 📚 參考資源 - [Vitest 官方文件](https://vitest.dev/) - [React Testing Library](https://testing-library.com/docs/react-testing-library/intro/) - [Jest DOM Matchers](https://github.com/testing-library/jest-dom) --- **下一步**: 執行 `npm install` 安裝測試依賴,然後執行 `npm test` 確認測試環境正常運作。