11 KiB
Storyboard Board Service 完整测试实现
日期: 2026-02-04
类型: 测试覆盖补充
模块:
server/tests/unit/services/test_storyboard_board_service.py(单元测试)server/tests/integration/test_storyboard_board_api.py(集成测试) 影响范围: 测试层 + API 层
📋 概述
为 StoryboardBoardService 创建完整的单元测试和集成测试,参考 test_project_service.py 和 test_storyboard_api.py 的测试结构:
- 单元测试: 24 个测试用例,覆盖所有公共方法和边界条件
- 集成测试: 18 个测试用例,覆盖所有 API 端点和业务流程
✅ 实现内容
1. 测试文件创建
位置: server/tests/unit/services/test_storyboard_board_service.py
测试组织结构:
TestStoryboardBoardService
├── 辅助方法(4 个)
│ ├── _create_service()
│ ├── _create_test_user_id()
│ ├── _create_test_project()
│ └── _create_test_storyboards()
│
├── 获取分镜看板数据测试(6 个)
│ ├── test_get_board_data_success
│ ├── test_get_board_data_with_track_filter
│ ├── test_get_board_data_empty_project
│ ├── test_get_board_data_permission_denied
│ ├── test_get_board_data_project_not_found
│ └── test_get_board_data_duration_calculation
│
├── 分镜重新排序测试(6 个)
│ ├── test_reorder_storyboards_success
│ ├── test_reorder_storyboards_permission_denied
│ ├── test_reorder_storyboards_invalid_id
│ ├── test_reorder_storyboards_verify_order
│ ├── test_reorder_storyboards_partial
│ └── test_reorder_storyboards_transaction_rollback
│
├── 获取轨道数据测试(4 个)
│ ├── test_get_track_items_success
│ ├── test_get_track_items_invalid_type
│ ├── test_get_track_items_permission_denied
│ └── test_get_track_items_empty
│
├── 时间范围查询测试(5 个)
│ ├── test_get_items_by_time_range_success
│ ├── test_get_items_by_time_range_partial_overlap
│ ├── test_get_items_by_time_range_no_overlap
│ ├── test_get_items_by_time_range_with_filter
│ └── test_get_items_by_time_range_permission_denied
│
└── 私有方法测试(3 个)
├── test_calculate_tracks_all_types
├── test_calculate_tracks_filtered
└── test_calculate_tracks_time_accumulation
2. Service 层修复
文件: server/app/services/storyboard_board_service.py
修复 1: 移除不支持的 order_by 参数
问题: Repository 的 get_by_project() 方法不接受 order_by 参数
修复:
# ❌ 错误(之前)
storyboards = await self.storyboard_repo.get_by_project(
project_id=project_id,
order_by='order_index'
)
# ✅ 正确(修复后)
storyboards = await self.storyboard_repo.get_by_project(
project_id=project_id,
page=1,
page_size=1000 # 获取所有分镜
)
说明: Repository 已经默认按 order_index 排序(第 67 行)
修复 2: 事务管理问题
问题: Service 层使用 async with self.db.begin() 导致嵌套事务错误
错误信息:
sqlalchemy.exc.InvalidRequestError: A transaction is already begun on this Session.
修复:
# ❌ 错误(之前)
async with self.db.begin():
for storyboard_id, new_order in reorder_data.storyboard_orders.items():
await self.db.execute(...)
# ✅ 正确(修复后)
for storyboard_id, new_order in reorder_data.storyboard_orders.items():
await self.db.execute(...)
await self.db.flush()
说明: 遵循 jointo-tech-stack 规范,Service 层依赖上层(API 层或测试 fixture)管理事务
🔍 测试覆盖点
单元测试覆盖(24 个)
功能测试
- ✅ 获取完整分镜看板数据
- ✅ 筛选特定轨道类型
- ✅ 处理空项目(无分镜)
- ✅ 分镜重新排序(完整/部分)
- ✅ 获取指定轨道数据
- ✅ 时间范围查询(重叠/无重叠)
权限测试
- ✅ viewer 权限验证
- ✅ editor 权限验证
- ✅ 无权限访问拦截
- ✅ 项目不存在处理
边界条件测试
- ✅ 空项目处理
- ✅ 无效分镜 ID 处理
- ✅ 无效轨道类型验证
- ✅ 时间重叠检测
业务逻辑测试
- ✅ 时长计算逻辑(actual_duration 优先于 estimated_duration)
- ✅ 时间累积计算(从 0 开始累加)
- ✅ 排序验证
- ✅ 事务回滚验证
集成测试覆盖(18 个)
API 端点测试
- ✅ GET
/projects/{project_id}/storyboard-board- 获取看板数据 - ✅ POST
/projects/{project_id}/storyboard-board/reorder- 调整分镜顺序 - ✅ GET
/projects/{project_id}/storyboard-board/tracks/{track_type}- 获取轨道数据 - ✅ GET
/projects/{project_id}/storyboard-board/items/time-range- 时间范围查询
HTTP 场景测试
- ✅ 认证验证(JWT Token)
- ✅ 权限控制(403 Forbidden)
- ✅ 参数验证(422 Unprocessable Entity)
- ✅ 业务错误(404 Not Found)
- ✅ 空数据处理
- ✅ 查询参数解析(track_types, start_time, end_time)
端到端业务流程
- ✅ 完整的 API → Service → Repository → DB 链路
- ✅ 响应格式验证(ApiResponse 结构)
- ✅ 数据一致性验证
2. 集成测试创建
位置: server/tests/integration/test_storyboard_board_api.py
测试组织结构:
TestStoryboardBoardAPI
├── 获取分镜看板数据测试(5 个)
│ ├── test_get_storyboard_board_success
│ ├── test_get_storyboard_board_with_track_filter
│ ├── test_get_storyboard_board_empty_project
│ ├── test_get_storyboard_board_unauthorized
│ └── test_get_storyboard_board_project_not_found
│
├── 分镜重新排序测试(3 个)
│ ├── test_reorder_storyboards_success
│ ├── test_reorder_storyboards_invalid_id
│ └── test_reorder_storyboards_unauthorized
│
├── 获取轨道数据测试(4 个)
│ ├── test_get_track_items_success
│ ├── test_get_track_items_invalid_type
│ ├── test_get_track_items_unauthorized
│ └── test_get_track_items_empty
│
├── 时间范围查询测试(5 个)
│ ├── test_get_items_by_time_range_success
│ ├── test_get_items_by_time_range_with_track_filter
│ ├── test_get_items_by_time_range_no_overlap
│ ├── test_get_items_by_time_range_invalid_params
│ └── test_get_items_by_time_range_unauthorized
│
└── 边界条件测试(1 个)
└── test_reorder_storyboards_empty_orders
测试的 API 端点:
GET /api/v1/projects/{project_id}/storyboard-boardPOST /api/v1/projects/{project_id}/storyboard-board/reorderGET /api/v1/projects/{project_id}/storyboard-board/tracks/{track_type}GET /api/v1/projects/{project_id}/storyboard-board/items/time-range
3. API 层修复
文件: server/app/api/v1/storyboard_board.py
修复:错误的响应构造方法
问题: 使用了不存在的 ApiResponse.success_response() 方法
修复:
# ❌ 错误(之前)
return ApiResponse.success_response(
data=storyboard_board_data,
message="分镜看板数据获取成功"
)
# ✅ 正确(修复后)
return ApiResponse(
code=200,
message="分镜看板数据获取成功",
data=storyboard_board_data
)
影响端点: 所有 4 个 API 端点
📊 测试结果
单元测试
$ docker exec jointo-server-app pytest tests/unit/services/test_storyboard_board_service.py -v
============================== 24 passed in 0.40s ==============================
测试通过率: 100% (24/24)
执行时间: 0.40s
集成测试
$ docker exec jointo-server-app pytest tests/integration/test_storyboard_board_api.py -v
============================== 18 passed in 0.44s ==============================
测试通过率: 100% (18/18)
执行时间: 0.44s
总计
- 单元测试: 24/24 ✅
- 集成测试: 18/18 ✅
- 总通过率: 100% (42/42)
- Linter 检查: 无错误
🎓 设计决策
1. 时间计算模型
Service 设计: 实时累积计算时间位置
# 从 0 开始累加每个分镜的时长
current_time = 0.0
for storyboard in storyboards:
start_time = current_time
duration = float(storyboard.actual_duration or storyboard.estimated_duration or 0)
end_time = current_time + duration
current_time = end_time
说明:
- 忽略分镜表中的
start_time/end_time字段 - 按
order_index顺序累积计算 - 符合"无独立数据存储,实时计算"的设计理念
2. 事务管理策略
原则: Service 层不主动开启事务,依赖上层管理
理由:
- 避免嵌套事务冲突
- 符合分层架构规范
- 测试更易于控制
3. 测试数据设计
策略: 在测试方法内创建数据
async def test_example(self, db_session):
# 1. 创建测试数据
project = await self._create_test_project(db_session, user_id)
storyboards = await self._create_test_storyboards(db_session, project.id, count=3)
# 2. 执行测试
result = await service.get_storyboard_board_data(user_id, project.id)
# 3. 验证结果
assert result.storyboard_count == 3
# 4. conftest 自动回滚
优点:
- 测试隔离
- 数据可控
- 自动清理
📝 相关文档
- 测试规范: .claude/skills/jointo-tech-stack/references/testing.md
- 后端架构: .claude/skills/jointo-tech-stack/references/backend.md
- 测试 README: tests/README.md
🚀 运行命令
运行单元测试
# 运行所有单元测试
docker exec jointo-server-app pytest tests/unit/services/test_storyboard_board_service.py -v
# 运行特定单元测试
docker exec jointo-server-app pytest tests/unit/services/test_storyboard_board_service.py::TestStoryboardBoardService::test_get_board_data_success -v
运行集成测试
# 运行所有集成测试
docker exec jointo-server-app pytest tests/integration/test_storyboard_board_api.py -v
# 运行特定集成测试
docker exec jointo-server-app pytest tests/integration/test_storyboard_board_api.py::TestStoryboardBoardAPI::test_get_storyboard_board_success -v
运行完整测试套件
# 运行 storyboard_board 的所有测试(单元 + 集成)
docker exec jointo-server-app pytest \
tests/unit/services/test_storyboard_board_service.py \
tests/integration/test_storyboard_board_api.py \
-v
🎯 后续优化建议
- 性能测试: 测试大量分镜(100+)的性能表现
- 并发测试: 测试多用户同时重新排序的并发安全性
- 其他轨道: 实现 resource/video/sound/subtitle/voice 轨道的关联数据查询
- 压力测试: 测试时间范围查询的性能(大量分镜场景)
维护者: Jointo 开发团队
最后更新: 2026-02-04