关于 Unity Test
Unity Test 是面向 Unity 开发者的测试编写指导技能,帮助分析待测代码、选择 EditMode 或 PlayMode,并生成 NUnit 测试。它还说明如何将 MonoBehaviour 中的逻辑提取为可独立测试的 C# 代码。
主要功能
- 01分析代码并选择测试模式
- 02从 MonoBehaviour 提取可测逻辑
- 03生成 NUnit 测试
- 04指导程序集定义与测试目录配置
- 05提供测试运行和故障排查建议
适用场景
- 01为纯 C# 逻辑编写 EditMode 测试
- 02为依赖场景生命周期的代码编写 PlayMode 测试
- 03拆分 MonoBehaviour 以便独立测试
安装
复制下面的命令,在终端中运行。
npx skills add https:/ / github.com/ juliankerignard/ unity-skills --skill unity-test安装后在 Claude Code 中输入/unity-test确认已加载。
附带文件references/test-templates.md
详细介绍
Unity 测试
此技能的功能
生成可用于生产环境的 Unity 测试。分析目标代码,确定最佳测试类型(EditMode 或 PlayMode),将可测试逻辑从 MonoBehaviour 中提取到纯 C# 代码,并生成对应的 NUnit 测试。
前提条件
- Unity Test Framework:package
com.unity.test-framework(默认包含) - NUnit:Unity Test Framework 已包含
- Assembly Definitions:每个测试文件夹(EditMode 和 PlayMode 分开)各配置一个
.asmdef - 项目结构:在
Assets/根目录下创建Tests/文件夹,并包含EditMode/和PlayMode/子文件夹
快速开始
- 分析目标代码(识别依赖项、副作用和 I/O)
- 根据决策树确定使用 EditMode 还是 PlayMode
- 如果代码与 MonoBehaviour 紧密耦合,则提取可测试逻辑
- 遵循相应模板编写测试
- 使用 Unity Test Runner 验证(
Window > General > Test Runner)
决策树
Le code a tester...
|
+-- Est de la logique pure (maths, state, data) ?
| +-- EditMode test (rapide, pas besoin de scene)
|
+-- Depend du lifecycle MonoBehaviour (Start, Update, OnCollision) ?
| +-- PlayMode test (simule une scene)
|
+-- Utilise des coroutines ou Awaitable ?
| +-- PlayMode test avec [UnityTest] ou async
|
+-- Valide un ScriptableObject ?
| +-- EditMode test (instancie avec ScriptableObject.CreateInstance)
|
+-- Teste une integration multi-systemes ?
+-- PlayMode Integration test (scene dediee)
主要规则:如果代码无需 MonoBehaviour、GameObject 或 Unity 场景即可运行,则使用 EditMode 测试;否则使用 PlayMode 测试。
分步指南
步骤 1:分析目标代码
编写测试前,先识别:
- 输入:参数、序列化字段、注入的依赖项
- 输出:返回值、状态变化、发出的事件
- 副作用:调用
Destroy、Instantiate、修改场景 - Unity 依赖项:
MonoBehaviour、Transform、Physics、Time.deltaTime
步骤 2:提取可测试逻辑
当逻辑与 MonoBehaviour 紧密耦合时,应用 “提取为可测试代码” 模式。
// AVANT : logique collee au MonoBehaviour (dur a tester)
public class PlayerHealth : MonoBehaviour
{
[SerializeField] private int maxHealth = 100;
private int currentHealth;
private void Start() => currentHealth = maxHealth;
public void TakeDamage(int amount)
{
currentHealth = Mathf.Max(0, currentHealth - amount);
if (currentHealth <= 0) Die();
}
private void Die() { /* destroy, VFX, etc. */ }
}
// APRES : logique pure extraite (facile a tester en EditMode)
public static class HealthCalculator
{
public static int CalculateDamage(int currentHealth, int damage)
=> Mathf.Max(0, currentHealth - damage);
public static bool IsDead(int health) => health <= 0;
}
// Le MonoBehaviour delegue a la logique pure
public class PlayerHealth : MonoBehaviour
{
[SerializeField] private int maxHealth = 100;
private int currentHealth;
private void Start() => currentHealth = maxHealth;
public void TakeDamage(int amount)
{
currentHealth = HealthCalculator.CalculateDamage(currentHealth, amount);
if (HealthCalculator.IsDead(currentHealth)) Die();
}
private void Die() { /* destroy, VFX, etc. */ }
}
优点:HealthCalculator 可在 EditMode 中立即测试,无需场景。
步骤 3:设置 Assembly Definitions
创建两个 .asmdef 文件:
Assets/Tests/EditMode/Game.Tests.EditMode.asmdef— 仅限Editor平台Assets/Tests/PlayMode/Game.Tests.PlayMode.asmdef— 所有平台
两者都必须引用游戏的运行时程序集,并在 defineConstraints 中包含 UNITY_INCLUDE_TESTS。
完整模板请参阅 references/test-templates.md。
步骤 4:编写测试
遵循与测试类型相对应的模板。命名约定:
[MethodName]_[Condition]_[ExpectedResult]
示例:
CalculateDamage_NormalHit_ReducesHealthTakeDamage_Overkill_ClampsToZeroStart_OnInitialize_SetsMaxHealth
步骤 5:运行并验证
- 打开 Unity Test Runner:
Window > General > Test Runner - 选择 EditMode 或 PlayMode 选项卡
- 点击
Run All,或选择特定测试 - 如需查看覆盖率:
Window > Analysis > Code Coverage
严格规则
- 始终使用
[SetUp]初始化,并使用[TearDown]清理 - 始终按
MethodName_Condition_ExpectedResult命名测试 - 绝不使用
Thread.Sleep— 使用yield return null或await - 绝不在 EditMode 测试中访问文件系统或网络
- 始终对浮点数使用
Assert.AreEqual(expected, actual, tolerance) - 一个测试 = 一个断言(或针对同一概念的一组断言)
- 必须使用 Assembly definitions — 绝不将测试放入主程序集
- 始终在
[TearDown]中使用Object.DestroyImmediate销毁创建的 GameObjects - 始终使用
[TestCase]编写参数化测试,而不是重复编写测试 - 绝不让测试相互依赖 — 每个测试都必须独立且幂等
不使用外部框架进行 Mock
Unity 不包含 mock 框架。使用接口 + 手动测试替身:创建一个接口(例如 IInputProvider),实现一个带可设置属性的 mock,并通过 [SerializeField] 或公共 setter 注入。
完整示例请参阅 references/test-templates.md。
相关技能
/unity-code-gen— 生成代码的同时生成测试/unity-refactor— 从现有 MonoBehaviour 中提取可测试的逻辑/unity-debug— 测试发现需要修复的错误时使用/perf-audit— 静态性能审计(检测反模式,不执行代码)
故障排除
| 问题 | 可能原因 | 解决方案 |
|---|---|---|
| 未检测到测试 | 缺少 .asmdef 或配置错误 | 检查 defineConstraints 和 references |
| PlayMode 很慢 | Scene 设置过于复杂 | 尽量简化:创建空 GameObjects,不使用复杂的预制件 |
| Async 测试超时 | 未处理 CancellationToken | 传入 token,检查最大时长 |
Start 中出现 NullReferenceException | AddComponent 后缺少 yield return null | 创建后始终等待一帧 |
| 测试在 EditMode 中通过,但在构建中失败 | 条件代码 #if UNITY_EDITOR | 将 editor 代码与 runtime 代码分开 |
Assert.AreEqual 对浮点数断言失败 | 浮点数精确比较 | 使用带容差的重载:Assert.AreEqual(1.0f, val, 0.001f) |