# 单元测试 03 · 状态效果系统(StatusEffects) > **测试类型**:EditMode 单元测试(NUnit) > **测试文件**:`Assets/Tests/EditMode/StatusEffectTests.cs`(已存在,本文为完整规范) > **被测程序集**:`BaseGames.Combat.StatusEffects` > **asmdef 依赖**:`BaseGames.Tests.EditMode.asmdef` 需引用 `BaseGames.Combat.StatusEffects` --- ## 目录 1. [测试覆盖范围](#1-测试覆盖范围) 2. [现有测试验证](#2-现有测试验证) 3. [补充测试说明](#3-补充测试说明) 4. [完整扩展测试代码](#4-完整扩展测试代码) 5. [手动集成验证(Play Mode)](#5-手动集成验证play-mode) --- ## 1. 测试覆盖范围 | 类 | 测试点 | |----|--------| | `FireEffect` | MaxStacks=1;MutualExclusions 含 Freeze;OnStack 刷新持续时间;EffectType=Fire | | `PoisonEffect` | MaxStacks=3;MutualExclusions 为空;OnStack 增加 StackCount;超限截断;EffectType=Poison | | `StaggerEffect` | MaxStacks=1;BlockedBy 含 Stun;EffectType=Stagger | | `StatusEffect` 基类 | IsExpired 在 Duration 耗尽后为 true;Update 正确减少剩余时间 | | `StatusEffectManager` | ApplyEffect 正确添加;互斥效果自动移除;到期效果自动清理(需 MonoBehaviour Test 或接口 Mock) | --- ## 2. 现有测试验证 项目已存在 `Assets/Tests/EditMode/StatusEffectTests.cs`,覆盖以下测试(共 14 个): | 测试名 | 验证点 | |--------|--------| | `FireEffect_MaxStacks_IsOne` | FireEffect.MaxStacks == 1 | | `PoisonEffect_MaxStacks_IsThree` | PoisonEffect.MaxStacks == 3 | | `PoisonEffect_OnStack_IncreasesStackCount` | OnStack 后 StackCount == 2 | | `PoisonEffect_OnStack_ClampsAtMaxStacks` | 多次叠加不超过 MaxStacks | | `StaggerEffect_MaxStacks_IsOne` | StaggerEffect.MaxStacks == 1 | | `FireEffect_MutualExclusions_ContainsFreeze` | MutualExclusions 含 Freeze | | `PoisonEffect_MutualExclusions_IsEmpty` | PoisonEffect 无互斥 | | `StaggerEffect_BlockedBy_ContainsStun` | BlockedBy 含 Stun | | `FireEffect_BlockedBy_IsEmpty` | FireEffect 无阻断 | | `StatusEffect_IsExpired_AfterDurationDepleted` | 时间耗尽后 IsExpired == true | | `FireEffect_OnStack_RefreshDuration` | OnStack 刷新剩余时间 | | `FireEffect_EffectType_IsFire` | EffectType == StatusEffectType.Fire | | `PoisonEffect_EffectType_IsPoison` | EffectType == StatusEffectType.Poison | | `StaggerEffect_EffectType_IsStagger` | EffectType == StatusEffectType.Stagger | **运行现有测试步骤:** 1. 打开 `Window → General → Test Runner` 2. 选择 `EditMode` 标签页 3. 展开 `BaseGames.Tests.EditMode → StatusEffectTests` 4. 点击 `Run All`(或右键 `Run`) 5. 确认所有 14 个测试 **全部绿色** --- ## 3. 补充测试说明 现有测试未覆盖以下场景,需补充: ### 3.1 StatusEffect.Update 时间精度 验证 `Update(delta)` 正确累计时间,不因浮点精度导致 IsExpired 判断提前/延迟。 ### 3.2 多效果独立计时 同时持有 `FireEffect` 和 `PoisonEffect` 时,两个效果的剩余时间独立递减,互不影响。 ### 3.3 OnApply / OnRemove 回调 `OnApply(owner)` 和 `OnRemove(owner)` 在正确时机被调用(owner 可传 `null` 用于纯计时测试)。 ### 3.4 StackCount 初始值 新建 Effect 时 `StackCount == 1`(已 Apply 一层)。 --- ## 4. 完整扩展测试代码 将以下代码**追加**到现有 `StatusEffectTests.cs` 的 `}` 前,或创建新文件 `StatusEffectExtendedTests.cs`: ```csharp // 追加到 BaseGames.Tests.EditMode 命名空间下,StatusEffectTests 类中 // ── 补充测试 ──────────────────────────────────────────────────────────────── [Test] public void StatusEffect_StackCount_InitialValueIsOne() { var effect = new FireEffect(); Assert.AreEqual(1, effect.StackCount, "新建 Effect 的初始 StackCount 应为 1"); } [Test] public void StatusEffect_Update_RemainingTime_Decreases() { var effect = new StaggerEffect(2.0f); effect.OnApply(null); float initialDuration = effect.Duration; effect.Update(0.5f); Assert.Less(effect.Duration, initialDuration, "Update 后剩余时间应减少"); Assert.IsFalse(effect.IsExpired, "0.5s Update 后 2.0s 效果不应过期"); } [Test] public void StatusEffect_Update_ExactExpiry() { var effect = new StaggerEffect(1.0f); effect.OnApply(null); effect.Update(1.0f); // 恰好耗尽 Assert.IsTrue(effect.IsExpired, "恰好耗尽 duration 后应 IsExpired == true"); } [Test] public void MultipleEffects_IndependentTimers() { var fire = new FireEffect(); var poison = new PoisonEffect(); fire.OnApply(null); poison.OnApply(null); float fireDuration = fire.Duration; float poisonDuration = poison.Duration; fire.Update(0.5f); // poison 未调用 Update,时间不应变化 Assert.AreEqual(poisonDuration, poison.Duration, 0.0001f, "未 Update 的 Effect 持续时间不应减少"); Assert.Less(fire.Duration, fireDuration, "已 Update 的 Effect 持续时间应减少"); } [Test] public void PoisonEffect_OnStack_DoesNotExceedMaxStacks_EdgeCase() { var effect = new PoisonEffect(); // MaxStacks = 3,初始 StackCount = 1,再叠加 2 次达到上限 effect.OnStack(); effect.OnStack(); Assert.AreEqual(3, effect.StackCount); // 再叠加,不应超过 3 effect.OnStack(); effect.OnStack(); Assert.AreEqual(3, effect.StackCount, "StackCount 不应超过 MaxStacks"); } [Test] public void FireEffect_OnApply_ThenRemove_DoesNotThrow() { var effect = new FireEffect(); Assert.DoesNotThrow(() => { effect.OnApply(null); effect.OnRemove(null); }); } ``` --- ## 5. 手动集成验证(Play Mode) 以下场景需要在 Unity Editor 中手动测试(无法在 EditMode 测试中验证): ### 5.1 StatusEffectManager 添加效果 **前提条件**:测试场景中有玩家或敌人,其 GameObject 挂有 `StatusEffectManager` 组件。 **步骤:** 1. 进入 Play Mode 2. 在 Inspector 中找到目标对象的 `StatusEffectManager` 组件 3. 通过 Console 调用(或临时测试按钮):`statusEffectManager.Apply(new PoisonEffect())` 4. 在 Console 中观察 Poison 效果 Tick 日志(每 `tickInterval` 秒一条) **预期结果:** - Poison 效果持续 `duration` 秒 - 每次 Tick 扣减目标 HP - 到期后自动从管理器移除,不再 Tick ### 5.2 互斥效果测试 **步骤:** 1. 对同一目标施加 `FireEffect` 2. 随后施加 `FreezeEffect`(FireEffect 的 MutualExclusion) **预期结果:** - `FireEffect` 被自动移除 - Console 无 `NullReferenceException` ### 5.3 效果叠加(Poison) **步骤:** 1. 对目标施加 `PoisonEffect`(StackCount = 1) 2. 再施加一次 `PoisonEffect` 3. 再施加一次(StackCount 达到 3) 4. 再施加一次(超过上限) **预期结果:** - StackCount 分别变为 2、3、3(截断) - Inspector 中 `StatusEffectManager` 显示正确的 StackCount ### 5.4 阻断效果(StaggerEffect BlockedBy Stun) **步骤:** 1. 对目标先施加 `StunEffect` 2. 尝试施加 `StaggerEffect` **预期结果:** - `StaggerEffect` 被阻断,未添加到管理器 - Console 无报错