name: 测试驱动开发 description: 使用红绿重构循环实现测试驱动开发(TDD)工作流程。适用于编写新功能、修复bug或重构现有代码。始终先写失败的测试,然后编写最少代码使其通过,最后重构。确保代码可靠性、防止回归、通过可测试性要求改进设计、通过测试记录预期行为、实现自信重构,并在整个开发过程中保持高代码质量标准。
测试驱动开发(TDD)
何时使用此技能
- 从头开始编写新功能或功能
- 修复bug时添加回归测试以防止复发
- 在测试覆盖下安全地重构现有代码
- 为未测试的遗留代码添加测试覆盖
- 在实现前确保代码按预期行为
- 通过可测试性约束改进代码设计
- 记录预期行为和边缘情况
- 构建必须正确的关键业务逻辑
- 开发具有明确合约的API或库
- 处理需要高可靠性的项目
- 实现复杂算法或业务规则
- 在团队协作中,测试作为文档使用
实现红绿重构循环,以获得可靠、可测试的代码。
TDD循环
1. 红色 - 编写失败的测试
在实现前编写描述期望行为的测试:
def test_user_can_register():
"""测试用户可以使用有效数据成功注册。"""
result = register_user(email="test@example.com", password="secure123")
assert result.success is True
assert result.user.email == "test@example.com"
为什么这有效:
- 强制清晰思考需求
- 早期捕获规格问题
- 提供对实现的即时反馈
- 创建活文档
2. 绿色 - 编写最少代码以通过
仅实现使测试通过所需的内容:
def register_user(email: str, password: str) -> RegistrationResult:
"""使用电子邮件和密码注册新用户。"""
user = User(email=email)
return RegistrationResult(success=True, user=user)
抵制诱惑:
- 添加未覆盖的功能
- 过度设计解决方案
- 过早优化
3. 重构 - 提高代码质量
在测试通过后,改进代码结构:
def register_user(email: str, password: str) -> RegistrationResult:
"""使用电子邮件和密码注册新用户。"""
validate_email(email)
validate_password(password)
hashed_password = hash_password(password)
user = User(email=email, password_hash=hashed_password)
user.save()
return RegistrationResult(success=True, user=user)
重构目标:
- 更好命名
- 移除重复
- 改进结构
- 增强可读性
最佳实践
从最简单测试开始
# 好 - 从简单开始
def test_add_returns_sum():
assert add(2, 3) == 5
# 避免 - 不要从边缘情况开始
def test_add_handles_overflow_with_large_numbers():
assert add(sys.maxsize, 1) == expected_overflow_behavior
一次测试一件事
# 好 - 单一关注点
def test_user_registration_creates_user():
result = register_user("test@example.com", "pass123")
assert result.user is not None
def test_user_registration_hashes_password():
result = register_user("test@example.com", "pass123")
assert result.user.password_hash != "pass123"
# 避免 - 多个断言
def test_user_registration():
result = register_user("test@example.com", "pass123")
assert result.user is not None
assert result.user.password_hash != "pass123"
assert result.user.email == "test@example.com"
assert result.success is True
使用描述性测试名称
# 好 - 描述行为
def test_invalid_email_returns_validation_error()
def test_duplicate_email_raises_already_exists_error()
def test_successful_registration_sends_welcome_email()
# 避免 - 模糊名称
def test_register()
def test_email()
def test_validation()
遵循三个A模式
def test_user_can_update_profile():
# 安排 - 设置测试数据
user = create_test_user(email="test@example.com")
# 行动 - 执行操作
result = user.update_profile(name="John Doe", bio="Developer")
# 断言 - 验证结果
assert result.success is True
assert user.name == "John Doe"
assert user.bio == "Developer"
常见模式
测试异常
def test_registration_with_invalid_email_raises_error():
with pytest.raises(ValidationError) as exc:
register_user(email="invalid", password="pass123")
assert "valid email" in str(exc.value)
测试异步代码
@pytest.mark.asyncio
async def test_async_user_registration():
result = await register_user_async("test@example.com", "pass123")
assert result.success is True
使用夹具
@pytest.fixture
def test_user():
return User(email="test@example.com", name="Test User")
def test_user_can_login(test_user):
result = login(test_user.email, "correct_password")
assert result.success is True
模拟外部依赖
def test_user_registration_sends_email(mocker):
# 模拟电子邮件服务
mock_send = mocker.patch('services.email.send_welcome_email')
register_user("test@example.com", "pass123")
# 验证电子邮件已发送
mock_send.assert_called_once_with("test@example.com")
验证清单
在完成TDD实现前:
- [ ] 测试在实现前编写
- [ ] 测试最初失败(红色阶段确认)
- [ ] 添加最少代码以通过测试(绿色阶段)
- [ ] 重构代码同时保持测试通过
- [ ] 测试名称清晰描述行为
- [ ] 每个测试专注于一个行为
- [ ] 没有未测试的代码路径
- [ ] 所有测试一致通过
- [ ] 代码可读且可维护
TDD的好处
- 更好设计 - 先写测试导致更模块化、可测试的代码
- 信心 - 全面测试套件捕获回归
- 文档 - 测试作为活文档
- 更快调试 - 失败精确定位问题
- 减少bug - 开发期间捕获边缘情况
何时不使用严格TDD
- 快速原型/概念验证
- UI布局实验
- 探索性编码(学习新API)
- 简单的getter/setter方法
对于这些情况,在实现后但提交前编写测试。
与开发工作流集成
# TDD开发循环
git checkout -b feature/user-registration
# 1. 编写失败的测试
# 2. 运行测试(应该失败)
pytest tests/test_registration.py
# 3. 实现最少代码
# 4. 运行测试(应该通过)
pytest tests/test_registration.py
# 5. 重构
# 6. 运行测试(应该仍通过)
pytest tests/test_registration.py
# 当所有测试通过时提交
git add .
git commit -m "feat: 使用TDD实现用户注册"
参考文献
- Kent Beck - “测试驱动开发:示例”
- Martin Fowler - “重构:改进现有代码设计”
- pytest文档
- unittest文档