dramaling-vocab-learning/note/複習系統/延遲計數系統測試規格.md

332 lines
11 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# 延遲計數系統測試規格
**目的**: 將您的延遲計數需求轉換為可執行的測試案例
**測試範圍**: 跳過功能、排序邏輯、答錯處理、優先級管理
**測試方式**: Jest + React Testing Library
**最後更新**: 2025-10-03
---
## 🧪 **核心需求測試**
### **測試1: 跳過功能基礎邏輯**
```typescript
describe('延遲計數系統 - 跳過功能', () => {
test('當使用者在答題時點擊跳過,該題目會跳過', () => {
// 準備測試數據
const initialCards = [
{
id: '1',
word: 'evidence',
skipCount: 0,
wrongCount: 0,
isCompleted: false
}
]
const currentIndex = 0
// 執行跳過操作
const result = handleSkip(initialCards, currentIndex)
// 驗證結果
expect(result.updatedCards[0].skipCount).toBe(1)
expect(result.updatedCards[0].wrongCount).toBe(0) // 不影響答錯次數
expect(result.nextIndex).toBe(1) // 移動到下一題
expect(result.updatedCards[0].isCompleted).toBe(false) // 未完成狀態
})
test('同一題目可以多次跳過,次數累加', () => {
const cards = [
{ id: '1', word: 'test', skipCount: 2, wrongCount: 1, isCompleted: false }
]
const result = handleSkip(cards, 0)
expect(result.updatedCards[0].skipCount).toBe(3) // 從2增加到3
expect(result.updatedCards[0].wrongCount).toBe(1) // 保持不變
})
})
```
### **測試2: 排序優先級邏輯**
```typescript
describe('延遲計數系統 - 排序邏輯', () => {
test('題目排序應該是被跳過次數越少越前面', () => {
// 準備不同延遲分數的卡片
const cards = [
{ id: '1', word: 'card1', skipCount: 3, wrongCount: 1, originalOrder: 1, isCompleted: false }, // 延遲分數: 4
{ id: '2', word: 'card2', skipCount: 1, wrongCount: 0, originalOrder: 2, isCompleted: false }, // 延遲分數: 1
{ id: '3', word: 'card3', skipCount: 0, wrongCount: 0, originalOrder: 3, isCompleted: false }, // 延遲分數: 0
{ id: '4', word: 'card4', skipCount: 2, wrongCount: 0, originalOrder: 4, isCompleted: false } // 延遲分數: 2
]
// 執行排序
const sorted = sortCardsByPriority(cards)
// 驗證排序結果 (延遲分數由小到大)
expect(sorted[0].id).toBe('3') // 延遲分數 0
expect(sorted[1].id).toBe('2') // 延遲分數 1
expect(sorted[2].id).toBe('4') // 延遲分數 2
expect(sorted[3].id).toBe('1') // 延遲分數 4
})
test('延遲分數相同時按原始順序排列', () => {
const cards = [
{ id: '1', skipCount: 1, wrongCount: 0, originalOrder: 3, isCompleted: false }, // 延遲分數: 1
{ id: '2', skipCount: 0, wrongCount: 1, originalOrder: 1, isCompleted: false }, // 延遲分數: 1
{ id: '3', skipCount: 1, wrongCount: 0, originalOrder: 2, isCompleted: false } // 延遲分數: 1
]
const sorted = sortCardsByPriority(cards)
// 相同延遲分數按originalOrder排序
expect(sorted[0].originalOrder).toBe(1)
expect(sorted[1].originalOrder).toBe(2)
expect(sorted[2].originalOrder).toBe(3)
})
})
```
### **測試3: 答錯效果等同跳過**
```typescript
describe('延遲計數系統 - 答錯處理', () => {
test('答錯的效果和跳過一樣都會被註記一次', () => {
const initialCard = {
id: '1',
word: 'test',
skipCount: 0,
wrongCount: 0,
isCompleted: false
}
// 測試跳過效果
const skippedResult = handleSkip([initialCard], 0)
const skippedScore = skippedResult.updatedCards[0].skipCount + skippedResult.updatedCards[0].wrongCount
// 測試答錯效果
const wrongResult = handleWrongAnswer([initialCard], 0)
const wrongScore = wrongResult.updatedCards[0].skipCount + wrongResult.updatedCards[0].wrongCount
// 兩者都應該增加1分延遲分數
expect(skippedScore).toBe(1)
expect(wrongScore).toBe(1)
})
test('信心度1-2算答錯3算答對', () => {
const card = { id: '1', word: 'test', skipCount: 0, wrongCount: 0, isCompleted: false }
// 信心度1 (模糊) → 答錯
const result1 = handleConfidenceAnswer([card], 0, 1)
expect(result1.updatedCards[0].wrongCount).toBe(1)
expect(result1.updatedCards[0].isCompleted).toBe(false)
// 信心度2 (一般) → 答對
const result2 = handleConfidenceAnswer([card], 0, 2)
expect(result2.updatedCards[0].isCompleted).toBe(true)
expect(result2.updatedCards[0].wrongCount).toBe(0) // 不增加
// 信心度3 (熟悉) → 答對
const result3 = handleConfidenceAnswer([card], 0, 3)
expect(result3.updatedCards[0].isCompleted).toBe(true)
})
})
```
### **測試4: 排序不排除邏輯**
```typescript
describe('延遲計數系統 - 不排除原則', () => {
test('被跳過只是排序問題,不是直接排除', () => {
const cards = [
{ id: '1', word: 'high-delay', skipCount: 10, wrongCount: 5, isCompleted: false }, // 高延遲
{ id: '2', word: 'normal', skipCount: 0, wrongCount: 0, isCompleted: false }, // 正常
{ id: '3', word: 'completed', skipCount: 2, wrongCount: 1, isCompleted: true } // 已完成
]
const sorted = sortCardsByPriority(cards)
// 驗證沒有卡片被排除
expect(sorted).toHaveLength(3)
expect(sorted.find(c => c.id === '1')).toBeDefined() // 高延遲卡片仍存在
// 驗證排序正確
expect(sorted[0].id).toBe('2') // 正常卡片優先
expect(sorted[1].id).toBe('1') // 延遲卡片其次
expect(sorted[2].id).toBe('3') // 已完成卡片最後
})
test('即使跳過很多次的卡片仍會被練習', () => {
const cards = [
{ id: '1', word: 'difficult', skipCount: 20, wrongCount: 15, isCompleted: false }
]
const sorted = sortCardsByPriority(cards)
expect(sorted).toHaveLength(1)
expect(sorted[0].id).toBe('1') // 即使延遲分數很高,仍然存在
})
})
```
### **測試5: 完整流程集成**
```typescript
describe('延遲計數系統 - 完整流程', () => {
test('完整的學習會話流程', () => {
// 初始狀態: 3張卡片
let cards = [
{ id: '1', word: 'easy', skipCount: 0, wrongCount: 0, originalOrder: 1, isCompleted: false },
{ id: '2', word: 'medium', skipCount: 0, wrongCount: 0, originalOrder: 2, isCompleted: false },
{ id: '3', word: 'hard', skipCount: 0, wrongCount: 0, originalOrder: 3, isCompleted: false }
]
// 第1輪: easy答對, medium跳過, hard答錯
cards = handleConfidenceAnswer(cards, 0, 3).updatedCards // easy完成
cards = handleSkip(cards, 1).updatedCards // medium跳過+1
cards = handleWrongAnswer(cards, 2).updatedCards // hard答錯+1
// 排序後應該是: medium, hard (延遲分數都是1按原順序)
const sorted1 = sortCardsByPriority(cards.filter(c => !c.isCompleted))
expect(sorted1[0].word).toBe('medium')
expect(sorted1[1].word).toBe('hard')
// 第2輪: medium再次跳過, hard答對完成
cards = handleSkip(cards, 1).updatedCards // medium跳過+1 (總共2次)
cards = handleConfidenceAnswer(cards, 2, 2).updatedCards // hard完成
// 最終只剩medium未完成延遲分數為2
const remaining = cards.filter(c => !c.isCompleted)
expect(remaining).toHaveLength(1)
expect(remaining[0].word).toBe('medium')
expect(remaining[0].skipCount).toBe(2)
})
})
```
---
## 🎯 **測試實作函數**
### **需要實作的核心函數**
```typescript
// 這些是測試中使用的函數,需要在實際代碼中實作
interface TestResult {
updatedCards: CardState[]
nextIndex: number
}
// 跳過處理函數
function handleSkip(cards: CardState[], currentIndex: number): TestResult
// 答錯處理函數
function handleWrongAnswer(cards: CardState[], currentIndex: number): TestResult
// 信心度答題函數
function handleConfidenceAnswer(cards: CardState[], currentIndex: number, confidence: 1|2|3): TestResult
// 排序函數
function sortCardsByPriority(cards: CardState[]): CardState[]
```
---
## 📊 **測試覆蓋範圍**
### **功能覆蓋**
- ✅ 跳過次數計數
- ✅ 答錯次數計數
- ✅ 優先級排序邏輯
- ✅ 完成狀態管理
- ✅ 多次操作累計效果
### **邊界條件測試**
- ✅ 極高延遲分數的卡片
- ✅ 相同延遲分數的排序
- ✅ 全部完成的情況
- ✅ 單卡片情況
### **集成測試**
- ✅ 完整學習會話流程
- ✅ 多輪練習的狀態變化
- ✅ 不同信心度的處理
---
## 🚀 **TDD開發流程**
### **第1步: Red (測試失敗)**
```bash
npm test delay-counting-system.test.ts
# 所有測試應該失敗,因為函數還沒實作
```
### **第2步: Green (最小實作)**
```typescript
// 實作最簡單的版本讓測試通過
const handleSkip = (cards, index) => {
const newCards = [...cards]
newCards[index] = { ...newCards[index], skipCount: newCards[index].skipCount + 1 }
return { updatedCards: newCards, nextIndex: index + 1 }
}
```
### **第3步: Refactor (重構優化)**
```typescript
// 重構為更優雅的版本,保持測試通過
const handleSkip = useCallback((cards: CardState[], currentIndex: number) => {
const updatedCards = cards.map((card, index) =>
index === currentIndex
? { ...card, skipCount: card.skipCount + 1 }
: card
)
return {
updatedCards,
nextIndex: getNextIndex(updatedCards, currentIndex)
}
}, [])
```
---
## 🎯 **測試的業務價值**
### **確保需求實現**
- ✅ 您的4個核心需求都有對應測試
- ✅ 邊界情況都有覆蓋
- ✅ 集成場景完整測試
### **回歸保護**
- ✅ 未來修改不會破壞核心邏輯
- ✅ 重構時有安全保障
- ✅ 新功能不會影響現有行為
### **需求文檔化**
- ✅ 測試即是可執行的需求規格
- ✅ 開發者可以直接理解期望行為
- ✅ 產品經理可以驗證實作正確性
---
## 📋 **測試執行清單**
### **開發前**
- [ ] 所有測試都失敗 (因為還沒實作)
- [ ] 測試準確描述了您的需求
### **開發中**
- [ ] 逐個實作函數讓測試通過
- [ ] 保持測試綠燈狀態
- [ ] 重構時確保測試不破壞
### **完成後**
- [ ] 所有測試通過
- [ ] 覆蓋率達到90%+
- [ ] 邊界條件都正確處理
---
**這些測試完美地捕捉了您的延遲計數需求可以指導TDD開發確保實作完全符合您的預期** 🎯
*測試規格制定: 2025-10-03*
*基於用戶明確需求*
*TDD開發就緒*