You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
4.7 KiB
4.7 KiB
CreditService 测试重构:使用 Mock 简化测试
日期: 2026-01-28
类型: Test Refactor
影响范围: CreditService 单元测试
背景
原有的单元测试使用真实数据库连接,导致以下问题:
- 测试基础设施复杂: pytest-asyncio 的事件循环管理困难
- 测试不稳定: 连接池、事务管理导致间歇性失败
- 测试速度慢: 每个测试都需要数据库 I/O
- 环境依赖: 需要 PostgreSQL 容器运行
解决方案
采用 Mock Repository 模式,将单元测试与数据库解耦:
- 使用
unittest.mock.AsyncMock模拟 Repository 层 - 测试 Service 层的业务逻辑,而非数据库操作
- 保持集成测试使用真实数据库
实现内容
1. 创建新的测试文件
文件: server/tests/unit/test_credit_service_mock.py
测试覆盖:
- ✅ 枚举转换测试(4个)
- ✅ 积分查询测试(3个)
- ✅ 积分操作测试(6个)
- ✅ 定价计算测试(3个)
- ✅ 超时管理测试(1个)
- ✅ 套餐管理测试(2个)
- ✅ 积分赠送测试(2个)
总计: 21 个测试用例
2. Mock 策略
@pytest.fixture
def mock_repository():
"""Mock CreditRepository"""
repo = AsyncMock()
return repo
@pytest.fixture
def credit_service(mock_session, mock_repository):
"""创建 CreditService 实例(使用 Mock)"""
service = CreditService(mock_session)
service.repository = mock_repository
return service
3. 测试示例
测试积分查询:
@pytest.mark.asyncio
async def test_get_balance(self, credit_service, mock_repository, sample_user):
"""测试查询积分余额"""
mock_repository.get_user.return_value = sample_user
balance = await credit_service.get_balance(sample_user.user_id)
assert balance['balance'] == 1000
assert balance['total_earned'] == 1000
mock_repository.get_user.assert_called_once_with(sample_user.user_id)
测试积分消耗:
@pytest.mark.asyncio
async def test_consume_credits(self, credit_service, mock_repository, sample_user):
"""测试消耗积分"""
mock_repository.user_exists.return_value = True
mock_repository.get_user.return_value = sample_user
mock_repository.create_consumption_log.return_value = consumption_log
result = await credit_service.consume_credits(
user_id=sample_user.user_id,
amount=100,
feature_type=FeatureType.IMAGE_GENERATION
)
assert result.credits_consumed == 100
assert sample_user.ai_credits_balance == 900
测试结果
修复前(使用真实数据库)
5 passed, 16 errors
- 时区问题
- 事件循环问题
- 连接池问题
修复后(使用 Mock)
21 passed in 1.12s
- 所有测试通过
- 无数据库依赖
- 执行速度快
优势对比
| 维度 | 真实数据库 | Mock Repository |
|---|---|---|
| 测试速度 | 慢(~5s) | 快(~1s) |
| 环境依赖 | 需要 PostgreSQL | 无依赖 |
| 测试稳定性 | 不稳定 | 稳定 |
| 测试隔离性 | 差 | 好 |
| 维护成本 | 高 | 低 |
| 业务逻辑覆盖 | 好 | 好 |
测试策略
单元测试(Mock)
- 目标: 测试 Service 层业务逻辑
- 工具: unittest.mock.AsyncMock
- 覆盖: 所有 Service 方法的逻辑分支
- 优势: 快速、稳定、无依赖
集成测试(真实数据库)
- 目标: 测试完整的数据流
- 工具: TestClient + 真实数据库
- 覆盖: API 端点 + Service + Repository + Database
- 优势: 验证真实环境行为
后续工作
- ✅ 单元测试已完成(21/21 通过)
- ⏳ 集成测试待完善(
test_credit_api.py) - ⏳ 添加性能测试(压力测试)
- ⏳ 添加边界条件测试
相关文件
- 新测试文件:
server/tests/unit/test_credit_service_mock.py - 旧测试文件:
server/tests/unit/test_credit_service.py(保留作为参考) - Service 实现:
server/app/services/credit_service.py - Repository 实现:
server/app/repositories/credit_repository.py
最佳实践
- 单元测试使用 Mock: 快速验证业务逻辑
- 集成测试使用真实数据库: 验证完整流程
- Mock 返回值要符合实际: 确保测试有效性
- 使用 AsyncMock: 正确处理异步方法
- 验证 Mock 调用: 确保方法被正确调用
总结
通过使用 Mock Repository 模式,成功将单元测试与数据库解耦,实现了:
- ✅ 100% 测试通过率(21/21)
- ✅ 测试速度提升 5 倍
- ✅ 消除环境依赖
- ✅ 提高测试稳定性
- ✅ 降低维护成本
这为后续的 Service 层测试提供了良好的模板和最佳实践。