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.
 

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.pytest_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-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() 方法

修复:

# ❌ 错误(之前)
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 自动回滚

优点:

  • 测试隔离
  • 数据可控
  • 自动清理

📝 相关文档

🚀 运行命令

运行单元测试

# 运行所有单元测试
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

🎯 后续优化建议

  1. 性能测试: 测试大量分镜(100+)的性能表现
  2. 并发测试: 测试多用户同时重新排序的并发安全性
  3. 其他轨道: 实现 resource/video/sound/subtitle/voice 轨道的关联数据查询
  4. 压力测试: 测试时间范围查询的性能(大量分镜场景)

维护者: Jointo 开发团队
最后更新: 2026-02-04