在 Day 17 中,我們成功整合了 AI 好同事——GitHub Copilot,但我們也需要能夠意識到,一個只會瘋狂寫 production code 的 AI,只會破壞整個 TDD 的流程,我們需要學會如何「指揮」AI,而不是被 AI「主宰」。
今天的目標:掌握 TDD 流程中的「AI 詠唱術」(Prompt Engineering),並透過一個全新的微型 Kata,完整地演練一次從需求發想到開發完成的 AI 協作 TDD 流程。
與 AI 協作,就像是和一個知識淵博但沒有主見的實習生一起工作,如果你對他說:「幫我做個登入功能」,他可能會用他所知道的「最常見」的方式,給你一個包含所有程式碼的巨大檔案。
但如果你遵循 TDD 的紀律,你會對他說:
看到區別了嗎?後者將一個大任務分解成了符合 TDD 節奏的微小步驟,而主導權始終在我們(開發者)手中,我們可以利用 AI 來加速每一個步驟,而不是讓 AI 取代整個流程。
Prompt Engineering 就是設計這些精準、分步指令的藝術。
我們可以將傳統的 R-G-R (Red-Green-Refactor) 循環,擴充為一個包含 AI 互動的新模式:
這個模式確保了我們始終遵循 TDD 的核心紀律,同時在每一步都利用 AI 來提升效率。
坐而言不如起而行,接下來就透過 「TDD 來寫個密碼驗證器」
我們來建立一個新的套件 password
,可以先建立 password/password.go 和 password/password_test.go 兩個空檔案。
我們的第一個需求是:「密碼長度必須至少為 8 個字元」。
根據這個需求,我們可以發想出幾個測試案例 (Test Case):
TDD 的精髓是小步快跑,我們選擇第一個失敗案例作為起點。
我們打開空的 password_test.go 檔案,然後對 Copilot 下我們的第一個指令。
- **角色**: 你是一位資深的 Golang TDD 專家。
- **上下文**: 我正在 password 套件中,需要為一個名為 Validate(password string) (bool, error) 的函式編寫測試。
- **指令**: 請為我生成一個表格驅動的單元測試。現在,只需要包含一個測試案例:驗證當密碼長度少於8個字元時(例如 "123"),Validate 函式應回傳 false 和一個內容為 "password is too short" 的錯誤。
- **限制**: 只生成測試程式碼,不要生成 Validate 函式的任何實作,且測試工具使用 `testify`。
Copilot Chat 可能會生成如下的測試程式碼:
// copilot 可能會產出的 code
// password/password_test.go
package gopasswordverify
import (
"testing"
"github.com/stretchr/testify/assert"
)
func TestValidate(t *testing.T) {
tests := []struct {
name string
password string
expectedValid bool
expectedError string
}{
{
name: "password too short",
password: "123",
expectedValid: false,
expectedError: "password is too short",
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
valid, err := Validate(tt.password)
assert.Equal(t, tt.expectedValid, valid)
if tt.expectedError != "" {
assert.Error(t, err)
assert.Equal(t, tt.expectedError, err.Error())
} else {
assert.NoError(t, err)
}
})
}
}
同時,我們需要在 password.go 補上一個空的函式簽名,以避免編譯錯誤:
// 手動建立空函式
// password/password.go
package password
func Validate(password string) (bool, error) {
return true, nil
}
執行 go test -v ./...
,我們如願以償地看到了紅燈!require.Error(t, err) 失敗了,因為我們回傳的是 nil 錯誤。
我們已經有了一個清晰的、失敗的測試。現在,我們再次召喚 Copilot Chat。
- **上下文**: (開啟 password.go 和 password_test.go 兩個檔案,讓 Copilot 知道全局)
- **指令**: 我的測試 `TestValidate` 失敗了,錯誤是測試期望一個 error 但得到 `nil`, 請修改 password.go 中的 Validate 函式,只編寫最少的程式碼,來讓這個測試通過。
Copilot 分析了測試案例後:
// copilot 可能會產出的 ocde
// password/password.go
package password
import (
"errors"
"fmt" // Copilot可能會選擇用 fmt.Errorf
)
func Validate(password string) (bool, error) {
if len(password) < 8 {
return false, errors.New("password is too short")
}
return true, nil
}
再次執行 go test -v ./...
,綠燈亮起!第一個 TDD 循環完成。
現在,我們想新增以下幾項需求
相信透過重複以上循環,我們可以快速、安全地完成所有需求 (這部分就先留給各位讀者大大啦,讓你們也享受一下當老闆的感覺 ~~~)
今天我們將理論與實踐相結合,完成了一次從零到一的 AI 協作 TDD 演練。
現在,你已經不僅僅是 TDD 的實踐者,更是懂得如何利用 AI 將 TDD 效率最大化的「新世代開發者」。
新預告:Day 19 - AI 輔助重構與測試覆蓋 - 讓 AI 成為你的程式碼審查員
今天我們讓 AI 幫我們寫程式碼,明天我們就讓 AI 幫我們「挑刺」,我們預期利用 AI 快速發現程式碼中的壞味道、提出重構建議,並利用它窮舉式的思維,幫助我們找到測試案例中可能遺漏的邊界條件,以達到更高的測試覆蓋率。