"""封面帧质量评分器测试 — cover_frame_scorer. 测试维度: 1. score_frame: 清晰度/亮度/色彩丰富度各维度评分 2. score_frames: 批量评分和排序 3. select_best_frame: 选出最佳帧 4. 降级策略:cv2 不可用时返回默认分 5. 边界条件:空帧、None、损坏数据 """ from __future__ import annotations import inspect import numpy as np import pytest def _cv2_available() -> bool: """检测 cv2 是否真实可用(非 mock).""" try: import cv2 import numpy as np if not hasattr(cv2, "Laplacian") or not inspect.isroutine(cv2.Laplacian): return False if "mock" in str(type(cv2.Laplacian)).lower(): return False # 实际调用测试 _test = np.zeros((2, 2, 3), dtype=np.uint8) _result = cv2.cvtColor(_test, cv2.COLOR_BGR2GRAY) return isinstance(_result, np.ndarray) except Exception: return False HAS_CV2 = _cv2_available() requires_cv2 = pytest.mark.skipif(not HAS_CV2, reason="cv2 不可用或是 mock 对象") class TestScoreFrame: """score_frame 单元测试.""" @requires_cv2 def test_clear_image_high_score(self): """清晰、亮度适中、色彩丰富的图像应得较高分.""" # 创建一个清晰的渐变图像(色彩丰富、亮度适中) img = np.zeros((100, 100, 3), dtype=np.uint8) for i in range(100): for j in range(100): img[i, j] = [i * 2, j * 2, (i + j) % 256] from packages.shared.cover_frame_scorer import score_frame score = score_frame(img) # 渐变图清晰度中等+亮度尚可+色彩有变化,分数应明显高于模糊/全黑/全白 assert 40.0 <= score <= 100.0, f"清晰图像应得较高分,实际: {score}" @requires_cv2 def test_blurry_image_low_clarity(self): """模糊图像的清晰度分数应较低.""" # 纯色图像(无高频细节) img = np.full((100, 100, 3), 128, dtype=np.uint8) from packages.shared.cover_frame_scorer import score_frame score = score_frame(img) # 纯色图像清晰度为 0,亮度满分 30,色彩为 0 assert score <= 35.0, f"模糊图像应低分,实际: {score}" @requires_cv2 def test_dark_image_low_brightness(self): """过暗图像应扣分.""" # 全黑图像 img = np.zeros((100, 100, 3), dtype=np.uint8) from packages.shared.cover_frame_scorer import score_frame score = score_frame(img) # 全黑:清晰度 0,亮度偏离130扣约24分,色彩 0 → 得分约0~7,允许cv2内部微小浮点差异 assert score <= 10.0, f"全黑图像应接近 0 分,实际: {score}" @requires_cv2 def test_bright_image_low_brightness(self): """过亮图像应扣分.""" # 全白图像 img = np.full((100, 100, 3), 255, dtype=np.uint8) from packages.shared.cover_frame_scorer import score_frame score = score_frame(img) # 全白:清晰度 0(无边缘),亮度偏低(偏离 130),色彩 0 assert score <= 20.0, f"全白图像应较低分,实际: {score}" @requires_cv2 def test_medium_brightness_full_score(self): """亮度在 80~180 区间应得亮度满分.""" # 中等亮度灰色 img = np.full((100, 100, 3), 130, dtype=np.uint8) # 加一些纹理增加清晰度 for i in range(0, 100, 10): img[i : i + 5, :] = 180 from packages.shared.cover_frame_scorer import score_frame score = score_frame(img) # 亮度应在舒适区间 assert score >= 25.0, f"中等亮度图像应有一定分数,实际: {score}" @requires_cv2 def test_colorful_image_high_color_score(self): """色彩丰富的图像应得高色彩分.""" # 彩虹渐变 img = np.zeros((100, 100, 3), dtype=np.uint8) for i in range(100): img[i, :, 0] = int(i * 2.55) # R img[i, :, 1] = int((100 - i) * 2.55) # G img[:, i, 2] = int(i * 2.55) # B from packages.shared.cover_frame_scorer import score_frame score = score_frame(img) assert score >= 40.0, f"彩色图像应得高分,实际: {score}" @requires_cv2 def test_empty_frame_returns_zero(self): """空帧返回 0 分(有 cv2)或默认分(无 cv2).""" from packages.shared.cover_frame_scorer import score_frame result1 = score_frame(np.array([])) result2 = score_frame(None) if HAS_CV2: assert result1 == 0.0 assert result2 == 0.0 else: assert result1 == 50.0 assert result2 == 50.0 @requires_cv2 def test_score_range(self): """评分必须在 0~100 范围内.""" from packages.shared.cover_frame_scorer import score_frame # 各种极端情况 for val in [0, 50, 128, 200, 255]: img = np.full((50, 50, 3), val, dtype=np.uint8) score = score_frame(img) assert 0.0 <= score <= 100.0, f"评分 {score} 超出范围 [0, 100]" class TestScoreFrames: """score_frames 批量评分测试.""" @requires_cv2 def test_returns_sorted_by_score(self): """返回结果应按分数从高到低排序.""" from packages.shared.cover_frame_scorer import score_frames frames = [ {"image_array": np.full((50, 50, 3), 128, dtype=np.uint8), "id": "mid"}, {"image_array": np.zeros((50, 50, 3), dtype=np.uint8), "id": "dark"}, ] # 添加一个清晰帧 clear = np.zeros((50, 50, 3), dtype=np.uint8) for i in range(50): clear[i, :, :] = i * 5 frames.insert(0, {"image_array": clear, "id": "clear"}) scored = score_frames(frames) assert len(scored) == 3 # 第一个应该是分数最高的 assert scored[0]["score"] >= scored[1]["score"] assert scored[1]["score"] >= scored[2]["score"] @requires_cv2 def test_all_have_score_field(self): """每个帧都应该有 score 字段.""" from packages.shared.cover_frame_scorer import score_frames frames = [ {"image_array": np.full((30, 30, 3), 100, dtype=np.uint8)}, {"image_array": np.full((30, 30, 3), 200, dtype=np.uint8)}, ] scored = score_frames(frames) for f in scored: assert "score" in f assert isinstance(f["score"], float) def test_empty_list(self): """空列表返回空列表.""" from packages.shared.cover_frame_scorer import score_frames assert score_frames([]) == [] class TestSelectBestFrame: """select_best_frame 测试.""" @requires_cv2 def test_returns_highest_score(self): """返回分数最高的帧.""" from packages.shared.cover_frame_scorer import select_best_frame frames = [ {"image_array": np.zeros((30, 30, 3), dtype=np.uint8), "id": "dark"}, {"image_array": np.full((30, 30, 3), 128, dtype=np.uint8), "id": "mid"}, ] best = select_best_frame(frames) assert best is not None # 中等亮度帧应该得分更高 assert best["id"] == "mid" def test_empty_returns_none(self): """空列表返回 None.""" from packages.shared.cover_frame_scorer import select_best_frame assert select_best_frame([]) is None @requires_cv2 def test_single_frame(self): """单帧直接返回.""" from packages.shared.cover_frame_scorer import select_best_frame frames = [{"image_array": np.full((30, 30, 3), 128, dtype=np.uint8), "id": "only"}] best = select_best_frame(frames) assert best is not None assert best["id"] == "only"