Initial commit
This commit is contained in:
11
.claude-plugin/plugin.json
Normal file
11
.claude-plugin/plugin.json
Normal file
@@ -0,0 +1,11 @@
|
||||
{
|
||||
"name": "kent-beck-tdd",
|
||||
"description": "Kent Beck's organic TDD: Let design emerge naturally through small, iterative test cycles",
|
||||
"version": "1.0.0",
|
||||
"author": {
|
||||
"name": "cash"
|
||||
},
|
||||
"commands": [
|
||||
"./commands"
|
||||
]
|
||||
}
|
||||
3
README.md
Normal file
3
README.md
Normal file
@@ -0,0 +1,3 @@
|
||||
# kent-beck-tdd
|
||||
|
||||
Kent Beck's organic TDD: Let design emerge naturally through small, iterative test cycles
|
||||
415
commands/kb-green.md
Normal file
415
commands/kb-green.md
Normal file
@@ -0,0 +1,415 @@
|
||||
---
|
||||
description: 讓測試通過(Green 階段)。用最簡單、甚至是"錯誤"的方式快速讓測試變綠。
|
||||
---
|
||||
|
||||
# TDD Green - 讓測試通過(Green)
|
||||
|
||||
用最簡單的方式讓測試通過。可以作弊、可以硬編碼、可以"錯誤"。
|
||||
|
||||
**【功能名】**:{{feature_name}}
|
||||
|
||||
## Kent Beck 的 Green 階段理念
|
||||
|
||||
> "快速讓測試通過,用任何手段。"
|
||||
> "乾淨的程式碼是目標,但先讓它能動。"
|
||||
|
||||
### 三種讓測試通過的策略
|
||||
|
||||
Kent Beck 在書中提出的三種策略:
|
||||
|
||||
#### 策略 1:Fake It(假實作)
|
||||
|
||||
**最快速的方式:回傳常數**
|
||||
|
||||
```javascript
|
||||
// 測試
|
||||
test('5 * 2 = 10', () => {
|
||||
expect(multiply(5, 2)).toBe(10);
|
||||
});
|
||||
|
||||
// 假實作:直接回傳 10!
|
||||
function multiply(a, b) {
|
||||
return 10;
|
||||
}
|
||||
```
|
||||
|
||||
**為什麼這樣做?**
|
||||
- 快速得到綠燈
|
||||
- 心理上的安全感
|
||||
- 用下一個測試來逼出真正的實作
|
||||
|
||||
#### 策略 2:Obvious Implementation(明顯實作)
|
||||
|
||||
**當實作很明顯時,直接寫出來**
|
||||
|
||||
```javascript
|
||||
// 測試
|
||||
test('加法運算', () => {
|
||||
expect(add(2, 3)).toBe(5);
|
||||
});
|
||||
|
||||
// 明顯實作:加法很簡單
|
||||
function add(a, b) {
|
||||
return a + b;
|
||||
}
|
||||
```
|
||||
|
||||
**何時使用?**
|
||||
- 實作非常簡單
|
||||
- 你很有信心
|
||||
- 不需要三角測量
|
||||
|
||||
#### 策略 3:Triangulation(三角測量)
|
||||
|
||||
**用多個測試來推導正確實作**
|
||||
|
||||
```javascript
|
||||
// 第 1 個測試
|
||||
test('1 + 1 = 2', () => {
|
||||
expect(add(1, 1)).toBe(2);
|
||||
});
|
||||
|
||||
// 假實作
|
||||
function add(a, b) {
|
||||
return 2;
|
||||
}
|
||||
|
||||
// 第 2 個測試(三角測量)
|
||||
test('2 + 3 = 5', () => {
|
||||
expect(add(2, 3)).toBe(5);
|
||||
});
|
||||
|
||||
// 被逼出真正的實作
|
||||
function add(a, b) {
|
||||
return a + b;
|
||||
}
|
||||
```
|
||||
|
||||
**何時使用?**
|
||||
- 不確定正確的實作方式
|
||||
- 想從多個角度驗證
|
||||
- 設計還不清楚
|
||||
|
||||
## 實作步驟
|
||||
|
||||
### 1. 選擇策略
|
||||
|
||||
根據你的信心程度:
|
||||
|
||||
```
|
||||
信心很低 → Fake It(假實作)
|
||||
信心中等 → Triangulation(三角測量)
|
||||
信心很高 → Obvious Implementation(明顯實作)
|
||||
```
|
||||
|
||||
### 2. 寫最少的程式碼
|
||||
|
||||
**重點:最少**
|
||||
|
||||
```javascript
|
||||
// 測試需要一個類別
|
||||
test('Dollar 乘法', () => {
|
||||
const five = new Dollar(5);
|
||||
const product = five.times(2);
|
||||
expect(product.amount).toBe(10);
|
||||
});
|
||||
|
||||
// 最少的實作
|
||||
class Dollar {
|
||||
constructor(amount) {
|
||||
this.amount = amount;
|
||||
}
|
||||
|
||||
times(multiplier) {
|
||||
return new Dollar(10); // 假實作!
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### 3. 執行測試
|
||||
|
||||
```bash
|
||||
npm test
|
||||
```
|
||||
|
||||
**看到綠燈!** 🟢
|
||||
|
||||
### 4. 更新 journey.md
|
||||
|
||||
```markdown
|
||||
#### 🟢 Green - 讓測試通過
|
||||
|
||||
策略:{Fake It / Obvious / Triangulation}
|
||||
|
||||
實作說明:
|
||||
{簡述你做了什麼}
|
||||
|
||||
程式碼位置:{檔案路徑}
|
||||
|
||||
測試結果:✅ 通過
|
||||
|
||||
#### 🤔 反思
|
||||
- 這個實作明顯是假的/硬編碼的嗎?
|
||||
- 需要下一個測試來逼出真正的實作嗎?
|
||||
- 有沒有重複的程式碼?
|
||||
|
||||
#### 📝 下一步
|
||||
執行 /kb-refactor 重構
|
||||
```
|
||||
|
||||
## Fake It 的威力
|
||||
|
||||
### 範例:Money 的演進
|
||||
|
||||
**第 1 輪:完全假實作**
|
||||
|
||||
```javascript
|
||||
test('5 * 2 = 10', () => {
|
||||
const five = new Dollar(5);
|
||||
expect(five.times(2).amount).toBe(10);
|
||||
});
|
||||
|
||||
class Dollar {
|
||||
constructor(amount) {
|
||||
this.amount = amount;
|
||||
}
|
||||
times(multiplier) {
|
||||
return new Dollar(10); // 硬編碼!
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
**第 2 輪:三角測量逼出真實作**
|
||||
|
||||
```javascript
|
||||
test('5 * 3 = 15', () => {
|
||||
const five = new Dollar(5);
|
||||
expect(five.times(3).amount).toBe(15);
|
||||
});
|
||||
|
||||
class Dollar {
|
||||
times(multiplier) {
|
||||
return new Dollar(this.amount * multiplier); // 真實作!
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## 常見的"作弊"技巧
|
||||
|
||||
### 技巧 1:回傳常數
|
||||
|
||||
```javascript
|
||||
function getWelcomeMessage() {
|
||||
return "Hello, World!"; // 先硬編碼
|
||||
}
|
||||
```
|
||||
|
||||
### 技巧 2:複製測試資料
|
||||
|
||||
```javascript
|
||||
// 測試
|
||||
expect(processData(input)).toEqual({
|
||||
status: 'success',
|
||||
count: 5
|
||||
});
|
||||
|
||||
// 實作:直接回傳期待值
|
||||
function processData(input) {
|
||||
return { status: 'success', count: 5 };
|
||||
}
|
||||
```
|
||||
|
||||
### 技巧 3:最簡單的 if
|
||||
|
||||
```javascript
|
||||
// 測試 1
|
||||
test('even number', () => {
|
||||
expect(classify(2)).toBe('even');
|
||||
});
|
||||
|
||||
// 假實作
|
||||
function classify(n) {
|
||||
if (n === 2) return 'even';
|
||||
}
|
||||
|
||||
// 測試 2 會逼出真實作
|
||||
test('another even number', () => {
|
||||
expect(classify(4)).toBe('even');
|
||||
});
|
||||
|
||||
function classify(n) {
|
||||
return n % 2 === 0 ? 'even' : 'odd';
|
||||
}
|
||||
```
|
||||
|
||||
## 實作的禁忌
|
||||
|
||||
Kent Beck 說明在 Green 階段應該避免的:
|
||||
|
||||
### ❌ 不要過度設計
|
||||
|
||||
```javascript
|
||||
// ❌ 不要這樣(太複雜)
|
||||
class Dollar {
|
||||
constructor(amount, currency = 'USD') {
|
||||
this.amount = amount;
|
||||
this.currency = currency;
|
||||
}
|
||||
|
||||
times(multiplier) {
|
||||
// 處理多幣別、匯率轉換...
|
||||
}
|
||||
|
||||
// 一堆還不需要的方法
|
||||
add() {}
|
||||
subtract() {}
|
||||
convert() {}
|
||||
}
|
||||
|
||||
// ✅ 要這樣(夠用就好)
|
||||
class Dollar {
|
||||
constructor(amount) {
|
||||
this.amount = amount;
|
||||
}
|
||||
|
||||
times(multiplier) {
|
||||
return new Dollar(this.amount * multiplier);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### ❌ 不要一次實作多個測試
|
||||
|
||||
```javascript
|
||||
// 目前只有一個測試需要 times()
|
||||
// 不要順便實作 add(), subtract()
|
||||
// 等有測試需要時再加
|
||||
```
|
||||
|
||||
### ❌ 不要重構
|
||||
|
||||
```javascript
|
||||
// ❌ Green 階段不要重構
|
||||
// 先讓測試通過
|
||||
// 重構留給下一步
|
||||
|
||||
// ✅ 先這樣
|
||||
function calculate(x) {
|
||||
return x * 2;
|
||||
}
|
||||
|
||||
// 不要在這裡就改成
|
||||
const MULTIPLIER = 2;
|
||||
function calculate(x) {
|
||||
return x * MULTIPLIER;
|
||||
}
|
||||
// 重構留給 /kb-refactor
|
||||
```
|
||||
|
||||
## 心態:接受"醜陋"的程式碼
|
||||
|
||||
Kent Beck 強調的心態轉變:
|
||||
|
||||
```
|
||||
階段 1:讓它能動 (Make it work) ← 現在在這裡
|
||||
↓
|
||||
階段 2:讓它正確 (Make it right)
|
||||
↓
|
||||
階段 3:讓它快速 (Make it fast)
|
||||
```
|
||||
|
||||
**Green 階段只追求"能動"**
|
||||
- 硬編碼?沒關係
|
||||
- 重複?沒關係
|
||||
- 醜陋?沒關係
|
||||
|
||||
**下一步會改善**
|
||||
|
||||
## 範例完整流程
|
||||
|
||||
### 測試(失敗)
|
||||
|
||||
```javascript
|
||||
test('購物車初始總價為 0', () => {
|
||||
const cart = new ShoppingCart();
|
||||
expect(cart.getTotal()).toBe(0);
|
||||
});
|
||||
```
|
||||
|
||||
### 實作策略選擇
|
||||
|
||||
**信心評估**:這個很簡單,用 Obvious Implementation
|
||||
|
||||
### 實作程式碼
|
||||
|
||||
```javascript
|
||||
// src/shopping-cart.js
|
||||
|
||||
class ShoppingCart {
|
||||
getTotal() {
|
||||
return 0; // 最簡單的實作
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = ShoppingCart;
|
||||
```
|
||||
|
||||
### 執行測試
|
||||
|
||||
```bash
|
||||
npm test
|
||||
# ✅ 通過!
|
||||
```
|
||||
|
||||
### 記錄到 journey.md
|
||||
|
||||
```markdown
|
||||
#### 🟢 Green - 讓測試通過
|
||||
|
||||
策略:Obvious Implementation
|
||||
|
||||
實作說明:
|
||||
建立 ShoppingCart 類別,getTotal() 回傳 0。
|
||||
雖然是硬編碼,但足以通過目前的測試。
|
||||
|
||||
程式碼位置:src/shopping-cart.js
|
||||
|
||||
測試結果:✅ 通過
|
||||
|
||||
#### 🤔 反思
|
||||
- 實作很簡單,是硬編碼
|
||||
- 下一個測試:加入商品後總價應該改變
|
||||
- 目前沒有明顯重複
|
||||
|
||||
#### 📝 下一步
|
||||
執行 /kb-refactor 檢查是否需要重構
|
||||
```
|
||||
|
||||
## 速度的重要性
|
||||
|
||||
Kent Beck:**Green 階段要快**
|
||||
|
||||
**目標**:
|
||||
- 幾秒鐘到幾分鐘
|
||||
- 不要花太久時間
|
||||
- 快速得到綠燈的心理回饋
|
||||
|
||||
**如果卡住**:
|
||||
- 寫更小的測試
|
||||
- 用更假的實作
|
||||
- 回退重來
|
||||
|
||||
## 下一步
|
||||
|
||||
測試通過了?執行:
|
||||
```
|
||||
/kb-refactor
|
||||
```
|
||||
|
||||
檢查是否有重複,進行重構!
|
||||
|
||||
## 記住 Kent Beck 的話
|
||||
|
||||
> "讓測試通過的技巧是暫時降低對程式碼品質的標準。"
|
||||
> "骯髒的程式碼是通往乾淨程式碼的墊腳石。"
|
||||
> "當你看到綠燈時,那是重構的信號。"
|
||||
274
commands/kb-red.md
Normal file
274
commands/kb-red.md
Normal file
@@ -0,0 +1,274 @@
|
||||
---
|
||||
description: 寫下一個測試(Red 階段)。一次只寫一個最小的測試,讓它失敗。
|
||||
---
|
||||
|
||||
# TDD Red - 寫下一個測試(Red)
|
||||
|
||||
寫下一個小測試,看它失敗。
|
||||
|
||||
**【功能名】**:{{feature_name}}
|
||||
|
||||
## Kent Beck 的 Red 階段理念
|
||||
|
||||
> "寫一個小測試。讓它失敗。"
|
||||
|
||||
### 一次一個測試的原則
|
||||
|
||||
**為什麼一次只寫一個?**
|
||||
- 保持專注
|
||||
- 快速反饋
|
||||
- 小步前進
|
||||
- 容易回退
|
||||
|
||||
### 如何選擇下一個測試?
|
||||
|
||||
Kent Beck 的建議:
|
||||
|
||||
1. **從 To-Do List 選**(心理 To-Do,不是文件)
|
||||
- 腦中想到什麼測試,記下來
|
||||
- 選最簡單的開始
|
||||
|
||||
2. **從上一個測試的經驗**
|
||||
- 上一個測試通過了,下一步自然浮現
|
||||
- "如果這樣,那會怎樣?"
|
||||
|
||||
3. **三角測量**
|
||||
- 如果不確定實作,寫第二個類似的測試
|
||||
- 從多個角度逼近正確實作
|
||||
|
||||
## 寫測試的步驟
|
||||
|
||||
### 1. 更新 journey.md
|
||||
|
||||
```markdown
|
||||
### 第 N 輪 - {日期時間}
|
||||
|
||||
#### 🤔 想法
|
||||
{為什麼要寫這個測試?從上一輪學到什麼?}
|
||||
|
||||
#### 🔴 Red - 寫測試
|
||||
測試名稱:{測試名稱}
|
||||
```
|
||||
|
||||
### 2. 寫測試程式碼
|
||||
|
||||
**測試結構:Given-When-Then**
|
||||
|
||||
```javascript
|
||||
test('簡短描述測試意圖', () => {
|
||||
// Given - 準備
|
||||
// 【準備】:{為什麼需要這些資料?}
|
||||
const input = testData;
|
||||
|
||||
// When - 執行
|
||||
// 【執行】:{測試什麼行為?}
|
||||
const result = functionUnderTest(input);
|
||||
|
||||
// Then - 驗證
|
||||
// 【驗證】:{為什麼期待這個結果?}
|
||||
expect(result).toBe(expected);
|
||||
});
|
||||
```
|
||||
|
||||
### 3. 執行測試,確認失敗
|
||||
|
||||
```bash
|
||||
npm test
|
||||
```
|
||||
|
||||
**必須看到失敗!**
|
||||
- 如果沒失敗,測試可能有問題
|
||||
- 失敗訊息要清楚
|
||||
|
||||
### 4. 記錄失敗訊息
|
||||
|
||||
在 journey.md 中:
|
||||
```markdown
|
||||
#### 失敗訊息
|
||||
```
|
||||
{實際的錯誤訊息}
|
||||
```
|
||||
|
||||
#### 📝 下一步
|
||||
執行 /kb-green 讓測試通過
|
||||
```
|
||||
|
||||
## 測試大小的藝術
|
||||
|
||||
### 測試要多小?
|
||||
|
||||
Kent Beck 的答案:**取決於你的信心**
|
||||
|
||||
**信心低(不確定)**:
|
||||
- 寫更小的測試
|
||||
- 更頻繁的反饋
|
||||
|
||||
**信心高(很確定)**:
|
||||
- 可以寫大一點的測試
|
||||
- 跨越明顯的步驟
|
||||
|
||||
### 範例:小測試 vs 大測試
|
||||
|
||||
**小測試(信心低時)**:
|
||||
```javascript
|
||||
// 第 1 個測試:最基本的
|
||||
test('5 元乘以 2 等於 10 元', () => {
|
||||
const five = new Dollar(5);
|
||||
const product = five.times(2);
|
||||
expect(product.amount).toBe(10);
|
||||
});
|
||||
|
||||
// 第 2 個測試:檢查副作用
|
||||
test('乘法不改變原物件', () => {
|
||||
const five = new Dollar(5);
|
||||
five.times(2);
|
||||
expect(five.amount).toBe(5); // 原值不變
|
||||
});
|
||||
```
|
||||
|
||||
**大測試(信心高時)**:
|
||||
```javascript
|
||||
// 直接測試完整行為
|
||||
test('Dollar 乘法運算', () => {
|
||||
const five = new Dollar(5);
|
||||
const ten = five.times(2);
|
||||
expect(ten.amount).toBe(10);
|
||||
expect(five.amount).toBe(5); // 也檢查副作用
|
||||
});
|
||||
```
|
||||
|
||||
## 三角測量的時機
|
||||
|
||||
當你不確定實作時,用三角測量:
|
||||
|
||||
```javascript
|
||||
// 第 1 個測試
|
||||
test('1 + 1 = 2', () => {
|
||||
expect(add(1, 1)).toBe(2);
|
||||
});
|
||||
|
||||
// 最簡單的假實作
|
||||
function add(a, b) {
|
||||
return 2; // 硬編碼
|
||||
}
|
||||
|
||||
// 第 2 個測試(三角測量)
|
||||
test('2 + 3 = 5', () => {
|
||||
expect(add(2, 3)).toBe(5);
|
||||
});
|
||||
|
||||
// 現在必須寫真正的實作
|
||||
function add(a, b) {
|
||||
return a + b;
|
||||
}
|
||||
```
|
||||
|
||||
## To-Do List 技巧
|
||||
|
||||
Kent Beck 建議在測試程式碼中寫註解:
|
||||
|
||||
```javascript
|
||||
describe('Dollar', () => {
|
||||
// TODO: 測試加法
|
||||
// TODO: 測試負數
|
||||
// TODO: 測試相等性
|
||||
|
||||
test('乘法', () => {
|
||||
// 目前的測試
|
||||
});
|
||||
});
|
||||
```
|
||||
|
||||
或在 journey.md 中:
|
||||
|
||||
```markdown
|
||||
## 心理 To-Do List
|
||||
- [x] 基本乘法
|
||||
- [ ] 乘法副作用
|
||||
- [ ] 加法運算
|
||||
- [ ] 負數處理
|
||||
```
|
||||
|
||||
## 常見問題
|
||||
|
||||
### Q: 要不要測試所有邊界情況?
|
||||
|
||||
Kent Beck:**不用一開始就全測**
|
||||
- 先從明顯的案例開始
|
||||
- 邊界情況在需要時再加入
|
||||
- 讓測試自然演進
|
||||
|
||||
### Q: 測試名稱要多詳細?
|
||||
|
||||
Kent Beck:**描述意圖,不是實作**
|
||||
```javascript
|
||||
// ✅ 好的測試名稱
|
||||
test('購物車加入商品後,總價增加')
|
||||
|
||||
// ❌ 不好的測試名稱
|
||||
test('testAddItem')
|
||||
```
|
||||
|
||||
### Q: 要寫測試到什麼程度?
|
||||
|
||||
Kent Beck:**寫到你有信心為止**
|
||||
- 如果還不確定,再寫一個測試
|
||||
- 如果已經確定,可以跳到實作
|
||||
|
||||
## 範例:Money 第一個測試
|
||||
|
||||
```javascript
|
||||
// __tests__/money.test.js
|
||||
|
||||
describe('Dollar', () => {
|
||||
test('乘法運算', () => {
|
||||
// 【準備】:建立 5 元的 Dollar 物件
|
||||
const five = new Dollar(5);
|
||||
|
||||
// 【執行】:乘以 2
|
||||
const product = five.times(2);
|
||||
|
||||
// 【驗證】:結果應該是 10 元
|
||||
expect(product.amount).toBe(10);
|
||||
});
|
||||
});
|
||||
```
|
||||
|
||||
執行測試:
|
||||
```bash
|
||||
npm test
|
||||
|
||||
# 結果:失敗!
|
||||
# Error: Dollar is not defined
|
||||
```
|
||||
|
||||
在 journey.md 記錄:
|
||||
```markdown
|
||||
### 第 1 輪
|
||||
|
||||
#### 🔴 Red - 寫測試
|
||||
測試名稱:Dollar 乘法運算
|
||||
|
||||
#### 失敗訊息
|
||||
```
|
||||
Error: Dollar is not defined
|
||||
```
|
||||
|
||||
#### 📝 下一步
|
||||
執行 /kb-green 建立 Dollar 類別
|
||||
```
|
||||
|
||||
## 下一步
|
||||
|
||||
測試寫好且失敗了?執行:
|
||||
```
|
||||
/kb-green
|
||||
```
|
||||
|
||||
用最簡單的方式讓測試通過!
|
||||
|
||||
## 記住
|
||||
|
||||
> "測試不是目的,而是思考的工具。"
|
||||
> "失敗的測試是進步的開始。"
|
||||
> "一次一小步,但要持續前進。"
|
||||
473
commands/kb-refactor.md
Normal file
473
commands/kb-refactor.md
Normal file
@@ -0,0 +1,473 @@
|
||||
---
|
||||
description: 重構消除重複(Refactor 階段)。在綠燈的保護下,改善程式碼品質。
|
||||
---
|
||||
|
||||
# TDD Refactor - 重構消除重複(Refactor)
|
||||
|
||||
在測試通過後,消除重複、改善設計。測試是你的安全網。
|
||||
|
||||
**【功能名】**:{{feature_name}}
|
||||
|
||||
## Kent Beck 的 Refactor 理念
|
||||
|
||||
> "重構的核心是消除重複。"
|
||||
> "測試通過後,就是重構的時機。"
|
||||
|
||||
### 什麼是重複?
|
||||
|
||||
Kent Beck 定義的重複包括:
|
||||
|
||||
#### 1. 明顯的程式碼重複
|
||||
|
||||
```javascript
|
||||
// ❌ 重複
|
||||
function processOrderA(order) {
|
||||
if (!order) throw new Error('Invalid order');
|
||||
return order.total * 1.1;
|
||||
}
|
||||
|
||||
function processOrderB(order) {
|
||||
if (!order) throw new Error('Invalid order');
|
||||
return order.total * 0.9;
|
||||
}
|
||||
|
||||
// ✅ 消除重複
|
||||
function validateOrder(order) {
|
||||
if (!order) throw new Error('Invalid order');
|
||||
}
|
||||
|
||||
function processOrderA(order) {
|
||||
validateOrder(order);
|
||||
return order.total * 1.1;
|
||||
}
|
||||
```
|
||||
|
||||
#### 2. 測試和實作之間的重複
|
||||
|
||||
```javascript
|
||||
// 測試
|
||||
test('5 * 2 = 10', () => {
|
||||
expect(multiply(5, 2)).toBe(10);
|
||||
});
|
||||
|
||||
// ❌ 實作中的重複(硬編碼)
|
||||
function multiply(a, b) {
|
||||
return 10; // 和測試中的 10 重複!
|
||||
}
|
||||
|
||||
// ✅ 消除重複(真正的實作)
|
||||
function multiply(a, b) {
|
||||
return a * b;
|
||||
}
|
||||
```
|
||||
|
||||
#### 3. 概念上的重複
|
||||
|
||||
```javascript
|
||||
// ❌ 概念重複(魔術數字)
|
||||
function applyDiscount(price) {
|
||||
return price * 0.9;
|
||||
}
|
||||
|
||||
function calculateTax(price) {
|
||||
return price * 0.1;
|
||||
}
|
||||
|
||||
// ✅ 消除重複(提取常數)
|
||||
const DISCOUNT_RATE = 0.9;
|
||||
const TAX_RATE = 0.1;
|
||||
|
||||
function applyDiscount(price) {
|
||||
return price * DISCOUNT_RATE;
|
||||
}
|
||||
|
||||
function calculateTax(price) {
|
||||
return price * TAX_RATE;
|
||||
}
|
||||
```
|
||||
|
||||
## 重構步驟
|
||||
|
||||
### 1. 確認測試通過
|
||||
|
||||
```bash
|
||||
npm test
|
||||
# 必須是綠燈!🟢
|
||||
```
|
||||
|
||||
**如果是紅燈**:
|
||||
- 不要重構
|
||||
- 先讓測試通過
|
||||
|
||||
### 2. 識別重複或異味
|
||||
|
||||
問自己:
|
||||
- 有重複的程式碼嗎?
|
||||
- 有硬編碼的值嗎?
|
||||
- 有不清楚的命名嗎?
|
||||
- 有太長的函式嗎?
|
||||
- 有不必要的複雜度嗎?
|
||||
|
||||
### 3. 小步重構
|
||||
|
||||
**一次只改一個地方**
|
||||
|
||||
```
|
||||
改一小步 → 執行測試 → 通過
|
||||
↓
|
||||
改下一步 → 執行測試 → 通過
|
||||
↓
|
||||
...
|
||||
```
|
||||
|
||||
### 4. 每次都執行測試
|
||||
|
||||
**頻率**:每改一個地方就測試一次
|
||||
|
||||
```bash
|
||||
# 改善命名
|
||||
npm test
|
||||
|
||||
# 提取函式
|
||||
npm test
|
||||
|
||||
# 提取常數
|
||||
npm test
|
||||
```
|
||||
|
||||
### 5. 如果測試失敗,立即回退
|
||||
|
||||
```bash
|
||||
# 紅燈!
|
||||
git checkout . # 回退
|
||||
# 或手動 Undo
|
||||
```
|
||||
|
||||
## 常見重構技巧
|
||||
|
||||
### 技巧 1:消除硬編碼
|
||||
|
||||
**Before**
|
||||
```javascript
|
||||
class Dollar {
|
||||
times(multiplier) {
|
||||
return new Dollar(10); // 硬編碼
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
**After**
|
||||
```javascript
|
||||
class Dollar {
|
||||
times(multiplier) {
|
||||
return new Dollar(this.amount * multiplier);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### 技巧 2:提取函式
|
||||
|
||||
**Before**
|
||||
```javascript
|
||||
function processOrder(order) {
|
||||
// 驗證
|
||||
if (!order.items || order.items.length === 0) {
|
||||
throw new Error('No items');
|
||||
}
|
||||
|
||||
// 計算
|
||||
let total = 0;
|
||||
for (const item of order.items) {
|
||||
total += item.price * item.quantity;
|
||||
}
|
||||
|
||||
return total;
|
||||
}
|
||||
```
|
||||
|
||||
**After**
|
||||
```javascript
|
||||
function processOrder(order) {
|
||||
validateOrder(order);
|
||||
return calculateTotal(order.items);
|
||||
}
|
||||
|
||||
function validateOrder(order) {
|
||||
if (!order.items || order.items.length === 0) {
|
||||
throw new Error('No items');
|
||||
}
|
||||
}
|
||||
|
||||
function calculateTotal(items) {
|
||||
let total = 0;
|
||||
for (const item of items) {
|
||||
total += item.price * item.quantity;
|
||||
}
|
||||
return total;
|
||||
}
|
||||
```
|
||||
|
||||
### 技巧 3:提取常數
|
||||
|
||||
**Before**
|
||||
```javascript
|
||||
function calculateShipping(weight) {
|
||||
if (weight > 1000) return 100;
|
||||
return 50;
|
||||
}
|
||||
```
|
||||
|
||||
**After**
|
||||
```javascript
|
||||
const HEAVY_WEIGHT_THRESHOLD = 1000;
|
||||
const SHIPPING_HEAVY = 100;
|
||||
const SHIPPING_STANDARD = 50;
|
||||
|
||||
function calculateShipping(weight) {
|
||||
if (weight > HEAVY_WEIGHT_THRESHOLD) {
|
||||
return SHIPPING_HEAVY;
|
||||
}
|
||||
return SHIPPING_STANDARD;
|
||||
}
|
||||
```
|
||||
|
||||
### 技巧 4:改善命名
|
||||
|
||||
**Before**
|
||||
```javascript
|
||||
function calc(x) {
|
||||
return x * 0.9;
|
||||
}
|
||||
```
|
||||
|
||||
**After**
|
||||
```javascript
|
||||
function applyDiscount(price) {
|
||||
const DISCOUNT_RATE = 0.9;
|
||||
return price * DISCOUNT_RATE;
|
||||
}
|
||||
```
|
||||
|
||||
### 技巧 5:簡化條件
|
||||
|
||||
**Before**
|
||||
```javascript
|
||||
function canPurchase(user, product) {
|
||||
if (user.age >= 18) {
|
||||
if (user.balance >= product.price) {
|
||||
if (product.stock > 0) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
```
|
||||
|
||||
**After**
|
||||
```javascript
|
||||
function canPurchase(user, product) {
|
||||
return user.age >= 18
|
||||
&& user.balance >= product.price
|
||||
&& product.stock > 0;
|
||||
}
|
||||
```
|
||||
|
||||
## 何時停止重構?
|
||||
|
||||
Kent Beck 的判斷標準:
|
||||
|
||||
### 停止的信號
|
||||
|
||||
✅ **可以停止了**:
|
||||
- 沒有明顯的重複
|
||||
- 程式碼清楚易懂
|
||||
- 命名恰當
|
||||
- 函式簡短
|
||||
|
||||
✅ **也可以停止**:
|
||||
- 想不到明顯的改善
|
||||
- 繼續重構的收益不大
|
||||
- 想寫下一個測試了
|
||||
|
||||
### 不要過度重構
|
||||
|
||||
```javascript
|
||||
// ✅ 夠好了
|
||||
function calculateTotal(items) {
|
||||
let total = 0;
|
||||
for (const item of items) {
|
||||
total += item.price * item.quantity;
|
||||
}
|
||||
return total;
|
||||
}
|
||||
|
||||
// ❌ 過度了(除非真的需要)
|
||||
class TotalCalculator {
|
||||
constructor(strategy) {
|
||||
this.strategy = strategy;
|
||||
}
|
||||
|
||||
calculate(items) {
|
||||
return this.strategy.compute(items);
|
||||
}
|
||||
}
|
||||
|
||||
class StandardCalculationStrategy {
|
||||
compute(items) {
|
||||
return items.reduce((sum, item) =>
|
||||
sum + this.calculateItemTotal(item), 0);
|
||||
}
|
||||
|
||||
calculateItemTotal(item) {
|
||||
return new Money(item.price)
|
||||
.multiply(item.quantity)
|
||||
.getAmount();
|
||||
}
|
||||
}
|
||||
// ... 太複雜了!
|
||||
```
|
||||
|
||||
## 重構的節奏
|
||||
|
||||
Kent Beck 建議的節奏:
|
||||
|
||||
```
|
||||
快速寫測試(幾分鐘)
|
||||
↓
|
||||
快速讓測試通過(幾分鐘)
|
||||
↓
|
||||
快速重構(幾分鐘)← 現在在這裡
|
||||
↓
|
||||
回到寫測試
|
||||
```
|
||||
|
||||
**每個階段都要快**
|
||||
- 不要花太久在重構
|
||||
- 幾分鐘就好
|
||||
- 保持節奏
|
||||
|
||||
## 更新 journey.md
|
||||
|
||||
```markdown
|
||||
#### 🔵 Refactor - 重構
|
||||
|
||||
重構內容:
|
||||
1. {改善項目 1}
|
||||
- 重構前:{簡述}
|
||||
- 重構後:{簡述}
|
||||
- 原因:{為什麼}
|
||||
|
||||
2. {改善項目 2}
|
||||
...
|
||||
|
||||
測試狀態:✅ 持續通過
|
||||
|
||||
#### 📝 下一步
|
||||
執行 /kb-review 回顧下一步
|
||||
```
|
||||
|
||||
## 範例:Money 的重構
|
||||
|
||||
### 重構前
|
||||
|
||||
```javascript
|
||||
class Dollar {
|
||||
constructor(amount) {
|
||||
this.amount = amount;
|
||||
}
|
||||
|
||||
times(multiplier) {
|
||||
return new Dollar(this.amount * multiplier);
|
||||
}
|
||||
}
|
||||
|
||||
class Franc {
|
||||
constructor(amount) {
|
||||
this.amount = amount;
|
||||
}
|
||||
|
||||
times(multiplier) {
|
||||
return new Franc(this.amount * multiplier);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
**發現重複**:Dollar 和 Franc 幾乎一樣!
|
||||
|
||||
### 重構步驟
|
||||
|
||||
**Step 1:提取父類別**
|
||||
```javascript
|
||||
class Money {
|
||||
constructor(amount) {
|
||||
this.amount = amount;
|
||||
}
|
||||
}
|
||||
|
||||
class Dollar extends Money {
|
||||
times(multiplier) {
|
||||
return new Dollar(this.amount * multiplier);
|
||||
}
|
||||
}
|
||||
|
||||
class Franc extends Money {
|
||||
times(multiplier) {
|
||||
return new Franc(this.amount * multiplier);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
測試:✅ 通過
|
||||
|
||||
**Step 2:將 times 提升到父類別**
|
||||
```javascript
|
||||
class Money {
|
||||
constructor(amount) {
|
||||
this.amount = amount;
|
||||
}
|
||||
|
||||
times(multiplier) {
|
||||
return new Money(this.amount * multiplier);
|
||||
}
|
||||
}
|
||||
|
||||
class Dollar extends Money {}
|
||||
class Franc extends Money {}
|
||||
```
|
||||
|
||||
測試:✅ 通過
|
||||
|
||||
**消除了重複!**
|
||||
|
||||
## 重構的勇氣來自哪裡?
|
||||
|
||||
Kent Beck:**測試給你重構的勇氣**
|
||||
|
||||
```
|
||||
沒有測試:
|
||||
😰 不敢改程式碼
|
||||
😰 怕改壞東西
|
||||
😰 程式碼越來越爛
|
||||
|
||||
有測試:
|
||||
😎 隨時可以重構
|
||||
😎 測試會告訴你有沒有改壞
|
||||
😎 程式碼越來越好
|
||||
```
|
||||
|
||||
## 下一步
|
||||
|
||||
重構完成?執行:
|
||||
```
|
||||
/kb-review
|
||||
```
|
||||
|
||||
回顧這一輪,計劃下一步!
|
||||
|
||||
## 記住 Kent Beck 的話
|
||||
|
||||
> "重構的時機是在測試通過之後。"
|
||||
> "重複是設計的敵人。"
|
||||
> "小步重構,頻繁測試,永遠保持綠燈。"
|
||||
> "測試讓你有勇氣重構,重構讓程式碼保持清潔。"
|
||||
359
commands/kb-review.md
Normal file
359
commands/kb-review.md
Normal file
@@ -0,0 +1,359 @@
|
||||
---
|
||||
description: 回顧這一輪 TDD,決定下一步。遵循 Kent Beck 的持續演進理念。
|
||||
---
|
||||
|
||||
# TDD Review - 回顧與規劃下一步
|
||||
|
||||
完成一輪 Red-Green-Refactor 後,停下來思考。
|
||||
|
||||
**【功能名】**:{{feature_name}}
|
||||
|
||||
## Kent Beck 的回顧理念
|
||||
|
||||
> "每完成一個測試,都要問自己:我學到了什麼?"
|
||||
> "下一個測試不是計劃出來的,而是從經驗中浮現的。"
|
||||
|
||||
## 回顧步驟
|
||||
|
||||
### 1. 回顧這一輪
|
||||
|
||||
在 journey.md 中更新:
|
||||
|
||||
```markdown
|
||||
### 第 N 輪完成 - {日期時間}
|
||||
|
||||
#### 💭 回顧與學習
|
||||
|
||||
**這一輪做了什麼**
|
||||
{簡述這輪的測試和實作}
|
||||
|
||||
**學到了什麼**
|
||||
- {發現 1}
|
||||
- {發現 2}
|
||||
- {驚喜或意外}
|
||||
|
||||
**設計如何演進**
|
||||
{設計有什麼改變?}
|
||||
|
||||
**目前的進度感覺**
|
||||
{覺得進度如何?順利還是卡住?}
|
||||
```
|
||||
|
||||
### 2. 回顧問題清單
|
||||
|
||||
Kent Beck 建議問自己:
|
||||
|
||||
#### 關於測試
|
||||
- ✅ 這個測試真的需要嗎?
|
||||
- ✅ 測試太大了嗎?
|
||||
- ✅ 測試太小了嗎?
|
||||
- ✅ 測試名稱清楚嗎?
|
||||
|
||||
#### 關於實作
|
||||
- ✅ 實作還是假的嗎?
|
||||
- ✅ 需要三角測量嗎?
|
||||
- ✅ 有沒有過度設計?
|
||||
- ✅ 程式碼清楚嗎?
|
||||
|
||||
#### 關於重構
|
||||
- ✅ 還有重複嗎?
|
||||
- ✅ 命名恰當嗎?
|
||||
- ✅ 結構清晰嗎?
|
||||
- ✅ 需要更多重構嗎?
|
||||
|
||||
#### 關於節奏
|
||||
- ✅ 步伐太大還是太小?
|
||||
- ✅ 速度合適嗎?
|
||||
- ✅ 感覺順暢嗎?
|
||||
|
||||
### 3. 決定下一步
|
||||
|
||||
Kent Beck:有三個選擇
|
||||
|
||||
#### 選項 1:繼續測試(最常見)
|
||||
|
||||
**何時選擇**:
|
||||
- 還有明顯的功能未實作
|
||||
- 想到下一個測試案例
|
||||
- 設計還在演進中
|
||||
|
||||
**下一步**:
|
||||
```
|
||||
/kb-red
|
||||
```
|
||||
|
||||
**在 journey.md 記錄**:
|
||||
```markdown
|
||||
#### 🎯 下一步計劃
|
||||
|
||||
**下一個測試想法**
|
||||
{為什麼要寫這個測試?}
|
||||
|
||||
**預期會發生什麼**
|
||||
{這個測試會逼出什麼實作?}
|
||||
|
||||
執行 /kb-red
|
||||
```
|
||||
|
||||
#### 選項 2:暫停休息
|
||||
|
||||
**何時選擇**:
|
||||
- 累了
|
||||
- 卡住了
|
||||
- 需要思考
|
||||
|
||||
**暫停建議**:
|
||||
- 寫下目前的想法
|
||||
- 記錄卡住的地方
|
||||
- 休息一下再回來
|
||||
|
||||
**在 journey.md 記錄**:
|
||||
```markdown
|
||||
#### ⏸️ 暫停點
|
||||
|
||||
**目前狀態**
|
||||
{進行到哪裡了}
|
||||
|
||||
**下次回來要做什麼**
|
||||
{下一步的提示}
|
||||
|
||||
**卡住的問題**(如果有)
|
||||
{什麼地方不確定}
|
||||
```
|
||||
|
||||
#### 選項 3:結束這個會話
|
||||
|
||||
**何時選擇**:
|
||||
- 目前的目標達成了
|
||||
- 找到自然的停止點
|
||||
- 功能暫時夠用了
|
||||
|
||||
**在 journey.md 記錄**:
|
||||
```markdown
|
||||
#### 🎉 會話結束
|
||||
|
||||
**達成的成果**
|
||||
{列出完成的功能}
|
||||
|
||||
**目前的狀態**
|
||||
- 測試數量:{數量}
|
||||
- 測試通過率:100%
|
||||
- 程式碼品質:{評估}
|
||||
|
||||
**未來可能的方向**
|
||||
- {可能的擴展 1}
|
||||
- {可能的擴展 2}
|
||||
```
|
||||
|
||||
## 心理 To-Do List
|
||||
|
||||
Kent Beck 建議維護一個簡單的 To-Do List:
|
||||
|
||||
### 在 journey.md 中
|
||||
|
||||
```markdown
|
||||
## 心理 To-Do List
|
||||
|
||||
### 已完成
|
||||
- [x] 基本乘法運算
|
||||
- [x] 處理負數
|
||||
|
||||
### 下一步可能的測試
|
||||
- [ ] 加法運算
|
||||
- [ ] 相等性比較
|
||||
- [ ] 不同幣別
|
||||
|
||||
### 設計想法
|
||||
- [ ] 考慮提取 Money 父類別
|
||||
- [ ] 可能需要 Currency 概念
|
||||
```
|
||||
|
||||
**特點**:
|
||||
- 非正式的
|
||||
- 隨時可以改
|
||||
- 不是承諾
|
||||
- 只是提醒
|
||||
|
||||
## 調整步伐
|
||||
|
||||
### 如果進度太慢
|
||||
|
||||
Kent Beck:**加大步伐**
|
||||
- 寫更大的測試
|
||||
- 用 Obvious Implementation
|
||||
- 跳過明顯的步驟
|
||||
|
||||
### 如果感到不安
|
||||
|
||||
Kent Beck:**縮小步伐**
|
||||
- 寫更小的測試
|
||||
- 用 Fake It
|
||||
- 三角測量
|
||||
- 更頻繁測試
|
||||
|
||||
## 範例反思記錄
|
||||
|
||||
### 順利的情況
|
||||
|
||||
```markdown
|
||||
### 第 3 輪完成 - 2025-10-15 22:00
|
||||
|
||||
#### 💭 回顧與學習
|
||||
|
||||
**這一輪做了什麼**
|
||||
測試了負數乘法,確認 Dollar(-5).times(2) 得到 -10。
|
||||
|
||||
**學到了什麼**
|
||||
- 現有實作自動支援負數
|
||||
- 不需要特殊處理
|
||||
- 乘法的數學性質自然延伸
|
||||
|
||||
**設計如何演進**
|
||||
沒有改變,驗證了設計的穩健性。
|
||||
|
||||
**目前的進度感覺**
|
||||
很順利!基本的乘法功能已經穩定。
|
||||
|
||||
#### 🎯 下一步計劃
|
||||
|
||||
**下一個測試想法**
|
||||
測試加法:Dollar(5).plus(Dollar(3)) 應該等於 Dollar(8)
|
||||
|
||||
**預期會發生什麼**
|
||||
需要新增 plus 方法,可能會很直接。
|
||||
|
||||
執行 /kb-red
|
||||
```
|
||||
|
||||
### 卡住的情況
|
||||
|
||||
```markdown
|
||||
### 第 5 輪完成 - 2025-10-15 22:30
|
||||
|
||||
#### 💭 回顧與學習
|
||||
|
||||
**這一輪做了什麼**
|
||||
嘗試實作不同幣別的比較。
|
||||
|
||||
**學到了什麼**
|
||||
- 設計變複雜了
|
||||
- 不確定該怎麼處理幣別
|
||||
- 可能需要重新思考
|
||||
|
||||
**設計如何演進**
|
||||
引入了 Currency 概念,但感覺有點勉強。
|
||||
|
||||
**目前的進度感覺**
|
||||
有點卡住。可能步伐太大了。
|
||||
|
||||
#### ⏸️ 暫停點
|
||||
|
||||
**目前狀態**
|
||||
Dollar 和 Franc 可以獨立運作,但比較功能還不清楚。
|
||||
|
||||
**下次回來要做什麼**
|
||||
考慮回退到更簡單的設計,或者寫更小的測試。
|
||||
|
||||
**卡住的問題**
|
||||
- 幣別應該是字串還是物件?
|
||||
- 需要匯率轉換嗎?
|
||||
- 設計是否過度複雜?
|
||||
|
||||
可能需要回到更小的步伐,寫一個更簡單的測試。
|
||||
```
|
||||
|
||||
## 長期視角
|
||||
|
||||
### Kent Beck 的"完成"定義
|
||||
|
||||
TDD **沒有**真正的完成:
|
||||
- 軟體是演進的
|
||||
- 永遠可以加新測試
|
||||
- 永遠可以改善設計
|
||||
- 永遠可以學到新東西
|
||||
|
||||
### 知道何時停止
|
||||
|
||||
**可以暫時停止的信號**:
|
||||
- 目前的需求滿足了
|
||||
- 設計達到一個穩定點
|
||||
- 沒有明顯的下一步
|
||||
- 想要轉向其他功能
|
||||
|
||||
**不是因為**:
|
||||
- "測試寫夠了"
|
||||
- "覆蓋率達標了"
|
||||
- "文件說的都做完了"
|
||||
|
||||
## 節奏意識
|
||||
|
||||
Kent Beck 強調 TDD 的節奏:
|
||||
|
||||
```
|
||||
🔴 Red → 緊張(寫測試)
|
||||
🟢 Green → 放鬆(通過了!)
|
||||
🔵 Refactor → 改善(重構)
|
||||
💭 Review → 回顧(現在)
|
||||
```
|
||||
|
||||
**好的節奏**:
|
||||
- 快速循環(幾分鐘)
|
||||
- 頻繁的綠燈
|
||||
- 定期的反思
|
||||
|
||||
**不好的節奏**:
|
||||
- 卡在紅燈太久
|
||||
- 測試寫太大
|
||||
- 實作太複雜
|
||||
- 忘記重構
|
||||
- 沒有回顧
|
||||
|
||||
## 下一步選擇
|
||||
|
||||
### 繼續前進
|
||||
|
||||
```bash
|
||||
/kb-red
|
||||
```
|
||||
|
||||
### 查看旅程記錄
|
||||
|
||||
```bash
|
||||
cat docs/tdd/{feature_name}/journey.md
|
||||
```
|
||||
|
||||
### 執行所有測試
|
||||
|
||||
```bash
|
||||
npm test
|
||||
```
|
||||
|
||||
## 記住 Kent Beck 的話
|
||||
|
||||
> "TDD 不是一個目的地,而是一個旅程。"
|
||||
> "每個測試都教會你一些東西。"
|
||||
> "下一步不是計劃出來的,而是從經驗中浮現的。"
|
||||
> "當你不知道下一步時,寫一個你知道答案的測試。"
|
||||
> "信任這個過程,設計會自然演進。"
|
||||
|
||||
---
|
||||
|
||||
## 完整循環
|
||||
|
||||
你已經完成一輪完整的 TDD 循環!
|
||||
|
||||
```
|
||||
/kb-start ← 從這裡開始
|
||||
↓
|
||||
/kb-red ← 寫測試 (Red)
|
||||
↓
|
||||
/kb-green ← 實作 (Green)
|
||||
↓
|
||||
/kb-refactor ← 重構 (Refactor)
|
||||
↓
|
||||
/kb-review ← 回顧(你在這裡)
|
||||
↓
|
||||
循環回 /kb-red 或結束
|
||||
```
|
||||
|
||||
準備好下一輪了嗎? 🚀
|
||||
129
commands/kb-start.md
Normal file
129
commands/kb-start.md
Normal file
@@ -0,0 +1,129 @@
|
||||
---
|
||||
description: 啟動新的 TDD 會話。遵循 Kent Beck 的純粹 TDD 理念,從第一個測試開始。
|
||||
---
|
||||
|
||||
# TDD Start - 啟動 TDD 會話
|
||||
|
||||
開始一個新的 TDD 開發會話。遵循 Kent Beck 的理念:從測試開始,讓設計演進。
|
||||
|
||||
**【功能想法】**:{{brief_idea}}
|
||||
|
||||
## Kent Beck 的起點
|
||||
|
||||
> "開始時不要想太多,從最簡單的測試開始。"
|
||||
|
||||
**不要做:**
|
||||
- ❌ 寫完整的需求文件
|
||||
- ❌ 規劃所有測試案例
|
||||
- ❌ 事先設計類別結構
|
||||
- ❌ 想太多細節
|
||||
|
||||
**要做:**
|
||||
- ✅ 想一個最簡單的情境
|
||||
- ✅ 寫出第一個測試
|
||||
- ✅ 讓測試驅動設計
|
||||
|
||||
## 開始步驟
|
||||
|
||||
### 1. 建立開發記錄
|
||||
|
||||
在 `docs/tdd/{feature_name}/` 建立:
|
||||
```
|
||||
journey.md - TDD 開發旅程記錄(不是計劃)
|
||||
```
|
||||
|
||||
### 2. 想第一個測試
|
||||
|
||||
問自己:
|
||||
- 什麼是最簡單的使用情境?
|
||||
- 什麼是最明顯的行為?
|
||||
- 從哪裡開始最容易?
|
||||
|
||||
**範例思考過程:**
|
||||
```
|
||||
功能想法:「計算購物車總價」
|
||||
|
||||
❌ 不要想:
|
||||
- 需要支援優惠券
|
||||
- 需要處理多幣別
|
||||
- 需要計算稅金
|
||||
- ...
|
||||
|
||||
✅ 只想:
|
||||
「一個商品的購物車,總價應該等於商品價格」
|
||||
|
||||
這就是第一個測試!
|
||||
```
|
||||
|
||||
### 3. journey.md 初始格式
|
||||
|
||||
```markdown
|
||||
# {feature_name} TDD Journey
|
||||
|
||||
## 開始想法
|
||||
{簡單描述要做什麼,1-2 句話}
|
||||
|
||||
## TDD 循環記錄
|
||||
|
||||
### 第 1 輪 - {日期時間}
|
||||
|
||||
#### 🤔 想法
|
||||
{為什麼要寫這個測試?}
|
||||
|
||||
#### 📝 下一步
|
||||
執行 /kb-red 寫第一個測試
|
||||
```
|
||||
|
||||
## 心態準備
|
||||
|
||||
Kent Beck 強調的 TDD 心態:
|
||||
|
||||
**好奇心**
|
||||
- "如果我這樣寫測試會怎樣?"
|
||||
- 用測試來探索問題
|
||||
|
||||
**勇氣**
|
||||
- 敢於寫會失敗的測試
|
||||
- 敢於用最簡單的方式實作
|
||||
|
||||
**簡單**
|
||||
- 從最簡單的開始
|
||||
- 不要過度設計
|
||||
|
||||
**反饋**
|
||||
- 頻繁執行測試
|
||||
- 快速得到反饋
|
||||
|
||||
## 範例:開始一個 Money 類別
|
||||
|
||||
```markdown
|
||||
# Money TDD Journey
|
||||
|
||||
## 開始想法
|
||||
建立一個 Money 類別,可以處理金錢運算。
|
||||
|
||||
## TDD 循環記錄
|
||||
|
||||
### 第 1 輪 - 2025-10-15 21:30
|
||||
|
||||
#### 🤔 想法
|
||||
最簡單的情境:5 元乘以 2 應該等於 10 元。
|
||||
|
||||
#### 📝 下一步
|
||||
執行 /kb-red 寫第一個測試
|
||||
```
|
||||
|
||||
## 下一步
|
||||
|
||||
準備好了嗎?執行:
|
||||
```
|
||||
/kb-red
|
||||
```
|
||||
|
||||
開始寫你的第一個測試!
|
||||
|
||||
## 記住 Kent Beck 的話
|
||||
|
||||
> "TDD 的目的不是測試,而是思考。"
|
||||
> "從最簡單的測試開始,讓複雜度自然演進。"
|
||||
> "如果不知道該寫什麼測試,那就寫一個你知道答案的測試。"
|
||||
61
plugin.lock.json
Normal file
61
plugin.lock.json
Normal file
@@ -0,0 +1,61 @@
|
||||
{
|
||||
"$schema": "internal://schemas/plugin.lock.v1.json",
|
||||
"pluginId": "gh:cashwu/claude-code-tdd-marketplace:plugins/kent-beck-tdd",
|
||||
"normalized": {
|
||||
"repo": null,
|
||||
"ref": "refs/tags/v20251128.0",
|
||||
"commit": "d87d435feabff2801b6588c5c30da50c827463e5",
|
||||
"treeHash": "7465967f0db91be17641bb12a329d7b3c5de8d78a821220c2871175c1466945c",
|
||||
"generatedAt": "2025-11-28T10:14:30.209144Z",
|
||||
"toolVersion": "publish_plugins.py@0.2.0"
|
||||
},
|
||||
"origin": {
|
||||
"remote": "git@github.com:zhongweili/42plugin-data.git",
|
||||
"branch": "master",
|
||||
"commit": "aa1497ed0949fd50e99e70d6324a29c5b34f9390",
|
||||
"repoRoot": "/Users/zhongweili/projects/openmind/42plugin-data"
|
||||
},
|
||||
"manifest": {
|
||||
"name": "kent-beck-tdd",
|
||||
"description": "Kent Beck's organic TDD: Let design emerge naturally through small, iterative test cycles",
|
||||
"version": "1.0.0"
|
||||
},
|
||||
"content": {
|
||||
"files": [
|
||||
{
|
||||
"path": "README.md",
|
||||
"sha256": "b2a7d3905094705b6e1c8a4f4522caa81c6fde713a443bc1e70e1cf6aff859fd"
|
||||
},
|
||||
{
|
||||
"path": ".claude-plugin/plugin.json",
|
||||
"sha256": "846c3bcb35ac3800de4df92e658001e7aa4f7c542f26ec83eb5e0ed254367b4b"
|
||||
},
|
||||
{
|
||||
"path": "commands/kb-review.md",
|
||||
"sha256": "939f2259438c215ffb981075309ee216b850fc4c2324afb87fbbe29bc18c18be"
|
||||
},
|
||||
{
|
||||
"path": "commands/kb-red.md",
|
||||
"sha256": "64ad38a10362a984b6c71ea433a99a782e07f95889b4bebc30655585b85d5988"
|
||||
},
|
||||
{
|
||||
"path": "commands/kb-start.md",
|
||||
"sha256": "8f277395e542800a7c5ae4abceefc3e3474e64c592fda5b564b485eb467692cf"
|
||||
},
|
||||
{
|
||||
"path": "commands/kb-refactor.md",
|
||||
"sha256": "d952ee0fb05993f76e8d8bd192e8edb41e9587778b7b6e306c45e82edd1d683d"
|
||||
},
|
||||
{
|
||||
"path": "commands/kb-green.md",
|
||||
"sha256": "4f6f72649d03f65b97dfa7db1b264106f7fb6bd13187430c18d0792cfd85f4a1"
|
||||
}
|
||||
],
|
||||
"dirSha256": "7465967f0db91be17641bb12a329d7b3c5de8d78a821220c2871175c1466945c"
|
||||
},
|
||||
"security": {
|
||||
"scannedAt": null,
|
||||
"scannerVersion": null,
|
||||
"flags": []
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user