mcpField
U
Unity Test

Unity Test

github.com/juliankerignard测试与质量852 次安装31 周前更新
代码仓库

关于 Unity Test

Unity Test 是面向 Unity 开发者的测试编写指导技能,帮助分析待测代码、选择 EditMode 或 PlayMode,并生成 NUnit 测试。它还说明如何将 MonoBehaviour 中的逻辑提取为可独立测试的 C# 代码。

主要功能

  1. 01分析代码并选择测试模式
  2. 02从 MonoBehaviour 提取可测逻辑
  3. 03生成 NUnit 测试
  4. 04指导程序集定义与测试目录配置
  5. 05提供测试运行和故障排查建议

适用场景

  1. 01为纯 C# 逻辑编写 EditMode 测试
  2. 02为依赖场景生命周期的代码编写 PlayMode 测试
  3. 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/ 子文件夹

快速开始

  1. 分析目标代码(识别依赖项、副作用和 I/O)
  2. 根据决策树确定使用 EditMode 还是 PlayMode
  3. 如果代码与 MonoBehaviour 紧密耦合,则提取可测试逻辑
  4. 遵循相应模板编写测试
  5. 使用 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_ReducesHealth
  • TakeDamage_Overkill_ClampsToZero
  • Start_OnInitialize_SetsMaxHealth
步骤 5:运行并验证
  1. 打开 Unity Test Runner:Window > General > Test Runner
  2. 选择 EditMode 或 PlayMode 选项卡
  3. 点击 Run All,或选择特定测试
  4. 如需查看覆盖率:Window > Analysis > Code Coverage

严格规则

  1. 始终使用 [SetUp] 初始化,并使用 [TearDown] 清理
  2. 始终按 MethodName_Condition_ExpectedResult 命名测试
  3. 绝不使用 Thread.Sleep — 使用 yield return null 或 await
  4. 绝不在 EditMode 测试中访问文件系统或网络
  5. 始终对浮点数使用 Assert.AreEqual(expected, actual, tolerance)
  6. 一个测试 = 一个断言(或针对同一概念的一组断言)
  7. 必须使用 Assembly definitions — 绝不将测试放入主程序集
  8. 始终在 [TearDown] 中使用 Object.DestroyImmediate 销毁创建的 GameObjects
  9. 始终使用 [TestCase] 编写参数化测试,而不是重复编写测试
  10. 绝不让测试相互依赖 — 每个测试都必须独立且幂等

不使用外部框架进行 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 中出现 NullReferenceExceptionAddComponent 后缺少 yield return null创建后始终等待一帧
测试在 EditMode 中通过,但在构建中失败条件代码 #if UNITY_EDITOR将 editor 代码与 runtime 代码分开
Assert.AreEqual 对浮点数断言失败浮点数精确比较使用带容差的重载:Assert.AreEqual(1.0f, val, 0.001f)

评论

支持 Markdown