Go 语言测试驱动开发 (TDD) 实战:倒着跳舞的艺术
"Fred Astaire? 当然,他很棒。但别忘了 Ginger Rogers 做了他做的一切……而且是倒着跳的,还穿着高跟鞋。" —— Bob Thaves
这是一篇关于 测试驱动开发 (TDD) 的趣味实战指南。作者 John Arundel(Go 语言作家兼教师)通过一个简单的 IsSorted 函数,展示了如何用"倒着编程"(先写测试,再写代码)的方式,写出健壮、优雅的 Go 代码。
假如函数不存在:一切从测试开始
通常,我们习惯先写函数 IsSorted,再写测试。但这次,我们学学 Ginger Rogers,倒着来。
假设 IsSorted 函数已经存在了,我们直接写测试:
func TestIsSorted_IsTrueForSortedSlice(t *testing.T) { t.Parallel() input := []int{1, 2, 3} // 假设 sorted 包和 IsSorted 函数已经存在 if !sorted.IsSorted(input) { t.Errorf("got false for %v", input) }}最快失败路径:红灯 -> 绿灯
现在的代码甚至无法编译,因为 IsSorted 根本不存在。利用 GoLand 的快速修复功能:
- 创建 sorted 包。
- 创建 IsSorted 函数。
- 添加返回值。
为了让测试先跑起来(并确认它能检测到错误),我们故意写一个错误的实现:
func IsSorted(input []int) bool { return false // 永远返回 false}运行测试 -> 失败 (FAIL)。太棒了!这证明了我们的测试是有效的。
婴儿学步:逐步完善实现
为了通过测试,我们不能只返回 true(那样无法区分未排序的情况)。我们需要增加反向测试用例:
func TestIsSorted_IsFalseForUnsortedSlice(t *testing.T) { t.Parallel() input := []int{1, 3, 2} if sorted.IsSorted(input) { t.Errorf("got true for %v", input) }}然后,我们写一个"最笨"的实现来通过测试:
func IsSorted(input []int) bool { prev := 0 for _, v := range input { if v < prev { return false } prev = v } return true}发现 Bug:倒着修补漏洞
虽然测试通过了,但我们很快意识到这个实现有问题:如果切片包含负数怎么办? 不要急着改代码!先写一个失败的测试用例:
"negative first element": {-1, 2, 3}, // 会失败,因为 -1 < 0 (初始 prev)看到测试失败后,我们再修改代码,将 prev 初始化为 input[0]。
随后,客户反馈了 Panic:空切片导致崩溃。 同样,先加测试用例 "no elements": {},复现 Panic,然后再修复代码(处理 len < 2 的情况)。
最终重构:标准库的降维打击
代码已经很健壮了,但新同事 Alyssa 指出:标准库 slices 包里已经有 IsSorted 了!
因为我们有完善的测试套件,我们可以放心地进行重构:
func IsSorted(input []int) bool { return slices.IsSorted(input)}运行测试 -> 全部通过 (PASS)。
总结:红、绿、重构
这就是"倒着编程"(TDD)的魅力:
- 红 (Red):添加一个失败的测试。
- 绿 (Green):写最少的代码让测试通过。
- 重构 (Refactor):在测试保护下优化代码。
下次写代码时,不妨试着穿上高跟鞋,倒着跳一段舞?你会发现每一步都踩得无比踏实。
求点赞 求关注 ❤️ 求收藏 ⭐️ 你的支持是我更新的最大动力!
