Initial commit
This commit is contained in:
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 的話
|
||||
|
||||
> "讓測試通過的技巧是暫時降低對程式碼品質的標準。"
|
||||
> "骯髒的程式碼是通往乾淨程式碼的墊腳石。"
|
||||
> "當你看到綠燈時,那是重構的信號。"
|
||||
Reference in New Issue
Block a user