小张刚接手一个用户注册功能,老板说‘先跑通就行’,他唰唰写了二十行代码,一测——注册成功,但邮箱格式错了也能过,密码没校验长度,连空字符串都放行。第二天测试同学提了五个 bug,他边改边挠头:怎么当初就没想着‘它该干啥’?
不是先写代码,是先问‘它得通过什么才算对’
测试驱动开发(TDD)不神秘,就三步循环:红 → 绿 → 重构。红,是写一个会失败的测试;绿,是写刚好能让它通过的最简代码;重构,是让代码更干净,但测试还得全绿。
来个真实小例子:判断闰年
需求很简单:输入年份,返回 true 或 false。我们不急着写判断逻辑,先写测试:
import unittest
class TestLeapYear(unittest.TestCase):
def test_2000_is_leap_year(self):
self.assertTrue(is_leap_year(2000))
def test_1900_is_not_leap_year(self):
self.assertFalse(is_leap_year(1900))
运行一下——报错:NameError: name 'is_leap_year' is not defined。这正是我们想要的“红”。接着,只写最简实现让它变绿:
def is_leap_year(year):
return True # 先硬编码,让第一个测试过
再跑,第一个测试绿了,第二个失败(因为 1900 返回了 True)。那就补逻辑:
def is_leap_year(year):
if year % 400 == 0:
return True
if year % 100 == 0:
return False
if year % 4 == 0:
return True
return False
两个测试全绿。此时你心里有底:这个函数至少对 2000 和 1900 是靠谱的。
为什么新手容易卡在第一步?
不是不会写测试,是怕“还没想清楚怎么写,先写测试不是瞎写吗?”其实恰恰相反——写测试的过程,就是在把模糊需求掰成可验证的动作。比如“用户登录后跳转首页”,拆出来就是:
① 输入正确账号密码 → 返回 200;
② 输入错误密码 → 返回 401;
③ 登录成功后 session 里有 user_id。
每一条,都是一个 assert。写完这些,你自然知道接口要返回啥、要设哪些字段、边界在哪。
别追求一步到位,先让一个测试亮起来
有次带实习生做订单状态流转,他盯着“待支付→已支付→已发货→已完成”发呆。我说:“别管全流程,先写一个测试:创建订单,默认状态是‘待支付’。”他秒写完,跑通。接着加第二条:“调用 pay() 方法后,状态变成‘已支付’。”再跑,红了——于是他去看 pay() 该干啥,而不是对着空白函数猜。
TDD 不是给代码上枷锁,是给思考装导航。你不需要一开始就写出完美设计,只需要每次只走一小步,且每一步都有测试盯着。