第3章 测试驱动开发(TDD):红绿重构循环,一个实战案例演示
我记得刚接触TDD那会儿,内心是拒绝的。
“先写测试?我代码都还没写呢,测什么呢?”
后来被一个老前辈硬拉着练了几个小项目,才发现——不是我写测试浪费时间,而是我以前写代码的方式太蛮干了。今天我们就用一个真实到不能再真实的案例,把这个红绿重构循环彻底聊透。
TDD是什么?先搞懂红绿重构
TDD说白了就三步:
- 红:写一个会失败的测试(没实现嘛,肯定红)
- 绿:写刚好能让测试通过的代码(别扯设计模式,先让它绿)
- 重构:在绿灯保护下,优化代码结构
循环往复,像心跳一样。我习惯把这个循环叫做“三拍子”,你想想看,一拍红,一拍绿,一拍重构,节奏感有了,代码质量也就上来了。
核心原则:写测试 → 看到红灯 → 写实现 → 看到绿灯 → 重构 → 继续下一轮。
好,理论就这么多。咱们直接上代码。
实战案例:用户登录功能
我们要做一个最基础的用户名+密码校验。需求简化版:
- 输入用户名和密码
- 用户名不能为空
- 密码长度至少6位
- 用户名和密码都合法时返回 true,否则返回 false
看起来简单吧?但你直接写代码,很容易写出一个又长又臭的if堆叠。用TDD试试。
红阶段:先写测试
打开你的测试文件(我用的是JUnit + Mockito,但原理都一样)。先写第一个测试:
@Test
fun `should return false when username is empty`() {
val result = loginValidator.validate("", "password123")
assertFalse(result)
}
保存,运行。嗯,红灯。因为 loginValidator 类都还没创建呢,编译都不通过。但别慌,红灯是正常的——它告诉我们:测试明确表达了期望行为,但实现还没跟上。
我的一点经验:写测试时不要想着“这个功能以后可能会变”,就盯着当前这个最简单的场景。一次只写一个失败的测试。
同样,我们再写第二个测试:
@Test
fun `should return false when password length less than 6`() {
val result = loginValidator.validate("user", "12345")
assertFalse(result)
}
也红。好了,红够了,该让它绿了。
绿阶段:只写刚好能通过的代码
创建一个 LoginValidator 类,只实现最简单的逻辑:
class LoginValidator {
fun validate(username: String, password: String): Boolean {
if (username.isBlank()) return false
if (password.length < 6) return false
return true
}
}
运行测试——两个都绿了。嗯,就是这么简单。你可能觉得“这也太粗糙了吧?”没错,TDD的绿阶段不允许你多想,只求让刚写的那个测试通过,不要超前实现任何功能。
我曾经犯过一个错:绿阶段顺手把邮箱格式校验也加上了,结果那个功能根本还没需求,后来改需求时全删了。白忙活一场。现在我只在绿阶段做最脏的活。
重构阶段:在绿灯保护下优化
现在两个测试都绿着,咱们可以安心重构了。看看这段代码有什么问题?
isBlank()其实可以处理空字符串和空格,但需求里只说了“不能为空”,用isBlank()太严格了?我决定保留,因为空格当成无效更符合直觉。- 条件语句顺序:先检查用户名再检查密码,逻辑清晰,可以保留。
- 要不要抽一个单独的校验方法?目前就两个规则,没必要。等第三个规则出现再说。
重构完了,运行测试——全部还是绿。完美。
注意:重构的前提是测试全绿。如果红了,先撤销改动,回到绿状态再重新思考。我上次手快,重构完发现测试红了,硬调了半小时才发现是漏了一个 import。
继续下一轮:增加新需求
现在产品说:密码不能全是数字。好,TDD再来一轮。
- 红:写一个测试
should return false when password is all digits - 绿:在
validate()里加一句if (password.matches(Regex("\\d+"))) return false - 重构:这时候你发现
validate()里已经有三个 if 了,可以考虑抽一个private fun isPasswordValid()出来。抽完测试全绿。
你看,循环就是这样转起来的。
红绿重构循环的SVG总结
下面这张图概括了完整的循环,我把它画出来方便你贴在工位上:
避坑指南(都是血泪)
- 测试太大? 一次只测一个行为,别一个测试测三个条件。我在项目里见过一个200行的测试,根本不知道它到底在测什么。
- 绿阶段想太多? 忍住。你现在的目标是“通过”,不是“优雅”。优雅留给重构阶段。
- 重构范围太大? 一次只改一小步。每改完一两行就运行测试。我曾经一次性改了20行,三个测试全红,查bug查到凌晨。
小结
红绿重构不是什么高大上的方法论,它就是一套防止你写出屎山的工序。你想想看,先写测试相当于先画图纸再施工,是不是比直接抹水泥靠谱多了?
好了,这一轮的红绿循环跑完了。下面轮到你在自己的项目里动手试试——拿一个最简单的功能,比如邮箱格式校验,走一遍红绿重构。相信我,走了第一次就想走第二次。