需求澄清
猜数字游戏的规则包括:
- 输入4个0~9中不同的数字,按enter键查阅结果是否正确(以“?A?B”形式显示)
说明: ?A表示所输入的?个数字和位置都与手机的答案相同; ?B表示有?个数字相同而位置有误。例如,输入“3609”时显示为“1A2B ”表示其中有一个数的数字和位置都对了;有两个数的数字对但位置不对;还有一个数的数字和位置都不对。 - 猜中数字,显示“4A0B”,游戏结束;
- 如果6次没猜中,那么游戏失败,显示“You are lose”。
故事拆分
编号 | 描述 |
---|---|
故事 1 | 作为玩家,我想要知道输入数中数字和位置都相同的个数,以便于赢得游戏 |
故事 2 | 作为玩家,我想要知道输入数中数字相同而位置有误的个数,以便于赢得游戏 |
故事 3 | 作为游戏设计者,我想要控制玩家尝试次数,以便于增加游戏的趣味性 |
故事 4 | 作为游戏设计者,我想要记录玩家每一次的猜数结果,以便于查看猜数历史 |
环境搭建
安装 Cursor
在Cursor官网下载最新版本,本地安装完成后在菜单 Cursor / About Cursor
查看版本号:
Version: 0.47.8
VSCode Version: 1.96.2
Commit: 82ef0f61c01d079d1b7e5ab04d88499d5af500e0
Date: 2025-03-18T05:28:47.245Z
Electron: 32.2.6
Chromium: 128.0.6613.186
Node.js: 20.18.1
V8: 12.8.374.38-electron.0
OS: Darwin x64 20.6.0
规则配置
在 Cursor Settings / Rules
添加 AI TDD 开发的规则:
- tdd-rule.mdc
# AI TDD 开发流程
- 根据用户故事或任务生成验收准则
- 根据验收准则生成测试代码
- 生成产品代码
- 测试成功(如果测试失败,则自动修复,直到成功,最多尝试3次)
- 重构代码:消除重复,代码易于理解,没有冗余
- 测试成功(如果测试失败,则自动修复,直到成功,最多尝试3次)
- tcode-rule.mdc
# 测试代码生成规则
请你作为TDD专家级程序员,按照以下要求生成测试代码:
- 为当前用户故事所有的验收准则编写对应的测试代码
- 不要写更多的测试代码,避免过度设计
- 测试代码要求是GWT格式(given-when-then三段式)
- 使用goconvey测试框架
示例:
```go
import (
. "github.com/smartystreets/goconvey/convey"
)
Convey("testEmptyManagerId", t, func() {
Convey("given ", t, func() {
Convey("when ", t, func() {
Convey("then ", t, func() {
})
})
})
})
mode 配置
Agent + claude-3.7-sonnet
AI TDD 实践
为了控制文章的规模,本文仅展示用AI TDD 开发故事1的过程。
故事一提示词
新建 Chat 页面,选择 tdd-rule 和 tcode-rule,并附上提示词:
prompt.png
prompt 文本如下:
## 需求澄清
猜数字游戏的规则包括:
- 输入4个0~9中不同的数字,按enter键查阅结果是否正确(以“?A?B”形式显示)
说明: ?A表示所输入的?个数字和位置都与手机的答案相同; ?B表示有?个数字相同而位置有误。例如,输入“3609”时显示为“1A2B ”表示其中有一个数的数字和位置都对了;有两个数的数字对但位置不对;还有一个数的数字和位置都不对。
- 猜中数字,显示“4A0B”,游戏结束;
- 如果6次没猜中,那么游戏失败,显示“You are lose”。
## 故事拆分
| 编号 | 描述 |
|------|------|
| 故事 1 | 作为玩家,我想要知道输入数中数字和位置都相同的个数,以便于赢得游戏 |
| 故事 2 | 作为玩家,我想要知道输入数中数字相同而位置有误的个数,以便于赢得游戏 |
| 故事 3 | 作为游戏设计者,我想要控制玩家尝试次数,以便于增加游戏的趣味性 |
| 故事 4 | 作为游戏设计者,我想要记录玩家每一次的猜数结果,以便于查看猜数历史 |
## 指令
- 请以TDD的方式完成故事1开发,暂不考虑故事2、故事3和故事4
- 代码工程名为guess-number,外部API调用为 restult = game.Guess(number)
- 不要编写main.go文件和README.md文档
验收准则生成
acceptance.png
测试代码生成
tcode.png
game_test.go 代码如下:
package game
import (
"testing"
. "github.com/smartystreets/goconvey/convey"
)
func TestGuessNumber(t *testing.T) {
Convey("故事1:计算输入数中数字和位置都相同的个数", t, func() {
Convey("给定一个答案是1234的游戏", func() {
game := NewGame("1234")
Convey("当输入数字5678(完全不同)时", func() {
result := game.Guess("5678")
Convey("则返回的A值应为0", func() {
So(result, ShouldEqual, "0A0B")
})
})
Convey("当输入数字1567(第一位相同)时", func() {
result := game.Guess("1567")
Convey("则返回的A值应为1", func() {
So(result, ShouldEqual, "1A0B")
})
})
Convey("当输入数字1537(第一位和第三位相同)时", func() {
result := game.Guess("1537")
Convey("则返回的A值应为2", func() {
So(result, ShouldEqual, "2A0B")
})
})
Convey("当输入数字1234(全部相同)时", func() {
result := game.Guess("1234")
Convey("则返回的A值应为4", func() {
So(result, ShouldEqual, "4A0B")
})
})
})
})
}
产品代码生成
pcode.png
game.go 代码如下:
package game
import "fmt"
// Game 表示一个猜数字游戏
type Game struct {
answer string
}
// NewGame 创建一个新的猜数字游戏
func NewGame(answer string) *Game {
return &Game{
answer: answer,
}
}
// Guess 猜测一个数字并返回结果
func (g *Game) Guess(number string) string {
a := 0
// 计算A的值(位置和数字都相同)
for i := 0; i < len(number) && i < len(g.answer); i++ {
if number[i] == g.answer[i] {
a++
}
}
// 当前只实现故事1,仅返回A的值,B暂时为0
return fmt.Sprintf("%dA0B", a)
}
测试执行
创建 go.mod 文件:
gomod.png
运行测试:
tcode-succ.png
重构
重构代码:
refactor.png
重构后的代码:game.go
package game
import "fmt"
// Game 表示一个猜数字游戏
type Game struct {
answer string // 游戏的答案(4位数字)
}
// NewGame 创建一个新的猜数字游戏
// answer: 表示游戏的答案,应该是4个不同的数字
func NewGame(answer string) *Game {
return &Game{
answer: answer,
}
}
// Guess 猜测一个数字并返回结果
// number: 玩家猜测的数字
// 返回格式为"xAyB",其中x表示位置和数字都正确的个数,y表示仅数字正确但位置错误的个数
// 当前实现只关注故事1,只计算A值,B始终为0
func (g *Game) Guess(number string) string {
countA := g.countPositionAndNumberMatch(number)
return fmt.Sprintf("%dA0B", countA)
}
// countPositionAndNumberMatch 计算位置和数字都匹配的个数(A值)
func (g *Game) countPositionAndNumberMatch(number string) int {
count := 0
for i := 0; i < len(number) && i < len(g.answer); i++ {
if number[i] == g.answer[i] {
count++
}
}
return count
}
再次运行测试:测试通过
test-ok.png
总结
AI 以 TDD 方式完成故事1开发后的自我总结:
summary.png
在 AI TDD 开发方式下,原来 TDD 繁琐的“红-绿-重构”实施步骤由 AI 自动完成,开发人员则专注于故事拆分,确保故事粒度与 AI 的现阶段能力高度匹配。从本文的实践可以看出,在编码阶段采用 AI TDD 开发方式能够大幅度提升编码的效率和质量,考虑后续在实际项目中试点和推广。
未来展望
当故事比较复杂时,还需要将故事进一步拆分成任务:
- 故事关注业务价值,强调做正确的事,一般面向微服务的 API 进行测试,对应FT(Functional Test,功能测试);
- 任务关注软件设计,强调正确的做事,一般面向微服务内部(分层分模块)的零部件进行测试,对应UT(Unit Test,单元测试)。
对于实际项目,在测试代码生成时还要考虑测试桩的提炼和测试关键字的封装。
在后续的文章中,我们再介绍相关的最佳实践,敬请期待!