223 lines
7.2 KiB
Markdown
223 lines
7.2 KiB
Markdown
# 单元测试 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 无报错
|