PyCC2 采用经典的测试金字塔策略,强调底层单元测试为主,顶层E2E测试为辅:
╱╲
╱E2E╲ 9.0% (621 tests)
╱──────╲
╱ 集成测试 ╲ 2.6% (181 tests)
╱──────────╲
╱ ╲
╱ 单元测试 ╲ 87.5% (6029 tests)
╱──────────────╲
数据来源:
pytest --collect-only实测(2026-08-01,v0.9.0)。 Marker 通过conftest.py的pytest_collection_modifyitems钩子按目录自动推断, 显式@pytest.mark.slow等正交标记保留不覆盖。
| 层级 | 数量 | 占比 | 执行时间 | 负责人 | 频率 | Marker |
|---|---|---|---|---|---|---|
| 单元测试 (Unit) | 6029 | 87.5% | ~4min | 核心开发 | 每次commit | @pytest.mark.unit(路径自动) |
| 集成测试 (Integration) | 181 | 2.6% | < 3min | 核心开发 | 每次PR | @pytest.mark.integration(路径自动) |
| 端到端测试 (E2E) | 621 | 9.0% | < 10min | QA团队 | 每日构建 | @pytest.mark.e2e(路径自动) |
| 性能基准 (Benchmark) | 21 | 0.3% | < 2min | 核心开发 | 每日构建 | @pytest.mark.benchmark(路径自动) |
| 验收测试 (Acceptance) | 42 | 0.6% | < 2min | QA团队 | 每次发布 | @pytest.mark.acceptance(路径自动) |
| 慢测试 (Slow,正交) | 16 | 0.2% | ~3min | 核心开发 | CI slow job | @pytest.mark.slow(显式) |
总计: 6894 个测试用例(v0.9.0 全量,含 slow 16 个;pytest -m "not slow" 基线 6891 passed / 3 skipped / 2 xfailed / 23 deselected,参考 PROJECT_STATUS.md)
数据校准说明: 2026-07-14 通过
pytest tests/{unit,integration,e2e,benchmark,acceptance}/ --co -q逐目录实测验证。单元测试数从原 5494 更新为 5817(v0.6.10 覆盖率提升新增 323 测试主要落入 unit 目录)。v0.9.0 校准(2026-08-01):总计 6552→6819(+267 = unit +212 / e2e +55),E2E 491→546,单元 5817→6029,数据来源 PROJECT_STATUS.md 实测pytest -m "not slow"6819 passed。E2E 盲区补齐(2026-08-01):E2E 546→621(+75),总计 6819→6894(+75),新增 12 个 E2E 文件覆盖音频/战役多天/存档安全/安装启动/战斗特殊命令/AI高级战术/UI细节/视觉基础/多地图/长时稳定性/错误恢复/跨场景连续性。
Marker 策略(Phase 5 新增):
- 路径自动标记:
tests/unit/→unit,tests/integration/→integration,tests/e2e/→e2e,tests/benchmark/→benchmark - 慢测试显式标记:sprite 生成类测试(
test_pixel_artist.py/test_content_expansion.py中超时>30s 的类)标记@pytest.mark.slow,默认pytest -m "not slow"跳过 - CI 门禁:
pytest -m "not slow"阻塞式全绿;pytest -m slow在 slow job 中全绿 --strict-markers已启用,未注册 marker 会报错
目标: 验证单个函数/类的正确性
特征:
- ✅ 无外部依赖(mock所有I/O)
- ✅ 独立运行,无执行顺序依赖
- ✅ 快速 (< 100ms/个)
- ✅ 覆盖正常/边界/异常路径
- ✅ TDD驱动开发
示例结构:
# tests/unit/test_vec2.py
class TestVec2Addition:
def test_add_positive_numbers(self):
v1 = Vec2(1, 2)
v2 = Vec2(3, 4)
result = v1 + v2
assert result == Vec2(4, 6)
def test_add_negative_numbers(self):
v1 = Vec2(-1, -2)
v2 = Vec2(-3, -4)
assert v1 + v2 == Vec4(-6)
def test_add_zero_vector(self):
v = Vec2(5, 10)
assert v + Vec2(0, 0) == v
def test_add_commutative_property(self):
a = Vec2(1, 2)
b = Vec2(3, 4)
assert a + b == b + a目标: 验证模块间协作的正确性
特征:
⚠️ 允许部分真实依赖(如文件系统、数据库)⚠️ 测试模块间接口契约⚠️ 中等速度 (100ms - 1s/个)⚠️ 关注数据流和状态转换
示例结构:
# tests/integration/test_combat_flow.py
class TestCombatIntegration:
def setup_method(self):
self.game = create_test_game()
self.attacker = spawn_unit(self.game, "rifle_squad_us", pos=(5, 5))
self.target = spawn_unit(self.game, "rifle_squad_ge", pos=(15, 5))
def test_full_attack_cycle(self):
"""完整攻击流程:命令→瞄准→射击→命中→伤害→死亡"""
cmd = AttackCommand(attacker_id=self.attacker.id, target_id=self.target.id)
self.game.execute_command(cmd)
# 验证弹药减少
assert self.attacker.ammo["m1_garand"] == 7 # 8 - 1
# 验证目标受伤或死亡
assert self.target.hp < 100 or not self.target.is_alive
def test_suppression_morale_interaction(self):
"""压制与士气系统的交互"""
# 连续攻击导致压制累积
for _ in range(10):
self.game.execute_command(AttackCommand(...))
# 验证士气下降
assert self.target.morale < 80目标: 验证完整的用户场景
特征:
- 🔴 完整系统运行(含pygame渲染)
- 🔴 模拟真实用户操作序列
- 🔴 较慢 (1-3min/scenario)
- 🔴 关注业务价值和用户体验
- 🔴 使用真实配置和数据文件
场景列表:
- Tutorial Scenario: 完成教学任务(占领中心建筑)
- Combat Scenario: 完整战斗流程(双方交火至一方获胜)
- Save/Load Scenario: 存档→退出→加载→继续游戏
Level 1 (基础):
├── Vec2 (25 tests) ← 无依赖
├── TileCoord (15 tests) ← 依赖 Vec2
└── TerrainType (12 tests) ← 无依赖
Level 2 (核心):
├── StateMachine (20 tests) ← 无依赖
├── RandomContext (10 tests) ← 无依赖
├── BallisticEngine (35 tests)← 依赖 RandomContext, TerrainType
└── MoraleCalculator (30 tests)← 依赖 StateMachine
Level 3 (系统):
├── PathFinder (25 tests) ← 依赖 Vec2, TileCoord, TerrainType
├── FogOfWar (20 tests) ← 依赖 TileCoord, TerrainType
├── CombatResolver (20 tests)← 依赖 BallisticEngine, MoraleCalculator
├── BehaviorTree (20 tests) ← 依赖 StateMachine
└── EventBus (15 tests) ← 无依赖
⚠️ v1.1 共识修复 — API 对齐说明:实际实现的 BallisticEngine 主入口为
calculate_shot(attacker: Unit, target: Unit, weapon_slot: str, game_map: GameMap) -> ShotResult, 这是一个高层统一接口(内部依次执行 LOS检查 → 距离衰减 → 掩体修正 → RNG命中 → 高斯散布 → 伤害公式 → 压制累积)。以下测试用例中的
calculate_hit(weapon, pos, cover)等简化API表示法应理解为对calculate_shot()内部各阶段的拆分验证:
- BE-HIT-01~08 (命中): 验证
calculate_shot()返回的ShotResult.hit和actual_accuracy- BE-DMG-01~08 (伤害): 验证
ShotResult.damage_dealt- BE-SPR-01~06 (散布): 验证
ShotResult.reason中的散布信息- BE-EDGE-01~08 (边缘): 验证异常输入的
ShotResult完整性- BE-STAT-01~08 (统计): 多次调用
calculate_shot()的聚合统计特性测试代码应使用实际
calculate_shot()签名,通过构造 mock Unit/GameMap 对象传入参数。
| Test ID | 测试名称 | 输入条件 | 预期输出 | 优先级 | 备注 |
|---|---|---|---|---|---|
| BE-HIT-01 | 有效范围命中 | distance=300m, range_effective=400m, accuracy_base=0.75, cover=0 | hit_probability > 0.6 | P0 | 在有效范围内应有较高命中率 |
| BE-HIT-02 | 最大距离未命中 | distance=550m (=range_max), accuracy_base=0.75 | hit_probability < 0.3 | P0 | 接近最大射程时命中率大幅下降 |
| BE-HIT-03 | 完美精度 | accuracy_base=1.0, distance=0, cover=0 | hit_probability ≥ 0.95 | P0 | 理想条件下应几乎必中 |
| BE-HIT-04 | Cover降低命中率 | base_acc=0.75, cover_bonus=0.5 | hit_with_cover < hit_without_cover | P0 | 掩体应显著降低命中率 |
| BE-HIT-05 | 确定同seed结果一致 | seed=42, 相同输入调用两次 | 结果完全相同 | P0 | 同一seed必须产生相同结果(确定性) |
| BE-HIT-06 | 极端cover满命中 | cover=1.0 (完全掩体), distance<effective_range | hit_probability ≈ 0 | P1 | 完全掩体下几乎不可能被命中 |
| BE-HIT-07 | 统计验证命中率 | 1000次射击, 理论命中率50% | 实际命中率 ∈ [45%, 55%] | P0 | 大数定律验证统计正确性 |
| BE-HIT-08 | 零距离特殊处理 | distance=0 (贴脸) | hit_probability > 0.9 | P1 | 近距离应保证高命中率 |
测试代码示例:
import pytest
from pycc2.combat.ballistic import BallisticEngine
from pycc2.core.random import RandomContext
class TestBasicHit:
def test_hit_within_effective_range(self):
"""BE-HIT-01: 有效范围内应有较高命中率"""
rng = RandomContext(seed=42)
engine = BallisticEngine(rng=rng)
weapon = create_test_weapon(range_effective=400, accuracy_base=0.75)
target_pos = Vec2(300, 0) # 300米距离
cover_value = 0.0
hits = sum(engine.calculate_hit(weapon, target_pos, cover_value) for _ in range(100))
hit_rate = hits / 100
assert hit_rate > 0.6, f"Hit rate {hit_rate:.2f} too low"
def test_miss_at_max_range(self):
"""BE-HIT-02: 最大距离命中率低"""
rng = RandomContext(seed=42)
engine = BallisticEngine(rng=rng)
weapon = create_test_weapon(range_max=550, range_effective=400, accuracy_base=0.75)
target_pos = Vec2(550, 0)
hits = sum(engine.calculate_hit(weapon, target_pos, cover=0.0) for _ in range(200))
hit_rate = hits / 200
assert hit_rate < 0.3, f"Hit rate at max range {hit_rate:.2f} too high"
def test_deterministic_same_seed(self):
"""BE-HIT-05: 相同seed产生相同结果"""
weapon = create_test_weapon()
target = Vec2(100, 0)
results_1 = [
BallisticEngine(RandomContext(seed=123)).calculate_hit(weapon, target, 0.0)
for _ in range(50)
]
results_2 = [
BallisticEngine(RandomContext(seed=123)).calculate_hit(weapon, target, 0.0)
for _ in range(50)
]
assert results_1 == results_2, "Same seed must produce identical results"| Test ID | 测试名称 | 输入条件 | 预期输出 | 优先级 | 备注 |
|---|---|---|---|---|---|
| BE-DMG-01 | 最优距离满伤 | distance=optimal, penetration足够 | damage = damage_base (或接近) | P0 | 最佳距离应造成满伤 |
| BE-DMG-02 | 距离衰减 | distance从optimal增加到max | damage单调递减 | P0 | 伤害应随距离增加而降低 |
| BE-DMG-03 | 穿透力影响 | 高penetration vs 低armor vs 高armor | 高穿>低穿, armor降低伤害 | P0 | 穿透力应影响对装甲目标的伤害 |
| BE-DMG-04 | 最低1伤 | 任何命中情况 | damage ≥ 1 | P0 | 命中至少造成1点伤害 |
| BE-DMG-05 | 致命击杀标记 | damage >= target.current_hp | is_kill_shot = True | P0 | 超过剩余HP的伤害应标记为击杀 |
| BE-DMG-06 | 伤害方差±20% | 多次相同条件射击 | std/mean ∈ [0, 0.2] | P1 | 伤害应有合理随机波动 |
| BE-DMG-07 | 高穿透满伤 | penetration >> target_armor | damage不受armor显著影响 | P1 | 足够穿透时应忽略装甲 |
| BE-DMG-08 | 距离修正公式验证 | 特定距离点 | 符合设计文档的衰减曲线 | P0 | 数学公式需精确实现 |
伤害衰减公式(参考):
其中:
decay_factor= 0.5 (默认距离衰减系数)armor_modifier=max(0.1, 1 - armor / (penetration * 2))
| Test ID | 测试名称 | 输入条件 | 预期输出 | 优先级 | 备注 |
|---|---|---|---|---|---|
| BE-SPR-01 | 散布近目标 | distance=50m, spread=5° | miss_offset小 (< 5px) | P0 | 近距离散布小 |
| BE-SPR-02 | 距离增大散布 | distance从50m增加到400m | offset随距离增大而增大 | P0 | 散布与距离成正比 |
| BE-SPR-03 | 高斯分布检验 | 1000次miss采样 | X/Y坐标符合正态分布 | P1 | 统计检验散布分布 |
| BE-SPR-04 | 未命中压制 | miss事件发生 | suppression_value > 0 但 < hit的suppression | P1 | 未中也造成少量压制 |
| BE-SPR-05 | 压制值小于命中 | compare(miss.suppression, hit.suppression) | miss_suppression < hit_suppression | P0 | 命中造成的压制更高 |
| BE-SPR-06 | Miss位置不进target | miss_position vs target_center | distance(miss_pos, target) > target_radius | P0 | 未命中的弹着点不在目标体内 |
散布计算公式:
| Test ID | 测试名称 | 输入条件 | 预期输出 | 优先级 | 备注 |
|---|---|---|---|---|---|
| BE-EDGE-01 | 零距离 | distance=0 | 必中, 满伤, 极小散布 | P0 | 贴脸射击特例 |
| BE-EDGE-02 | 负cover | cover=-0.5 (非法值) | clamp到0 或 raise ValueError | P0 | 非法参数处理 |
| BE-EDGE-03 | 极端cover满 | cover=1.5 (>1.0) | clamp到1.0 | P0 | 参数上界约束 |
| BE-EDGE-04 | 武器无弹 | ammo=0 | 返回CannotFireError 或 (hit=False, damage=0) | P0 | 弹药耗尽处理 |
| BE-EDGE-05 | OOM保护 | 极大次数循环(100万次) | 内存使用稳定, 不泄漏 | P1 | 性能边界测试 |
| BE-EDGE-06 | 多次射击序列确定性 | 固定seed连续射击N次 | 序列可复现 | P0 | 回放系统需求 |
| BE-EDGE-07 | Result不可变 | calculate_hit返回的对象 | 修改result不影响后续调用 | P1 | 防御性编程 |
| BE-EDGE-08 | 统计验证伤害分布 | 5000次射击直方图 | 符合理论期望分布 (χ²检验 p>0.05) | P0 | 分布正确性验证 |
| Test ID | 测试名称 | 方法 | 通过标准 | 优先级 |
|---|---|---|---|---|
| BE-STAT-01 | 命中率大数验证 | 1000次射击统计 | |实际-理论| < 5% | P0 |
| BE-STAT-02 | 伤害均值验证 | 1000次命中伤害均值 | |mean - E[damage]| / E[damage] < 10% | P0 |
| BE-STAT-03 | 压制累积验证 | 连续射击N次的累积压制值 | 符合线性/非线性累积模型 | P1 |
| BE-STAT-04 | 距离-命中率曲线 | 多距离点采样 | 曲线平滑单调递减 | P1 |
| BE-STAT-05 | Cover-命中率关系 | 不同cover值采样 | cover↑ → hit_rate↓ (负相关) | P0 |
统计测试框架代码:
from scipy import stats
import numpy as np
class TestStatisticalValidation:
def test_hit_rate_converges_to_theoretical(self):
"""BE-STAT-01: 大数定律验证命中率"""
engine = BallisticEngine(RandomContext(seed=999))
weapon = create_test_weapon(accuracy_base=0.70)
theoretical_hit_rate = 0.70 # 理论值 (简化)
n_shots = 1000
results = [engine.calculate_hit(weapon, Vec2(200, 0), 0.0) for _ in range(n_shots)]
actual_hit_rate = sum(results) / n_shots
error = abs(actual_hit_rate - theoretical_hit_rate)
assert error < 0.05, f"Hit rate error {error:.3f} exceeds 5% threshold"
def test_damage_distribution_chi_square(self):
"""BE-STAT-08: 卡方检验伤害分布"""
engine = BallisticEngine(RandomContext(seed=777))
weapon = create_test_weapon(damage_base=35)
damages = []
for _ in range(5000):
result = engine.fire(weapon, Vec2(100, 0), 0.0)
if result.hit:
damages.append(result.damage)
if len(damages) < 100:
pytest.skip("Not enough hits for statistical test")
# 假设伤害服从正态分布 N(μ=30, σ=5)
mean_damage = np.mean(damages)
std_damage = np.std(damages)
# Shapiro-Wilk正态性检验 (样本量<5000时适用)
if len(damages) < 5000:
stat, p_value = stats.shapiro(damages[:5000])
assert p_value > 0.05, f"Distribution not normal (p={p_value:.4f})"| 分类 | 测试数量 | P0 | P1 | 预估耗时 |
|---|---|---|---|---|
| 基础命中 | 8 | 6 | 2 | 0.5s |
| 伤害计算 | 8 | 6 | 2 | 0.6s |
| 散布未命中 | 6 | 4 | 2 | 0.8s |
| 边缘条件 | 8 | 6 | 2 | 0.4s |
| 统计验证 | 5 | 3 | 2 | 2.0s (大数据量) |
| 总计 | 35 | 25 | 10 | ~4.3s |
MoraleCalculator 管理单位士气系统,包括事件响应、状态转换、传染机制等。
| Test ID | 事件类型 | 权重变化 | 初始士气 | 预期结果 | 优先级 |
|---|---|---|---|---|---|
| MC-EVT-01 | ally_killed (友军阵亡) | -15 | 100 | 85 | P0 |
| MC-EVT-02 | leader_killed (指挥官阵亡) | -25 | 100 | 75 | P0 |
| MC-EVT-03 | heavy_fire (遭受猛攻) | -5/tick | 90 | 85 (1tick后) | P0 |
| MC-EVT-04 | explosion (爆炸冲击) | -20 | 100 | 80 | P0 |
| MC-EVT-05 | kill_confirmed (确认击杀) | +8 | 70 | 78 | P0 |
| MC-EVT-06 | rally (集结号令) | +3 | 50 | 53 | P1 |
| MC-EVT-07 | in_cover (进入掩体) | +5/tick | 60 | 65 (1tick后) | P0 |
| MC-EVT-08 | commander_nearby (指挥官在旁) | +10 | 70 | 80 | P0 |
| MC-EVT-09 | panic_contagion (恐慌传染) | -10 | 50 | 40 | P0 |
| MC-EVT-10 | unknown_event (未知事件) | 0 | 80 | 80 (不变) | P1 |
| MC-EVT-11 | magnitude_scaling (量级缩放) | weight × magnitude | base_weight=10, mag=2.0 | -20 | P1 |
| MC-EVT-12 | 复合事件同时发生 | 多事件叠加 | 顺序应用 | 最终值正确 | P0 |
测试代码示例:
class TestMoraleEvents:
def test_ally_killed_reduces_morale(self):
"""MC-EVT-01: 友军阵亡降低士气"""
calc = MoraleCalculator(initial_morale=100)
calc.apply_event("ally_killed")
assert calc.current_morale == 85
def test_leader_killed_bigger_penalty(self):
"""MC-EVT-02: 指挥官阵亡惩罚更大"""
calc = MoraleCalculator(initial_morale=100)
calc.apply_event("leader_killed")
assert calc.current_morale == 75 # -25 > -15
def test_kill_confirmed_increases_morale(self):
"""MC-EVT-05: 确认击杀提升士气"""
calc = MoraleCalculator(initial_morale=70)
calc.apply_event("kill_confirmed")
assert calc.current_morale == 78 # +8
def test_unknown_event_no_change(self):
"""MC-EVT-10: 未知事件不影响士气"""
calc = MoraleCalculator(initial_morale=80)
calc.apply_event("nonexistent_event")
assert calc.current_morale == 80Morale状态机: normal → suppressed → panic → routing
| Test ID | 转换路径 | 触发条件 | 预期状态 | 优先级 |
|---|---|---|---|---|
| MC-STATE-01 | normal → panic | morale ≤ 30 | state = 'panic' | P0 |
| MC-STATE-02 | at_threshold_exact | morale = 30 (恰好阈值) | state = 'panic' | P0 |
| MC-STATE-03 | routing不可恢复 | state='routing', +morale event | 保持routing | P0 |
| MC-STATE-04 | suppressed状态 | morale ≤ 50 且 > 30 | state = 'suppressed' | P0 |
| MC-STATE-05 | suppression_decay | suppressed状态下无负面事件 | morale缓慢恢复 | P1 |
| MC-STATE-06 | natural_recovery_only_after_30ticks | 从panic恢复需要≥30ticks | tick<30时不恢复 | P0 |
| MC-STATE-07 | clamped_at_100 | morale +event > 100 | morale = 100 (不上溢) | P0 |
| MC-STATE-08 | floor_at_0 | morale -event < 0 | morale = 0 (不下溢) | P0 |
| MC-STATE-09 | state_order | 状态只能按 normal→suppressed→panic→routing 单向转换 | 不能跳级或回退 | P0 |
| MC-STATE-10 | recovery_from_suppressed | suppressed → morale>50 | 回到normal | P0 |
状态机图示:
┌─────────────────────────────────┐
│ │
▼ │
[normal: 51-100] ──→ [suppressed: 31-50] ──→ [panic: 0-30]
▲ │
│ │
│ (30 ticks cooldown) │
│ ▼
└──────── [recovery] ◄────────── [routing: 不可恢复]
(morale≤0持续>60ticks)
恐慌可以在相邻单位间传播。
| Test ID | 场景 | 条件 | 预期结果 | 优先级 |
|---|---|---|---|---|
| MC-CONTAG-01 | 无panic单位不传染 | 所有单位normal | 无传染发生 | P0 |
| MC-CONTAG-02 | 传染给所有非panic邻居 | 1个panic + 3个normal邻居(距离<5) | 3个都受影响 | P0 |
| MC-CONTAG-03 | 已panic单位不受重复扣减 | 已panic单位再次接触传染源 | 不额外扣减 | P0 |
| MC-CONTAG-04 | 链式传染 | A→B→C→D (都在范围内) | 全部最终panic | P1 |
传染算法规则:
- 传染范围: Manhattan距离 ≤ 4 tiles
- 传染概率: 基础30%, 受距离和cover调整
- 传染效果: -10士气 (可触发连锁panic)
- 冷却时间: 单位每10tick最多被传染1次
| Test ID | 测试内容 | 条件 | 预期行为 | 优先级 |
|---|---|---|---|---|
| MC-TICK-01 | decay_suppression | suppressed, 无combat, 10ticks | morale += 5 (假设+0.5/tick) | P0 |
| MC-TICK-02 | no_recovery_during_combat | suppressed, 正在被攻击 | morale不恢复或恢复极慢 | P0 |
| MC-TICK-03 | recovery_after_30ticks | panic → 等待30ticks → 开始恢复 | tick 30后morale开始增加 | P0 |
| MC-TICK-04 | recovery_rate & clamping | 恢复速度验证 | rate符合设计, max=100 | P1 |
恢复速率表:
| 当前状态 | 每tick恢复 | 条件 |
|---|---|---|
| Normal | +0 (保持) | 无事件时稳定 |
| Suppressed | +0.5 | 无combat事件 |
| Panic | 0 (前30tick) / +0.2 (30tick后) | 需要冷却期 |
| Routing | 0 | 不可恢复 |
| 分类 | 测试数量 | P0 | P1 | 预估耗时 |
|---|---|---|---|---|
| 事件权重 | 12 | 9 | 3 | 0.3s |
| 状态转换 | 10 | 8 | 2 | 0.4s |
| 传染机制 | 4 | 3 | 1 | 0.2s |
| Tick恢复 | 4 | 3 | 1 | 0.3s |
| 总计 | 30 | 23 | 7 | ~1.2s |
PathFinder 基于A*算法,支持地形代价、动态障碍物等。
| Test ID | 测试名称 | 地图设置 | 预期结果 | 优先级 |
|---|---|---|---|---|
| PF-BASIC-01 | 直线无障碍 | 10x10空地, (0,0)→(9,9) | 路径存在, length≈18 (Manhattan) | P0 |
| PF-BASIC-02 | 绕障行走 | 中间有墙, 需绕行 | 路径避开障碍, length > 直线距离 | P0 |
| PF-BASIC-03 | 对角线代价 | 允许对角移动 | 对角步代价 = √2 × 直线代价 | P0 |
| PF-BASIC-04 | 不可达目标 | 目标在封闭区域内 | 返回None或抛出NoPathError | P0 |
| PF-BASIC-05 | 起点==终点 | (5,5)→(5,5) | 返回空路径 [起点] 或长度0 | P1 |
| PF-BASIC-06 | 相邻两点 | (3,3)→(3,4) | 路径长度=2 (含起点终点) | P1 |
| PF-BASIC-07 | 启发式可容 | h(n) ≤ actual_cost(n→goal) | A*最优性保证 | P0 |
| PF-BASIC-08 | 启发式一致 | h(n) ≤ cost(n,n') + h(n') | 无多余节点展开 | P0 |
| Test ID | 场景 | 特殊条件 | 预期 | 优先级 |
|---|---|---|---|---|
| PF-TER-01 | 绕墙 | L形墙壁阻挡 | 路径沿墙边绕行 | P0 |
| PF-TER-02 | 穿隙 | 两墙之间有1格间隙 | 能穿过间隙 | P0 |
| PF-TER-03 | 优先道路 | 有ROAD(代价0.8)和GRASS(代价1.2) | 优选道路即使稍远 | P1 |
| PF-TER-04 | 避开水域 | 必须跨越河流(有桥) | 路径经过桥梁tile | P0 |
| PF-TER-05 | 建筑阻塞 | BUILDING_SOLID区域 | 路径不穿过建筑 | P0 |
| PF-TER-06 | 大地图性能 | 256×256地图, 跨地图寻路 | < 100ms完成 | P0 |
| PF-TER-07 | 动态blockage | 寻路过程中临时添加障碍 | 支持重新规划或失败 | P1 |
| Test ID | 质量指标 | 标准 | 通过条件 | 优先级 |
|---|---|---|---|---|
| PF-QUAL-01 | 无环路 | 路径中无重复节点 | all unique | P0 |
| PF-QUAL-02 | 单调性 | 每步距目标曼哈顿距离不减 | non-increasing distance to goal | P1 |
| PF-QUAL-03 | 最优性*110% | path_cost ≤ optimal * 1.10 | 接近最优解 | P0 |
| PF-QUAL-04 | 平滑度 | 方向改变次数少 | turns ≤ length / 3 | P2 |
| PF-QUAL-05 | 安全边距 | 不紧贴危险地形 | 与WALL/WATER保持≥1格 | P1 |
| Test ID | 异常输入 | 预期处理 | 优先级 |
|---|---|---|---|
| PF-EDGE-01 | 越界坐标 | start=(-1, 0) 或 goal=(300, 300) | ValueError / Clamp / None |
| PF-EDGE-02 | 空地图 | 0×0地图 | ValueError |
| PF-EDGE-03 | 单tile地图 | 1×1地图, (0,0)→(0,0) | 空路径 |
| PF-EDGE-04 | 缓存失效 | 地形修改后重新寻路 | 不使用过期缓存 |
| PF-EDGE-05 | 极端代价地形 | movement_cost=10.0 | 仍能找到路径(如果可达) |
| 分类 | 测试数量 | P0 | P1 | P2 | 预估耗时 |
|---|---|---|---|---|---|
| 基本功能 | 8 | 6 | 2 | 0 | 0.5s |
| 复杂地形 | 7 | 5 | 1 | 1 | 1.5s (含大地图) |
| 路径质量 | 5 | 2 | 2 | 1 | 0.8s |
| 边缘条件 | 5 | 4 | 1 | 0 | 0.3s |
| 总计 | 25 | 17 | 6 | 2 | ~3.1s |
| 文件路径 | 测试类数量 | 主要测试内容 | 依赖模块 | 预估时间 |
|---|---|---|---|---|
tests/integration/test_game_loop.py |
5 | 主循环启动/暂停/步进 | EventBus, StateMachine | 0.5s |
tests/integration/test_save_load.py |
8 | 存档读写完整性 | SecureIO, All Models | 1.0s |
tests/integration/test_combat_scenario.py |
8 | 完整战斗流程 | CombatResolver, BallisticEngine, Morale | 2.0s |
tests/integration/test_pathfinding_real_map.py |
5 | 真实地图上的寻路 | PathFinder, TileMapConfig | 1.5s |
tests/integration/test_fog_of_war.py |
5 | FoW与单位视野交互 | FogOfWar, Unit, Terrain | 1.0s |
tests/integration/test_ui_input.py |
5 | 输入→命令→状态链路 | InputHandler, CommandBus, Game | 0.8s |
tests/integration/test_mod_loading.py |
4 | Mod加载与注册 | ModLoader (v2.0) | 0.5s |
tests/integration/test_combat_loop.py |
13 | Combat loop全链路: attack/damage/death/move/stop/constraints | CombatLoop, BallisticEngine, Effects, Weapon | 1.5s |
| 合计 | 53 | - | - | ~9.3s |
| 文件路径 | 测试数量 | 主要测试内容 | 阶段 |
|---|---|---|---|
tests/unit/test_vec2.py |
25 | Vec2向量运算 | P0 |
tests/unit/test_tile_coord.py |
15 | TileCoord坐标系统 | P0 |
tests/unit/test_terrain_type.py |
12 | 地形类型枚举与代价 | P0 |
tests/unit/test_state_machine.py |
20 | 状态机转换 | P0 |
tests/unit/test_random_context.py |
10 | 确定性随机数 | P0 |
tests/unit/test_ballistic_engine.py |
35 | 弹道引擎: 命中/伤害/散布/边缘/统计 | P0-P1 |
tests/unit/test_morale_calculator.py |
30 | 士气计算: 事件/状态/传染/恢复 | P0-P1 |
tests/unit/test_pathfinder.py |
25 | A*寻路: 基本/地形/质量/边缘 | P0-P1 |
tests/unit/test_fog_of_war.py |
20 | 战争迷雾计算 | P1 |
tests/unit/test_combat_resolver.py |
20 | 战斗结算 | P1 |
tests/unit/test_behavior_tree.py |
20 | 行为树节点 | P1 |
tests/unit/test_event_bus.py |
15 | 事件总线发布/订阅 | P0 |
tests/unit/test_sound_system.py |
21 | 程序化音效生成、播放、音量控制、缓存 | P2 |
| 单元合计 | 268 | - | - |
集成测试示例:
# tests/integration/test_combat_scenario.py
class TestCombatScenario:
@pytest.fixture
def combat_setup(self):
"""创建一个标准的战斗测试场景"""
game = IntegrationTestGame(map_file="data/maps/tutorial.json")
allies_squad = game.spawn_squad("rifle_squad_us", pos=(3, 3), faction="allies")
axis_squad = game.spawn_squad("rifle_squad_ge", pos=(12, 8), faction="axis")
return {"game": game, "allies": allies_squad, "axis": axis_squad}
def test_full_engagement_until_victory(self, combat_setup):
"""完整交战直到一方获胜"""
game = combat_setup["game"]
# 盟军进攻
game.issue_command(MoveCommand(allies_squad.id, target=(10, 8)))
game.advance_ticks(50) # 移动到接近位置
# 开火
game.issue_command(AttackCommand(allies_squad.id, target=axis_squad.id))
# 模拟持续战斗直到一方全灭
max_ticks = 5000
ticks_elapsed = 0
while (allies_squad.is_alive and axis_squad.is_alive) and ticks_elapsed < max_ticks:
game.tick()
ticks_elapsed += 1
assert ticks_elapsed < max_ticks, "Combat did not conclude within time limit"
assert (not allies_squad.is_alive) or (not axis_squad.is_alive), "One side must be defeated"
def test_suppression_leading_to_panic(self, combat_setup):
"""验证压制导致恐慌的完整链条"""
game = combat_setup["game"]
target = combat_setup["axis"].units[0]
# 用机枪组持续压制
mg_squad = game.spawn_squad("mg_squad_us", pos=(10, 8), faction="allies")
game.issue_command(AttackCommand(mg_squad.id, target_unit=target.id))
for _ in range(200): # 持续压制
game.tick()
# 验证目标进入panic状态
assert target.morale_state == "panic", f"Expected panic, got {target.morale_state}"
assert target.morale <= 20目标: 新手玩家完成第一个任务(占领中心建筑)
前置条件:
- 加载 tutorial.json 地图
- 2个盟军小队 (步枪班+机枪组)
- 2个德军小队 (防御方)
- 目标: 占领坐标(8,8)的建筑
操作步骤:
steps:
- action: LOAD_MAP
params: { file: "tutorial.json" }
- action: SELECT_SQUAD
params: { squad_id: "allies_rifle_1" }
- action: ISSUE_COMMAND
params: { type: MOVE, target: (7, 7) }
- action: WAIT_TICKS
params: { count: 100 }
- action: SELECT_SQUAD
params: { squad_id: "allies_mg_1" }
- action: ISSUE_COMMAND
params: { type: MOVE, target: (9, 9) }
- action: WAIT_TICKS
params: { count: 100 }
- action: AUTO_COMBAT_ENABLE
params: {}
- action: WAIT_UNTIL_CONDITION
params: { condition: "objective_captured", timeout: 5000 }
expected_result:
mission_status: VICTORY
casualties_allies: <= 3
duration_ticks: < 5000
no_crashes: true
no_errors_logged: true通过标准:
- 任务状态为 VICTORY
- 盟军伤亡 ≤ 3 人
- 总用时 < 5000 ticks
- 无崩溃或未捕获异常
- 日志无ERROR级别记录
目标: 双方交火的完整战斗流程
场景描述:
- 32×32地图,混合地形
- 盟军: 2步枪班 + 1反坦克组
- 德军: 2步枪班 + 1机枪组
- 无特定目标,歼灭战
关键验证点:
validation_points:
- name: initial_deployment_correct
check: "All units at correct spawn positions"
- name: line_of_sight_works
check: "Units behind WALL cannot be targeted"
- name: ammunition_consumed
check: "Total ammo decreases as firing occurs"
- name: casualties_occur
check: "HP decreases and units die over time"
- name: morale_system_active
check: "Units enter suppressed/panic under heavy fire"
- name: fog_of_war_updates
check: "Hidden enemies revealed when in vision range"
- name: battle_concludes
check: "One side eliminated or retreats within time limit"
- name: save_during_combat
check: "Can save and resume mid-combat without desync"目标: 验证存档系统的完整性
步骤:
steps:
- action: START_NEW_GAME
params: { map: "mission_1.json" }
- action: PLAY_FOR_TICKS
params: { count: 1000 } # 游戏一段时间
- action: RECORD_STATE
params: {} # 记录当前完整状态快照
- action: SAVE_GAME
params: { slot: 1 }
- action: EXIT_TO_MENU
params: {}
- action: LOAD_GAME
params: { slot: 1 }
- action: VERIFY_STATE_MATCHES
params: { tolerance: { hp: 0, position: 0, ammo: 0, morale: 0, tick: 0 } }
- action: CONTINUE_PLAYING
params: { ticks: 100 }
- action: VERIFY_NO_DESYNC
params: {}严格匹配字段:
- 所有单位的 HP、位置、弹药、士气
- 游戏全局 tick 数
- Camera 位置和缩放
- 任务目标进度
- 战役状态
容差说明:
- HP: ±0 (必须精确匹配)
- Position: ±0 (tile坐标整数)
- Ammo: ±0 (整数计数)
- Morale: ±0 (精确状态)
- Tick: ±0 (必须完全一致)
目标: 验证战役四层架构的完整流程
场景描述:
- 从Grand Campaign开始,选择Sector → Operation → Battle
- 完成3场连续Battle,验证状态继承
- 验证补给线机制和增援触发
关键验证点:
validation_points:
- name: campaign_initialization
check: "Grand Campaign loads with 3 Sectors and 7 Operations"
- name: sector_to_operation_navigation
check: "Selecting Sector shows available Operations"
- name: battle_state_inheritance
check: "Unit HP/ammo/morale carry over between Battles"
- name: supply_line_mechanics
check: "Supply level affects ammo replenishment rate"
- name: reinforcement_trigger
check: "Reinforcements arrive at correct Operation timeline points"
- name: campaign_victory_condition
check: "Campaign victory triggers when all Sectors completed"
- name: campaign_defeat_condition
check: "Campaign defeat triggers when critical objectives lost"
- name: cross_battle_veterancy
check: "Unit experience accumulates across Battles"
- name: strategic_map_update
check: "Strategic map reflects Battle outcomes"
- name: save_resume_campaign
check: "Campaign state saves and resumes correctly mid-campaign"目标: 验证P0 AI行为在完整游戏场景中的表现
场景描述:
- 多种战场情境测试AI决策
- 覆盖全部7项P0 AI行为
关键验证点:
validation_points:
- name: ammo_pickup_under_fire
check: "AI unit with no ammo moves to ammo source while under fire"
- name: weapon_scavenge_priority
check: "AI prefers scavenge closer weapons over farther ones"
- name: surrender_conditions
check: "AI surrenders only when morale<5, no support, surrounded"
- name: surrender_no_friendly_nearby
check: "AI does not surrender when friendly units within 5 tiles"
- name: weapon_jam_recovery
check: "Jammed weapon clears after correct tick duration"
- name: weapon_jam_probability
check: "Jam rate statistically matches configured jam_chance"
- name: smoke_deployment_timing
check: "AI deploys smoke when suppression>50 and has smoke grenades"
- name: smoke_blocks_los
check: "Smoke tile blocks line of sight for both sides"
- name: squad_degradation_effects
check: "Squad with <50% survivors has reduced accuracy/morale/speed"
- name: squad_degradation_nonlinear
check: "Degradation penalty increases nonlinearly with casualties"
- name: nco_rally_range
check: "NCO rally affects units within 5 tiles"
- name: nco_rally_morale_boost
check: "Rallied units gain +15 morale and exit PANIC state"
- name: nco_rally_cooldown
check: "NCO cannot rally again within 30 ticks"
- name: ai_behavior_tree_integration
check: "P0 behaviors integrate correctly with existing BT"
- name: multiple_ai_behaviors_conflict
check: "AI resolves conflicts between competing behaviors"
- name: ammo_pickup_then_attack
check: "AI picks up ammo then resumes attacking"
- name: smoke_then_reposition
check: "AI deploys smoke then moves to better position"
- name: surrender_prevents_exploitation
check: "Surrendered units cannot be attacked"
- name: jam_during_critical_moment
check: "Weapon jam during combat triggers scavenge or retreat"
- name: nco_death_squad_morale
check: "NCO death triggers squad morale penalty before rally check"目标: 验证新游戏设置系统在完整游戏流程中的效果
场景描述:
- 测试5种难度预设的实际游戏效果
- 验证独立经验/补给等级
关键验证点:
validation_points:
- name: recruit_preset_easy
check: "RECRUIT preset: allies GREEN+ABUNDANT vs axis GREEN+SCARCE"
- name: veteran_preset_hard
check: "VETERAN preset: allies ELITE+SCARCE vs axis CRACK+ABUNDANT"
- name: independent_experience_levels
check: "Allies and Axis can have different experience levels"
- name: independent_supply_levels
check: "Allies and Axis can have different supply levels"
- name: experience_affects_accuracy
check: "ELITE units have statistically higher hit rate than GREEN"
- name: supply_affects_ammo
check: "SCARCE supply reduces starting ammo by ~40%"
- name: supply_affects_reinforcement
check: "ABUNDANT supply enables more reinforcement waves"
- name: custom_settings_override_preset
check: "Custom settings override preset values"
- name: settings_persist_save_load
check: "Game settings persist through save/load cycle"
- name: settings_displayed_correctly
check: "Settings UI shows correct preset and individual values"
- name: normal_preset_balanced
check: "NORMAL preset: symmetric settings for both sides"
- name: easy_preset_advantage
check: "EASY preset gives allies measurable advantage"
- name: hard_preset_disadvantage
check: "HARD preset gives axis measurable advantage"
- name: supply_replenishment_rate
check: "Supply level affects between-battle ammo replenishment rate"
- name: experience_morale_bonus
check: "Higher experience level provides morale recovery bonus"Quality Gate (质量门禁) 是代码合并前的强制性检查项。
| Gate ID | 名称 | 描述 | 严重性 | 自动阻断? |
|---|---|---|---|---|
| QG-1 | TDD Core Pass | 12个核心模块全部单元测试通过 | 🔴 Critical | ✅ Yes |
| QG-2 | Domain 100% Coverage | 领域模型(Vec2/Ballistic/Morale/PathFinder) 100%行覆盖 | 🔴 Critical | ✅ Yes |
| QG-3 | Overall ≥70% | 项目总体代码覆盖率 ≥ 70% | 🟠 High | ✅ Yes |
| QG-4 | Ballistic Statistics | 弹道引擎统计测试通过 (命中率/伤害分布) | 🔴 Critical | ✅ Yes |
| QG-5 | Pathfinder Perf | 256×256地图寻路 < 100ms (P95) | 🟠 High | ✅ Yes |
| QG-6 | Ruff Zero Errors | Ruff linter零错误零警告 | 🟡 Medium | |
| QG-7 | Mypy Domain Strict | 领域模型mypy strict模式通过 | 🟠 High | ✅ Yes |
| QG-8 | Integration Pass | 全部40个集成测试通过 | 🔴 Critical | ✅ Yes |
GitHub Actions 示例 (.github/workflows/ci.yml):
name: PyCC2 CI Pipeline
on:
push:
branches: [main, develop]
pull_request:
branches: [main]
jobs:
lint-and-typecheck:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- uses: actions/setup-python@v3
with:
python-version: '3.11'
cache: 'pip'
- name: Install dependencies
run: |
pip install -e ".[dev]"
pip install ruff mypy pytest-cov
# QG-6: Ruff检查
- name: Ruff Linting
run: ruff check src/pycc2/
# QG-7: Mypy类型检查
- name: Mypy Type Check (Strict on domain)
run: mypy src/pycc2/core/ --strict
unit-tests:
runs-on: ubuntu-latest
needs: lint-and-typecheck
steps:
- uses: actions/checkout@v3
- uses: actions/setup-python@v3
with:
python-version: '3.11'
- name: Install deps
run: pip install -e ".[dev]"
# QG-1: TDD Core单元测试
- name: Run Unit Tests (TDD Core)
run: |
pytest tests/unit/ -v \
--cov=src/pycc2 \
--cov-report=xml \
--cov-report=term-missing \
--junitxml=junit.xml
# QG-2 & QG-3: 覆盖率检查
- name: Coverage Check
uses: codecov/codecov-action@v3
with:
files: ./coverage.xml
fail_ci_if_error: true
thresholds: |
domain_modules: 100%
overall: 70%
# QG-4: 弹道统计测试
- name: Ballistic Statistical Tests
run: pytest tests/unit/test_ballistic_engine.py::TestStatisticalValidation -v
# 上传覆盖率报告
- uses: actions/upload-artifact@v3
with:
name: coverage-report
path: coverage.xml
integration-tests:
runs-on: ubuntu-latest
needs: unit-tests
steps:
- uses: actions/checkout@v3
- uses: actions/setup-python@v3
with:
python-version: '3.11'
- name: Install deps
run: pip install -e ".[dev]"
# QG-8: 集成测试
- name: Run Integration Tests
run: |
pytest tests/integration/ -v \
--timeout=120 \
--junitxml=integration-junit.xml
# QG-5: Pathfinder性能测试
- name: Pathfinder Performance Test
run: pytest tests/integration/test_pathfinding_real_map.py::test_large_map_performance -v -s
e2e-tests:
runs-on: ubuntu-latest
needs: integration-tests
if: github.event_name == 'push' && github.ref == 'refs/heads/main'
steps:
- uses: actions/checkout@v3
- uses: actions/setup-python@v3
with:
python-version: '3.11'
- name: Install deps with pygame
run: pip install -e ".[dev,e2e]"
- name: Run E2E Scenarios
run: |
xvfb-run pytest tests/e2e/ -v \
--timeout=300 \
--e2e-headless=True
- name: Upload E2E Screenshots (on failure)
if: failure()
uses: actions/upload-artifact@v3
with:
name: e2e-failure-screenshots
path: tests/e2e/screenshots/# === 安装测试依赖 ===
pip install -e ".[dev]"
pip install pytest pytest-cov pytest-xdist pytest-timeout ruff mypy
# === 运行全部测试 ===
pytest # 所有测试
pytest tests/unit/ # 仅单元测试
pytest tests/integration/ # 仅集成测试
pytest tests/e2e/ # 仅E2E测试
# === 并行执行 (多核加速) ===
pytest -n auto # 自动检测CPU核心数并行
# === 覆盖率报告 ===
pytest --cov=src/pycc2 --cov-report=html # HTML报告 (htmlcov/index.html)
pytest --cov=src/pycc2 --cov-report=term-missing # 终端显示缺失行
# === 单个模块测试 ===
pytest tests/unit/test_ballistic_engine.py -v
pytest tests/unit/test_ballistic_engine.py::TestBasicHit -v # 只测某个类
pytest tests/unit/test_ballistic_engine.py::TestBasicHit::test_hit_within_effective_range -vs # 单个测试+打印
# === 按优先级运行 ===
pytest -m "P0" # 只运行P0测试 (需自定义marker)
pytest -m "not slow" # 排除慢速测试
# === 调试失败的测试 ===
pytest --lf # 只运行上次失败的测试
pytest -x # 第一个失败就停止
pytest --tb=long # 完整traceback
# === Lint和类型检查 ===
ruff check src/ # Python linting
ruff check src/ --fix # 自动修复
mypy src/pycc2/core/ # 类型检查
mypy src/pycc2/core/ --strict # 严格模式
# === 性能基准测试 ===
pytest tests/benchmark/ --benchmark-only # 需要pytest-benchmark插件
# === 生成测试报告 ===
pytest --html=report.html --self-contained-html # HTML报告
pytest --junitxml=result.xml # JUnit XML格式[tool.pytest.ini_options]
testpaths = ["tests"]
python_files = ["test_*.py", "*_test.py"]
python_classes = ["Test*"]
python_functions = ["test_*"]
markers = [
"P0: Critical priority tests",
"P1: High priority tests",
"P2: Medium priority tests",
"slow: Marks tests as slow (deselect with '-m \"not slow\"')",
"integration: Integration test (requires more setup)",
"e2e: End-to-end test (requires full system)",
"statistical: Tests requiring large sample sizes for validation"
]
addopts = [
"-v",
"--strict-markers",
"--tb=short",
"--cov=src/pycc2",
"--cov-report=term-missing:skip-covered",
"-p no:warnings", # 过滤第三方库警告
"--durations=10", # 显示最慢的10个测试
]
filterwarnings = [
"ignore::DeprecationWarning",
"ignore::PendingDeprecationWarning"
]
timeout = 120 # 单个测试最大执行时间(秒)[tool.coverage.run]
source = ["src/pycc2"]
branch = true
parallel = true
omit = [
"*/tests/*",
"*/__pycache__/*",
"*/typings/*",
"*/main.py", # 入口脚本通常不需要全覆盖
]
[tool.coverage.report]
exclude_lines = [
"pragma: no cover",
"def __repr__",
"raise AssertionError",
"raise NotImplementedError",
"if __name__ == .__main__.:",
"@abstractmethod",
"if TYPE_CHECKING:",
]
fail_under = 70 # 总体覆盖率底线
show_missing = true
skip_covered = true
[tool.coverage.html]
directory = "htmlcov"
title = "PyCC2 Test Coverage Report"
[tool.coverage.paths]
source = [
"src/pycc2/",
".tox/*/lib/python*/site-packages/pycc2/",
]| 测试文件 | 类型 | 数量 | 覆盖内容 | 阶段 |
|---|---|---|---|---|
| test_vec2.py | unit | 25 | 向量运算、距离、归一化、旋转 | P0 |
| test_tile_coord.py | unit | 15 | 瓦片坐标转换、邻居查询 | P0 |
| test_terrain_type.py | unit | 12 | 地形枚举、移动代价、掩体等级 | P0 |
| test_state_machine.py | unit | 20 | 状态转换、条件守卫、回调 | P0 |
| test_random_context.py | unit | 10 | 确定性RNG、seed隔离 | P0 |
| test_ballistic_engine.py | unit | 35 | 命中/伤害/散布/边缘/统计验证 | P0-P1 |
| test_morale_calculator.py | unit | 30 | 士气事件/状态机(4态)/传染/tick恢复 | P0-P1 |
| test_pathfinder.py | unit | 25 | A*基本/地形代价/路径质量/边缘 | P0-P1 |
| test_fog_of_war.py | unit | 20 | FoW计算/视野更新/探索标记 | P1 |
| test_combat_resolver.py | unit | 20 | 战斗结算/伤害应用/击杀判定 | P1 |
| test_behavior_tree.py | unit | 20 | BT节点/Selector/Sequence/Condition | P1 |
| test_event_bus.py | unit | 15 | 发布/订阅/事件过滤/异步分发 | P0 |
| test_sound_system.py | unit | 21 | 程序化音效生成、播放、音量控制、缓存 | P2 |
| test_game_loop.py | integration | 5 | 主循环启动/暂停/步进/状态管理 | P1 |
| test_save_load.py | integration | 8 | 存档读写/HMAC签名/完整性校验 | P1 |
| test_combat_scenario.py | integration | 8 | 完整战斗流程/双方交火至胜负 | P1 |
| test_pathfinding_real_map.py | integration | 5 | 真实地图寻路/动态障碍 | P1 |
| test_fog_of_war.py | integration | 5 | FoW与单位视野交互 | P1 |
| test_ui_input.py | integration | 5 | 输入→命令→状态链路 | P1 |
| test_mod_loading.py | integration | 4 | Mod加载与注册 | P1 |
| test_combat_loop.py | integration | 13 | Combat loop全链路: attack/damage/death/move/stop/constraints | P2 |
| test_victory_conditions.py | unit | 39 | VictoryConditionEvaluator, BattleStats, objectives, morale collapse, time limits | P3-A |
| test_animation_system.py | unit | 52 | UnitAnimator (6 anim types), ScreenShake, ParticleEmitter (8 particle types), physics | P3-B |
| test_content_expansion.py | unit | 31 | UnitTemplates (8 types), new terrains (CRATER/SWAMP), Campaign system, new sprite generation | P3-C |
| test_save_system.py | unit | 26 | SecureSaveManager (HMAC/JSON/8 slots), SaveMetaData, export_state, tamper detection | P3-D |
| test_render_pipeline.py | unit | ~20 | RenderPipeline rendering orchestration | P4.1-A |
| test_combat_director.py | unit | ~15 | CombatDirector combat management | P4.1-A |
| test_input_router.py | unit | ~10 | InputRouter keyboard/mouse routing | P4.1-A |
| test_save_controller.py | unit | ~12 | SaveController save/load/restore | P4.1-A |
| test_settings_menu.py | unit | 62 | Settings UI 4-tab menu interaction | P4.1-B |
| test_security_hardening.py | unit | 13 | HMAC env loading, input bounds | P4.1-C |
| test_tile_cache.py | unit | ~15 | TileCache terrain pre-caching, LRU eviction, hit rate optimization | P4.2-A |
| test_ai_throttle.py | unit | ~12 | AI decision throttling at 10Hz, cooldown management, burst prevention | P4.2-A |
| test_particle_pool.py | unit | ~12 | Particle object pooling, memory reuse, pool exhaustion handling | P4.2-A |
| test_tutorial_overlay.py | unit | ~15 | TutorialOverlay first-launch guide, step progression, skip/resume functionality | P4.2-B |
| test_hint_manager.py | unit | ~13 | HintManager context-aware hints, display timing, dismissal logic | P4.2-B |
| test_mission_4_night.py | unit | ~18 | Mission 4 Night Assault logic: night vision, stealth mechanics, flare effects, AI night behavior | P4.2-C |
| test_mission_5_armor.py | unit | ~17 | Mission 5 Armored Column: anti-tank tactics, vehicle combat, defensive formations | P4.2-C |
| test_new_maps.py | unit | ~12 | New map data validation: night_map, road_ambush, bridge_assault, defense_line schema & loading | P4.2-D |
| 总计 (P0-M1) | - | 2767 | P0-M1 full suite | - |
| 测试文件 | 类型 | 数量 | 覆盖内容 | 阶段 |
|---|---|---|---|---|
| test_campaign_state.py | unit | ~45 | CampaignState序列化/反序列化, 单位名册/经验/弹药/作战日 | P5.1 |
| test_battle_result.py | unit | ~30 | BattleResult记录: KIA/WIA/弹药消耗/目标占领/时长/得分 | P5.1 |
| test_unit_veterancy.py | unit | ~55 | UnitVeterancy经验等级: Green→Veteran→Elite→Crack + 战斗加成验证 | P5.1 |
| test_strategic_map.py | unit/integration | ~40 | StrategicMapRenderer渲染 + 桥梁状态tooltip + 路线选择UI | P5.2 |
| test_operation_timeline.py | unit/integration | ~35 | OperationTimeline Day 1→Day 6推进 + 增援事件 + 桥梁链依赖 | P5.3 |
| test_campaign_integration.py | integration | ~35 | E2E: 战斗→BattleResult→CampaignState→下一关 数据一致性 | P5.x |
| P5 合计 | - | ~240 | Campaign Core 全套 | - |
| 测试文件 | 类型 | 数量 | 覆盖内容 | 阶段 |
|---|---|---|---|---|
| test_swiss_cheese.py | unit | ~60 | SquadMember实体 + 概率性伤亡(70%miss/20%near/7%WIA/2%KIA) | P6.1 |
| test_fatigue_system.py | unit | ~45 | FatigueSystem: 累积衰减(5-15%/battle) + 休息恢复 + critical fatigue | P6.2 |
| test_experience_component.py | unit | ~40 | UnitExperienceComponent: XP追踪 + 老兵加成(+15%acc/+20morale/-40panic) | P6.2 |
| test_after_action_report.py | unit/integration | ~35 | BattleStatistics收集 + AAR UI面板 + 单位评级(A-F) + Campaign进度 | P6.3 |
| test_time_control.py | unit/integration | ~25 | TimeControl: Pause/0.5x/1x/2x + desync检测 + physics稳定性 | P6.4 |
| P6 合计 | - | ~205 | Combat Depth 全套 | - |
| 测试文件 | 类型 | 数量 | 覆盖内容 | 阶段 |
|---|---|---|---|---|
| test_mission_6_son.py | unit | ~18 | Mission 6 Son: 桥梁争夺战逻辑 + 胜利条件 | P7.1 |
| test_mission_7_veghel.py | unit | ~18 | Mission 7 Veghel: 防线突破 + 坦克协同 | P7.1 |
| test_mission_8_grave.py | unit | ~18 | Mission 8 Grave: 渡河生存 + 包围防御 | P7.1 |
| test_mission_9_nijmegen.py | unit | ~20 | Mission 9 Nijmegen: Waal河强渡 + 城市巷战 | P7.1 |
| test_mission_10_arnhem.py | unit | ~22 | Mission 10 Arnhem: 最终突击 + 复杂地形 | P7.1 |
| test_new_maps_p7.py | unit | ~25 | 新地图schema校验: son/veghel/grave/nijmegen/arnhem (44×44 to 64×64) | P7.1 |
| test_historical_authenticity.py | unit | ~28 | OOB验证 + 武器规格匹配 + 任务简报文本渲染 + 动态事件触发 | P7.2 |
| test_mission_p7_integration.py | integration | ~20 | M6-M10完整流程测试 + 难度曲线验证 + 解锁进度 | P7.x |
| P7 合计 | - | ~189 | Content Expansion 全套 | - |
| 阶段 | 当前累计 | 新增 | 目标总数 | 增长率 |
|---|---|---|---|---|
| Current (v0.6-p4w2) | 1377 | — | Baseline | — |
| P5 Complete | ~1617 | +240 | +17% | Campaign系统重度测试 |
| P6 Complete | ~1822 | +205 | +13% | 战斗机制边缘用例 |
| P7 Complete | ~2011 | +189 | +11% | 内容驱动,较少新代码路径 |
| P8 Complete | ~1944 | +334 | +17% | 地图/战役/补给线/部署UI |
| P9 Complete | ~2233 | +289 | +15% | P0 AI行为+游戏设置+战役完成 |
| M1 Fix (v0.1.1) | 2767 | +534 | +24% | M1紧急修复+回归测试 |
| 行为 | 单元测试 | 集成测试 | E2E测试 | 总计 | 覆盖要点 |
|---|---|---|---|---|---|
| 弹药拾取 (Ammo Pickup) | ~20 | ~5 | ~3 | ~28 | 搜索逻辑/路径规划/拾取动画/弹药恢复 |
| 武器搜刮 (Weapon Scavenging) | ~18 | ~4 | ~3 | ~25 | 武器选择/拾取流程/战斗力恢复 |
| 投降 (Surrender) | ~15 | ~3 | ~2 | ~20 | 条件判定/状态转换/不可攻击 |
| 武器卡壳 (Weapon Jam) | ~22 | ~5 | ~3 | ~30 | 概率触发/状态恢复/战斗影响 |
| 烟雾部署 (Smoke Deployment) | ~18 | ~4 | ~3 | ~25 | 触发条件/LOS阻挡/战术效果 |
| 班降解 (Squad Degradation) | ~18 | ~4 | ~3 | ~25 | 非线性惩罚/多属性影响/恢复条件 |
| NCO集结 (NCO Rally) | ~18 | ~4 | ~3 | ~25 | 范围效果/士气恢复/冷却时间 |
| P0 AI 合计 | ~129 | ~29 | ~20 | ~178 | 7项P0行为全覆盖 |
| 设置维度 | 单元测试 | 集成测试 | E2E测试 | 总计 | 覆盖要点 |
|---|---|---|---|---|---|
| 难度预设 (5级) | ~15 | ~5 | ~3 | ~23 | 预设值正确/切换/持久化 |
| 经验等级 (每方独立) | ~12 | ~4 | ~3 | ~19 | 命中率影响/士气加成/独立设置 |
| 补给等级 (每方独立) | ~12 | ~4 | ~3 | ~19 | 弹药量/增援频率/独立设置 |
| 自定义覆盖 | ~8 | ~3 | ~2 | ~13 | 覆盖预设/校验/保存加载 |
| 预设对应表验证 | ~5 | ~2 | ~1 | ~8 | 5×4矩阵完整性 |
| Settings 合计 | ~52 | ~18 | ~12 | ~82 | 设置系统全覆盖 |
| 测试维度 | 单元测试 | 集成测试 | E2E测试 | 总计 | 覆盖要点 |
|---|---|---|---|---|---|
| 四层架构 (Campaign→Sector→Op→Battle) | ~10 | ~5 | ~2 | ~17 | 层级导航/状态传递 |
| 3 Sectors | ~8 | ~3 | ~2 | ~13 | Sector独立/切换/完成 |
| 7 Operations | ~8 | ~3 | ~2 | ~13 | Operation进度/增援/时间线 |
| 29 Battles | ~5 | ~2 | ~2 | ~9 | Battle引用地图/胜负/继承 |
| 63 Maps引用 | ~5 | ~2 | ~1 | ~8 | 全部地图加载/Schema校验 |
| Campaign 合计 | ~36 | ~15 | ~9 | ~60 | 战役完成全覆盖 |
| 测试文件 | 类型 | 数量 | 覆盖内容 | 阶段 |
|---|---|---|---|---|
| test_night_combat.py | unit | ~8 | 夜战机制: Environment+FogOfWar夜间修正+照明弹效果+潜行加成 | P4.3-A |
| test_armor_system.py | unit | ~19 | 反坦克装甲: 装甲剖面(正/侧/后)+角度穿透计算+伤害衰减 | P4.3-B |
| test_weather_visual.py | unit | ~15 | 视觉增强: WeatherRenderer(雨/雾/夜渲染)+粒子预设(爆炸/血液) | P4.3-C |
| P4.3 小计 | - | ~42 | 夜战/装甲/视觉增强 | - |
| 来源 | 新增测试数 | 覆盖内容 |
|---|---|---|
| 文档统一v1.6 (14文件三语) | ~20 | 文档格式校验/多语言一致性/链接完整性 |
| 代码质量修复 | ~15 | Ruff lint修复/Mypy类型修复/安全漏洞修复 |
| E2E回归测试新增 | ~26 | 端到端场景扩展/跨模块集成/边界条件 |
| QG-Docs 小计 | ~61 | 质量门禁通过 |
| 阶段 | 当前累计 | 预计新增 | 目标总数 | 增长率 |
|---|---|---|---|---|
| Current (v0.6-p4w2) | 1377 | — | Baseline | — |
| P5 Complete (v0.7) | ~1750 | +373 | +27% | Campaign系统重度测试 |
| P6 Complete (v0.8) | ~2350 | +600 | +34% | 战斗机制边缘用例 |
| P7 Complete (v0.9) | ~2600 | +250 | +11% | 内容驱动,较少新代码路径 |
| P8 Complete (v1.0) | ~2700 | +100 | +4% | 回归+性能测试为主 |
详细P5测试计划见 Section 14: P5 Test Plan Preview
- Total: 940 tests passed (937 existing + 13 combat_loop + 21 sound_system ≈ 940)
- Pass Rate: 100% (940/940)
- Failures: 0
- Regression: ✅ 全量回归通过
| # | 测试项 | 结果 | 说明 |
|---|---|---|---|
| 1 | Attack executes damage | ✅ | 攻击命令正确执行并造成伤害 |
| 2 | Attack kills unit | ✅ | 伤害超过HP时单位死亡 |
| 3 | Hit flash triggers | ✅ | 受伤时触发闪白特效 |
| 4 | Damage number shows | ✅ | 伤害数值飘字正确显示 |
| 5 | Death animation on kill | ✅ | 死亡动画正确播放 |
| 6 | Muzzle flash on attack | ✅ | 攻击时枪口火焰特效 |
| 7 | No attack out of range | ✅ | 射程外攻击被拒绝 |
| 8 | No attack wrong faction | ✅ | 友军伤害被阻止 (已修复测试逻辑) |
| 9 | Weapon ammo consumes | ✅ | 弹药正确消耗 |
| 10 | Weapon reload needed | ✅ | 弹药耗尽后需要换弹 |
| 11 | Move command works | ✅ | 移动命令正确执行 |
| 12 | Stop command clears move | ✅ | 停止命令清除移动目标 |
| 13 | Multiple attacks accumulate | ✅ | 多次攻击伤害正确累加 |
| 分类 | 数量 | 覆盖内容 |
|---|---|---|
| 波形生成 | 8 | sine/square/triangle/sawtooth/noise/pulse/organ/string |
| SoundType 枚举 | 23种 | UI/战斗/脚步/系统全覆盖 |
| 播放控制 | 5 | play/stop/volume/pause/resume |
| 缓存机制 | 3 | LRU缓存/预生成/内存管理 |
| 参数化 | 5 | 频率/振幅/包络/持续时间/音高 |
- 全量套件: 940/940 passed, 0 failures
- 新增代码: 无回归问题
- 已有功能: 全部保持通过
- 性能: 无明显退化
- Total: 1088 tests (unchanged — fixes were to existing code, not new features)
- Pass Rate: 100% (1088/1088)
- New Tests: 0 (bug fixes only)
| Fix # | Component | Issue | Impact | Verification |
|---|---|---|---|---|
| #1 | BallisticEngine | Missing weapon stats: tank_cannon, sniper_rifle, mortar | New units (Tank/Sniper/Medic) dealt 2-4x less damage than intended | Weapon damage values now correct per UNIT_TEMPLATES |
| #2 | game_loop.py quick_load() | No-op returning True without restoring state | Save files loaded but game state unchanged | Full _restore_state_from_dict() implementation |
| #3 | commander_ai.py | Copy-paste loop boundary errors in _find_cover_positions() and TacticalAdvisor.suggest_defensive_positions() | AI cover-finding could miss valid positions or crash | Loop bounds corrected (2 locations) |
| #4 | main.py | Entry point referenced 4 non-existent modules | Application failed to start | Rewrote to use GameLoop() no-arg constructor |
- 全量套件: 1088/1088 passed, 0 failures
- P0-P2 基线: 940/940 passed (无回归)
- P3 功能: 148/148 passed (无回归)
- P3-Fix: 0 new tests, all existing tests still pass
- 性能: 无明显退化
- Total: 1088 tests (940 P0-P2 base + 148 P3 new)
- Pass Rate: 100% (1088/1088)
- Failures: 0
- Regression: ✅ 全量回归通过
| 分类 | 数量 | 覆盖内容 |
|---|---|---|
| BattleStats tracking | 16 | kills/deaths/shots_fired/shots_hit/damage_dealt/damage_taken/units_lost/survivors |
| VictoryConditionEvaluator | 23 | commander kill, eliminate all, morale collapse, time limit, objective capture, multi-condition |
| 分类 | 数量 | 覆盖内容 |
|---|---|---|
| UnitAnimator | 14 | 6 animation types (IDLE/WALK/SHOOT/DEATH/HIT_REACT/RELOAD) with numerical verification |
| ScreenShake | 8 | trigger/decay/cap/multiple simultaneous |
| ParticleEmitter | 18 | 8 emission types + physics simulation (gravity/velocity/lifetime) |
| AnimationState + edge cases | 12 | state transitions, frame timing, interrupt handling |
| 分类 | 数量 | 覆盖内容 |
|---|---|---|
| UnitTemplates | 8 | all 8 types with sensible stats (TANK/SNIPER/MEDIC + existing 5) |
| TerrainTypes | 8 | CRATER + SWAMP properties (movement cost, cover, passable) |
| CampaignSystem | 9 | 3 missions, registration, completion tracking, progression |
| New Sprites | 6 | tank/sniper/medic pixel sprite generation, crater/swamp tile generation |
| 分类 | 数量 | 覆盖内容 |
|---|---|---|
| SecureSaveManager | 17 | save/load/delete/tamper detection/version migration/8 slot management |
| SaveMetaData | 3 | serialization roundtrip, field completeness |
| ExportState | 5 | game_loop state extraction, snapshot consistency |
- 全量套件: 1088/1088 passed, 0 failures
- P3 新增: 148/148 passed
- P0-P2 基线: 940/940 passed (无回归)
- 性能: 无明显退化
| 组件 | 版本要求 | 说明 |
|---|---|---|
| Python | >= 3.11, < 3.13 | 使用最新stable特性 |
| pytest | >= 8.0 | 测试框架 |
| pytest-cov | >= 5.0 | 覆盖率插件 |
| pytest-xdist | >= 3.5 | 并行测试 |
| pytest-timeout | >= 2.3 | 超时控制 |
| ruff | >= 0.4.0 | Linter |
| mypy | >= 1.8 | 类型检查器 |
| hypothesis | >= 6.100 | 属性化测试 (可选, 用于统计测试) |
| scipy | >= 1.12 | 统计检验库 (仅统计测试需要) |
| numpy | >= 1.26 | 数值计算 (仅性能/统计测试) |
操作系统矩阵:
- macOS 14+ (Sonoma) ✅ 主要开发平台
- Ubuntu 22.04/24.04 LTS ✅ CI服务器
- Windows 11 (可选)
⚠️ 可能需要额外配置
| 依赖 | Mock方式 | 原因 |
|---|---|---|
| 文件系统 | tmp_path fixture / unittest.mock.patch |
单元测试不应写磁盘 |
| 时间 | freezegun / mock time.time |
控制时间依赖逻辑 |
| Pygame | pygame.mock 或纯Python替代 |
CI无显示器 |
| 网络 | 全部禁止 | PyCC2是单机游戏 |
| SecureIO._derive_key | mock返回固定key | 避免慢PBKDF2和设备依赖 |
| RandomContext | 注入固定seed | 保证测试确定性 |
Mock示例:
from unittest.mock import patch, MagicMock
import pytest
@pytest.fixture
def mock_secure_io():
"""Mock SecureIO避免真正的HMAC/PBKDF2"""
with patch("pycc2.core.secure_io.SecureIO._derive_key") as mock_key:
mock_key.return_value = b"test_key_32bytes_long!!!!" # 固定测试密钥
yield
@pytest.fixture
def headless_pygame():
"""Headless Pygame (无需显示器)"""
import os
os.environ["SDL_VIDEODRIVER"] = "dummy"
os.environ["SDL_AUDIODRIVER"] = "dummy"
import pygame
pygame.init()
yield pygame
pygame.quit()tests/
├── fixtures/
│ ├── maps/
│ │ ├── test_small_16x16.json # 最小测试地图
│ │ ├── test_medium_64x64.json # 中等地图
│ │ ├── test_full_terrain.json # 包含所有12种地形
│ │ └── test_obstacle_course.json # 寻路专用障碍地图
│ ├── units/
│ │ ├── test_infantry.json
│ │ └── test_vehicle.json
│ ├── weapons/
│ │ ├── test_rifle_perfect.json # accuracy=1.0 用于测试
│ │ └── test_mortar_indirect.json
│ └── saves/
│ ├── valid_save_with_sig.sav # 合法存档 (用于read测试)
│ ├── tampered_save.sav # 被篡改的存档 (用于安全测试)
│ └── corrupted_save.sav # 损坏的存档 (错误恢复测试)
├── conftest.py # 共享fixtures
└── ...
# tests/conftest.py
import pytest
from pathlib import Path
@pytest.fixture
def sample_weapon_config():
"""返回标准测试武器配置"""
return {
"id": "test_rifle",
"name": "Test Rifle",
"type": "rifle",
"caliber": "test_cal",
"range_max": 500,
"range_effective": 300,
"accuracy_base": 0.75,
"damage_base": 35,
"rof": 40,
"magazine_size": 8,
"reload_time_ticks": 20,
}
@pytest.fixture
def small_test_map():
"""加载16x16测试地图"""
map_path = Path(__file__).parent / "fixtures/maps/test_small_16x16.json"
return load_tile_map_config(map_path)
@pytest.fixture
def deterministic_rng():
"""确定性的随机数生成器 (seed=42)"""
return RandomContext(seed=42)| 测试场景 | 目标时间 | 最大容忍 | 测量方法 |
|---|---|---|---|
| Vec2运算 (100万次) | < 0.5s | 1s | time.perf_counter |
| BallisticEngine单次射击 | < 1ms | 5ms | per-shot计时 |
| BallisticEngine 1000次统计 | < 2s | 5s | 批量计时 |
| PathFinder 16×16地图 | < 10ms | 50ms | 单次寻路 |
| PathFinder 256×256地图 | < 100ms | 500ms | 最坏情况 |
| MoraleCalculator 1000事件 | < 0.5s | 2s | 批量处理 |
| FogOfWar 更新 (full map) | < 20ms | 100ms | 每tick |
| SaveGameData 序列化 | < 50ms | 200ms | 含签名 |
| SaveGameData 反序列化+校验 | < 100ms | 500ms | 含HMAC验证 |
| 完整单元测试套件 | < 30s | 60s | pytest --duration |
| 完整集成测试套件 | < 2min | 5min | 包含setup |
| E2E Tutorial Scenario | < 3min | 10min | 端到端 |
每次CI运行生成标准化报告:
# PyCC2 Test Report
**Run ID:** #1234
**Branch:** feature/new-weapon-system
**Commit:** abc1234
**Timestamp:** 2024-01-15 14:30:00 UTC
**Trigger:** Pull Request #56
## Summary
| Category | Total | Passed | Failed | Skipped | Duration |
|----------|-------|--------|--------|---------|----------|
| Unit Tests | 237 | 235 | 2 | 0 | 28.5s |
| Integration | 40 | 40 | 0 | 0 | 7.3s |
| E2E Scenarios | 3 | 3 | 0 | 0 | 4m12s |
| **TOTAL** | **280** | **278** | **2** | **0** | **~5min** |
## Quality Gates Status
| Gate | Status | Details |
|------|--------|---------|
| QG-1 TDD Core Pass | ❌ FAILED | 2 failures in test_ballistic_engine.py |
| QG-2 Domain 100% Coverage | ✅ PASS | Vec2: 100%, Ballistic: 98.5% |
| QG-3 Overall ≥70% | ✅ PASS | Current: 74.2% |
| QG-4 Ballistic Statistics | ⚠️ WARNING | Hit rate variance slightly high |
| QG-5 Pathfinder Perf | ✅ PASS | P95: 45ms (limit: 100ms) |
| QG-6 Ruff Zero Errors | ✅ PASS | 0 errors, 0 warnings |
| QG-7 Mypy Strict | ✅ PASS | No type errors |
| QG-8 Integration Pass | ✅ PASS | 40/40 passed |
## Failures Detail
### Failure 1: test_hit_within_effective_range
**File:** tests/unit/test_ballistic_engine.py:142
**Error:** AssertionError: Hit rate 0.58 too low (expected > 0.6)
**Root Cause:** Seed-dependent flaky test (need to increase sample size or adjust threshold)
### Failure 2: test_damage_distribution_chi_square
**File:** tests/unit/test_ballistic_engine.py:298
**Error:** Distribution not normal (p=0.0234)
**Root Cause:** Sample size too small for chi-square test (only 800 hits)
## Recommendations
1. Increase sample size in BE-HIT-01 from 100 to 500 shots
2. Use Kolmogorov-Smirnov test instead of Shapiro-Wilk for larger samples
3. Consider marking statistical tests as `@pytest.mark.flaky(max_runs=3)` until variance is reduced
## Coverage Highlights
**Highest Coverage Modules:**
- vec2.py: 100% (25/25 lines)
- terrain_type.py: 100% (45/45 lines)
- secure_io.py: 96.4% (270/281 lines)
**Lowest Coverage Modules (needs attention):**
- ui/renderer.py: 45% (requires E2E/screenshot testing)
- audio/mixer.py: 38% (hardware dependent)
- main.py: 52% (entry point, lower priority)
## Artifacts
- [Coverage HTML Report](./htmlcov/index.html)
- [JUnit XML](./junit.xml)
- [E2E Screenshots](./e2e-screenshots/)
- [Performance Data](./benchmarks.json)- Total: 1163 tests (1088 P0-P3 base + 75 P4.1 new)
- Pass Rate: 100% (1163/1163)
- Failures: 0
- Regression: ✅ 全量回归通过
| 分类 | 数量 | 覆盖内容 |
|---|---|---|
| RenderPipeline | ~20 | render() orchestration, FPS update, screen management, alpha blending |
| CombatDirector | ~15 | update(), combat resolution, event publishing, sound integration |
| InputRouter | ~10 | route_input(), keyboard/mouse handling, ESC/F10/F3 shortcuts |
| SaveController | ~12 | save/load/restore, state serialization, slot management |
关键改进:
- GameLoop 从 880 行减少到 356 行 (-59%)
- 职责清晰分离为 4 个独立组件
- 每个组件可独立测试
| 分类 | 数量 | 覆盖内容 |
|---|---|---|
| Initialization | 8 | constructor, display config, default state |
| Tab Management | 12 | 4 tabs (General/Audio/Controls/Gameplay), switching, navigation |
| Option Rendering | 15 | slider, toggle, enum, info option types |
| Input Handling | 15 | keyboard (arrows/enter/tab/esc), mouse click, selection |
| State Persistence | 7 | apply_to_systems(), state save/restore |
| Edge Cases | 5 | visibility toggling, empty state, boundary values |
功能验证:
- ✅ 4 个设置标签页正确切换
- ✅ 键盘和鼠标输入完整支持
- ✅ 设置应用到 SoundSystem
- ✅ 视觉渲染正确(覆盖层 + 面板)
| 分类 | 数量 | 覆盖内容 |
|---|---|---|
| HMAC Key Loading | 5 | environment variable, secrets.toml file, fallback with warning |
| Input Bounds Checking | 4 | coordinate clamping, value validation, overflow prevention |
| Secrets Template | 2 | secrets.toml.example validation, key format checking |
| Integration | 2 | SecureSaveManager + env key interaction |
安全改进:
- ✅ HMAC 密钥从硬编码改为环境变量/配置文件
- ✅ 输入边界检查防止溢出
- ✅ secrets.toml.example 模板提供
- 全量套件: 1163/1163 passed, 0 failures
- P0-P3 基线: 1088/1088 passed (无回归)
- P4.1 新增: 75/75 passed
- 性能: 无明显退化
- 代码质量: Ruff lint 通过 (97 remaining non-critical warnings)
- Total: 1270 tests (1163 P0-P4.1 base + 107 P4.2 new)
- Pass Rate: 100% (1270/1270)
- Failures: 0
- Regression: ✅ 全量回归通过
| 分类 | 数量 | 覆盖内容 |
|---|---|---|
| TileCache | ~15 | 地形瓦片预缓存、LRU淘汰策略、命中率优化(>95%)、内存管理 |
| AI Throttle | ~12 | AI决策节流10Hz、冷却时间管理、突发行为防止、决策队列 |
| ParticlePool | ~12 | 粒子对象池化、内存复用、池耗尽处理、GC压力降低(-40%) |
性能改进验证:
- ✅ TileCache 命中率 > 95% (128×128地图)
- ✅ AI 决策频率稳定在 8-12Hz (目标10Hz)
- ✅ 粒子系统内存分配减少 60%
- ✅ 整体 FPS 提升 +15% (64×64地图+20单位基准)
| 分类 | 数量 | 覆盖内容 |
|---|---|---|
| TutorialOverlay | ~15 | 首次启动引导、步骤进度控制、跳过/恢复功能、UI渲染、状态持久化 |
| HintManager | ~13 | 上下文感知提示、显示时机逻辑、关闭机制、提示队列、优先级排序 |
功能验证:
- ✅ TutorialOverlay 正确检测首次启动
- ✅ 5步引导流程完整(选择单位→移动→攻击→胜利→完成)
- ✅ HintManager 根据游戏状态触发正确提示
- ✅ 提示系统不影响正常游戏操作(可随时关闭)
| 分类 | 数量 | 覆盖内容 |
|---|---|---|
| Mission 4 - Night Assault | ~18 | 夜战视野系统(-40%)、潜行机制、照明弹效果、AI夜间巡逻模式、听觉侦测增强 |
| Mission 5 - Armored Column | ~17 | 反坦克战术逻辑、载具AI行为、防御阵型管理、装甲单位伤害计算、AT武器效果 |
Mission 4 Night Assault 测试要点:
- ✅ 夜间视野范围正确缩减
- ✅ 潜行状态移动不暴露位置
- ✅ 照明弹临时提升视野区域
- ✅ AI 在夜间使用巡逻模式而非主动搜索
- ✅ 听觉侦测范围在夜间提升(+30%)
Mission 5 Armored Column 测试要点:
- ✅ 反坦克武器对载具造成额外伤害(+200%)
- ✅ 坦克AI优先攻击反坦克阵地
- ✅ 防御阵型提供掩体加成(+25% cover)
- ✅ 载具死亡时乘员逃生逻辑
- ✅ 战斗统计正确记录载具击杀
| 分类 | 数量 | 覆盖内容 |
|---|---|---|
| Map Schema Validation | ~6 | 4张新地图JSON格式校验、地形类型合法性、坐标边界检查、出生点有效性 |
| Map Loading Integration | ~6 | 地图加载性能(<100ms)、tile数据完整性、Objective点解析、地形代价表一致性 |
新增地图测试详情:
- ✅ night_map.json: 36×36, 12种地形, Schema校验通过
- ✅ road_ambush.json: 40×24, 11种地形, Schema校验通过
- ✅ bridge_assault.json: 32×32, 10种地形, Schema校验通过
- ✅ defense_line.json: 48×32, 12种地形, Schema校验通过
- ✅ 所有地图加载时间 < 80ms
- ✅ 地形移动代价表与 TerrainType 枚举一致
| 功能模块 | 单元测试 | 集成测试 | E2E场景 | 总计 | 覆盖率 |
|---|---|---|---|---|---|
| 性能优化 (TileCache/AI/Particle) | 39 | 4 | 1 | 44 | 98% |
| 教程/提示系统 (Tutorial/Hint) | 28 | 3 | 1 | 32 | 95% |
| 新任务内容 (M4/M5) | 35 | 5 | 2 | 42 | 96% |
| 新地图数据 (4 maps) | 12 | 2 | 1 | 15 | 100% |
| P4.2 合计 | 114 | 14 | 5 | 133 | 97% |
- 全量套件: 1270/1270 passed, 0 failures
- P0-P4.1 基线: 1163/1163 passed (无回归)
- P4.2 新增: 107/107 passed
- Mission 4/5 特定测试: 35/35 passed
- 新地图数据测试: 12/12 passed
- 性能: 无退化,部分指标提升 (FPS +15%, 内存 -20%)
- 代码质量: Ruff lint 通过 (95 remaining non-critical warnings, -2 from P4W1)
- 安全性: 无新安全漏洞引入
| Test ID | 场景 | 验证内容 | 优先级 |
|---|---|---|---|
| CP-State-01 | CampaignState 序列化/反序列化 | 跨战斗状态完整保存/恢复(单位名册/经验/弹药/作战日/战略点/伤亡日志) | P0 |
| CP-State-02 | 3场连续战斗继承 | 完成3场战斗后单位属性正确继承(HP衰减/经验累积/弹药消耗) | P0 |
| CP-Vet-01 | UnitVeterancy 经验等级 | Green→Veteran→Elite→Crack 晋升逻辑 + 战斗加成验证 | P0 |
| CP-Vet-02 | 老兵加成实战验证 | Veteran单位命中率提升可统计验证 (p < 0.05) | P0 |
| CP-BR-01 | BattleResult 记录完整性 | KIA/WIA计数/弹药消耗/目标占领/时长/得分 完整记录 | P1 |
| CP-Ser-01 | CampaignSerializer HMAC扩展 | 存档防篡改 + 版本迁移 + 数据完整性 | P1 |
| CP-Int-01 | E2E: 战斗→BattleResult→CampaignState→下一关 | 端到端流程数据一致性 | P0 |
| Test ID | 场景 | 验证内容 | 优先级 |
|---|---|---|---|
| SM-Render-01 | StrategicMapRenderer 帧率 | Market Garden走廊渲染 60fps稳定 (缩放/平移) | P0 |
| SM-Render-02 | 桥梁状态tooltip | 点击桥梁显示详细状态(占领/摧毁/争夺中) | P1 |
| SM-Route-01 | 路线选择UI | 多进攻方向选择 + 风险/收益权衡影响后续任务生成 | P0 |
| SM-Force-01 | 全局兵力分布 | 友军/敌军集中度显示 + 情报可靠性迷雾 | P1 |
| SM-Bridge-01 | 5座桥梁追踪 | 实时捕获/摧毁/争议状态更新 | P1 |
| Test ID | 场景 | 验证内容 | 优先级 |
|---|---|---|---|
| OT-Day-01 | Day 1→Day 6 推进 | 作战时间线按历史序列正确推进 | P0 |
| OT-Rein-01 | 增援事件准时触发 | XXX Corps 时间表 / 波兰旅 / 紧急补给 ±1hr容差 | P0 |
| OT-Bridge-01 | 桥梁链依赖 | Son→Nijmegen→Arnhem 顺序强制执行 | P0 |
| OT-Resrc-01 | 资源分配决策 | 弹药分发/医疗优先/预备队投入 影响后续战备 | P1 |
| OT-Hist-01 | 历史事件触发 | 天气变化(9.19雾) / 德军装甲反击(9.18) / Waal渡河(9.20) | P1 |
| 功能模块 | 单元测试 | 集成测试 | E2E场景 | 总计 | 覆盖率目标 |
|---|---|---|---|---|---|
| Campaign Persistence | ~120 | ~35 | ~5 | ~160 | 95% |
| Strategic Map Layer | ~85 | ~30 | ~3 | ~118 | 90% |
| Operation Timeline | ~95 | ~35 | ~5 | ~135 | 92% |
| Cross-module Integration | — | ~40 | ~8 | ~48 | 90% |
| P5 合计 | ~300 | ~140 | ~21 | ~461 | ~92% |
- CampaignState 在 5+ 场战斗间序列化/反序列化 100% 数据完整性
- Unit Veterancy 战斗效能差异统计显著 (p < 0.05)
- Strategic Map 交互渲染 60fps (缩放/平移/选择)
- Operation Timeline Day 1 → Day 3 历史序列正确 + 增援事件准时
- 零回归: 所有 v0.6 测试仍通过
- 新增测试: +350-450 (总计 1727-1827)
| 版本 | 日期 | 作者 | 变更内容 |
|---|---|---|---|
| v1.0 | 2026-05-15 | PyCC2 QA Team | 初始测试计划,定义280个测试用例 |
| v1.1 | 2026-05-15 | - | 补充E2E场景详细步骤和CI配置 |
| v1.2 | 2026-05-18 | DevSquad | P2阶段完成: 新增test_combat_loop.py(13) + test_sound_system.py(21), Test Coverage Summary表, P2 Phase Test Results章节, 总数940 |
| v1.3 | 2026-05-18 | DevSquad | P3阶段全部完成(4子阶段): 新增test_victory_conditions(39)+test_animation_system(52)+test_content_expansion(31)+test_save_system(26), P3 Phase Test Results章节, 总数1088 |
| v1.4 | 2026-05-18 | DocSync | P3-Fix: 4个关键Bug修复(BallisticEngine武器/quick_load/AI循环边界/main.py入口), 新增Section 11 P3-Fix Phase Results, 总数保持1088 |
| v1.5 | 2026-05-18 | Integration | P4 Week 1 完成: GameLoop Decomposition(4组件/~62测试) + Settings Menu(62测试) + Security Hardening(13测试), 新增Section 12 P4 Week 1 Results, 总数1163 |
| v1.6 | 2026-05-19 | Integration | P4 Week 2 完成: 性能优化(TileCache/AI节流/粒子池/~39测试) + 教程系统(TutorialOverlay/HintManager/~28测试) + 新任务(Mission 4夜战/Mission 5反坦克/~35测试) + 新地图(4张/~12测试), 新增Section 13 P4 Week 2 Results, 总数1270 |
| v1.7 | 2026-05-19 | PM+CC2 Analysis | CC2差距分析完成: 新增P4.3测试(~42夜战/装甲/视觉) + QG E2E(+61文档/质量/E2E), 测试总数1377, 新增Section 8.1/8.2/8.5 P4.3统计与P5预览, 新增Section 14 P5 Test Plan Preview(P5.1/P5.2/P5.3场景+覆盖矩阵+Gate Criteria) |
| v1.8 | 2026-05-19 | P5/P6/P7 Complete | P5 Campaign Core完成: ~240 tests (CampaignState/BattleResult/UnitVeterancy/StrategicMap/OperationTimeline). P6 Combat Depth完成: ~205 tests (Swiss Cheese/Fatigue/AAR/TimeControl). P7 Content Expansion完成: |
| v2.0 | 2026-05-20 | P8/P9 Complete | P8 地图与战役补全完成: |
| v0.1.1 | 2026-05-23 | M1 Fix Update | M1紧急修复进展: P0-1(display_name)/P0-2(属性别名)/P0-3(AI导入)/P0-4(set_mode)已修复。测试总数2767(1 failed)。版本号统一为v0.1.1 |
| v0.4.0 | 2026-06-14 | 文档同步至v0.4.0 | 测试总数~3513。P0 Bug全部已修复。新增38-phase E2E真实SDL测试。覆盖率门禁引入 |
| v0.5.0 | 2026-07-08 | PixVoxel P0接入 | 新增490 E2E测试验证PixVoxel正交版精灵接入。测试金字塔更新(unit占比提升) |
| v0.6.10 | 2026-07-13 | 覆盖率提升+CI增强 | 当前版本: 323新测试(覆盖率提升), radon复杂度门禁集成CI。测试总数6552(unit 5817/integration 181/e2e 491/benchmark 21/acceptance 42/slow 16)。数据校准: 逐目录实测验证 |