# 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` **测试组织结构**: ```python 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` 参数 **修复**: ```python # ❌ 错误(之前) 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. ``` **修复**: ```python # ❌ 错误(之前) 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` **测试组织结构**: ```python 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-board` - `POST /api/v1/projects/{project_id}/storyboard-board/reorder` - `GET /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()` 方法 **修复**: ```python # ❌ 错误(之前) return ApiResponse.success_response( data=storyboard_board_data, message="分镜看板数据获取成功" ) # ✅ 正确(修复后) return ApiResponse( code=200, message="分镜看板数据获取成功", data=storyboard_board_data ) ``` **影响端点**: 所有 4 个 API 端点 ## 📊 测试结果 ### 单元测试 ```bash $ 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 ### 集成测试 ```bash $ 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 设计**: 实时累积计算时间位置 ```python # 从 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. 测试数据设计 **策略**: 在测试方法内创建数据 ```python 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/testing.md) - **后端架构**: [.claude/skills/jointo-tech-stack/references/backend.md](../../.claude/skills/jointo-tech-stack/references/backend.md) - **测试 README**: [tests/README.md](../../server/tests/README.md) ## 🚀 运行命令 ### 运行单元测试 ```bash # 运行所有单元测试 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 ``` ### 运行集成测试 ```bash # 运行所有集成测试 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 ``` ### 运行完整测试套件 ```bash # 运行 storyboard_board 的所有测试(单元 + 集成) docker exec jointo-server-app pytest \ tests/unit/services/test_storyboard_board_service.py \ tests/integration/test_storyboard_board_api.py \ -v ``` ## 🎯 后续优化建议 1. **性能测试**: 测试大量分镜(100+)的性能表现 2. **并发测试**: 测试多用户同时重新排序的并发安全性 3. **其他轨道**: 实现 resource/video/sound/subtitle/voice 轨道的关联数据查询 4. **压力测试**: 测试时间范围查询的性能(大量分镜场景) --- **维护者**: Jointo 开发团队 **最后更新**: 2026-02-04