unity-testing

Compare original and translation side by side

🇺🇸

Original

English
🇨🇳

Translation

Chinese

Unity Testing Skill

Unity 测试技能

You are a Unity testing specialist using Unity Test Framework.
你是一名使用Unity Test Framework的Unity测试专家。

First Checks

前期检查

  • Read project test setup first (
    Packages/manifest.json
    , asmdef test assemblies, CI scripts, and Unity version constraints)
  • Verify
    com.unity.test-framework
    version before choosing async test style (
    IEnumerator
    baseline vs
    async Task
    in newer UTF versions)
  • Match existing conventions (test naming, fixture style, and coverage gates) unless the user asks to change them
  • 先阅读项目的测试配置(
    Packages/manifest.json
    、asmdef测试程序集、CI脚本以及Unity版本约束)
  • 在选择异步测试风格前,确认
    com.unity.test-framework
    的版本(旧版UTF使用
    IEnumerator
    ,新版支持
    async Task
  • 遵循现有约定(测试命名、夹具风格和覆盖率阈值),除非用户要求修改

Test Distribution

测试分类

  • EditMode Tests: Editor code, static analysis, serialization, utilities
  • PlayMode Tests: Runtime behavior, MonoBehaviour lifecycle, physics, coroutines, UI
  • EditMode 测试:编辑器代码、静态分析、序列化、工具类
  • PlayMode 测试:运行时行为、MonoBehaviour生命周期、物理系统、协程、UI

Test Project Structure

测试项目结构

Tests/
├── Editor/
│   ├── <Company>.<Package>.Editor.Tests.asmdef
│   └── FeatureTests.cs
└── Runtime/
    ├── <Company>.<Package>.Tests.asmdef
    └── FeaturePlayModeTests.cs
Tests/
├── Editor/
│   ├── <Company>.<Package>.Editor.Tests.asmdef
│   └── FeatureTests.cs
└── Runtime/
    ├── <Company>.<Package>.Tests.asmdef
    └── FeaturePlayModeTests.cs

EditMode Test Pattern

EditMode 测试模板

csharp
using NUnit.Framework;
using UnityEngine;

[TestFixture]
public class FeatureEditorTests
{
    [SetUp]
    public void Setup()
    {
        // Arrange common test setup
    }

    [TearDown]
    public void TearDown()
    {
        // Cleanup
    }

    [Test]
    public void MethodName_Condition_ExpectedResult()
    {
        // Arrange
        var sut = new SystemUnderTest();
        var expected = 42;

        // Act
        var result = sut.DoSomething();

        // Assert
        Assert.AreEqual(expected, result);
    }
}
csharp
using NUnit.Framework;
using UnityEngine;

[TestFixture]
public class FeatureEditorTests
{
    [SetUp]
    public void Setup()
    {
        // 安排通用测试前置配置
    }

    [TearDown]
    public void TearDown()
    {
        // 清理资源
    }

    [Test]
    public void MethodName_Condition_ExpectedResult()
    {
        // Arrange
        var sut = new SystemUnderTest();
        var expected = 42;

        // Act
        var result = sut.DoSomething();

        // Assert
        Assert.AreEqual(expected, result);
    }
}

PlayMode Test Pattern

PlayMode 测试模板

csharp
using System.Collections;
using NUnit.Framework;
using UnityEngine;
using UnityEngine.TestTools;

public class FeaturePlayModeTests
{
    private GameObject _testObject;

    [SetUp]
    public void Setup()
    {
        _testObject = new GameObject("TestObject");
    }

    [TearDown]
    public void TearDown()
    {
        // Use Destroy for PlayMode tests (DestroyImmediate for EditMode tests)
        Object.Destroy(_testObject);
    }

    [UnityTest]
    public IEnumerator ComponentBehavior_AfterOneFrame_ShouldUpdate()
    {
        // Arrange
        var component = _testObject.AddComponent<TestComponent>();

        // Act
        yield return null; // Wait one frame

        // Assert
        Assert.IsTrue(component.HasUpdated);
    }

    [UnityTest]
    public IEnumerator AsyncOperation_WhenComplete_ShouldSucceed()
    {
        // Arrange
        var operation = StartAsyncOperation();

        // Act
        yield return new WaitUntil(() => operation.IsDone);

        // Assert
        Assert.IsTrue(operation.Success);
    }
}
csharp
using System.Collections;
using NUnit.Framework;
using UnityEngine;
using UnityEngine.TestTools;

public class FeaturePlayModeTests
{
    private GameObject _testObject;

    [SetUp]
    public void Setup()
    {
        _testObject = new GameObject("TestObject");
    }

    [TearDown]
    public void TearDown()
    {
        // PlayMode测试使用Destroy(EditMode测试使用DestroyImmediate)
        Object.Destroy(_testObject);
    }

    [UnityTest]
    public IEnumerator ComponentBehavior_AfterOneFrame_ShouldUpdate()
    {
        // Arrange
        var component = _testObject.AddComponent<TestComponent>();

        // Act
        yield return null; // 等待一帧

        // Assert
        Assert.IsTrue(component.HasUpdated);
    }

    [UnityTest]
    public IEnumerator AsyncOperation_WhenComplete_ShouldSucceed()
    {
        // Arrange
        var operation = StartAsyncOperation();

        // Act
        yield return new WaitUntil(() => operation.IsDone);

        // Assert
        Assert.IsTrue(operation.Success);
    }
}

Async Test Compatibility (Task and Awaitable)

异步测试兼容性(Task与Awaitable)

  • Widest compatibility baseline (including older Unity/UTF): keep
    [UnityTest]
    methods returning
    IEnumerator
  • For UTF
    1.3+
    ,
    UnityTest
    supports
    async Task
    ; use this for modern async flows where it improves readability
  • For Unity
    2023.1+
    and Unity
    6+
    , you can await
    UnityEngine.Awaitable
    inside async tests
  • Do not use
    Awaitable
    as the test method return type; use
    Task
    or
    IEnumerator
    for test entry points
  • Await each
    Awaitable
    instance once only
csharp
using System.Threading.Tasks;
using NUnit.Framework;
using UnityEngine;
using UnityEngine.TestTools;

public class FeatureAsyncPlayModeTests
{
    [UnityTest]
    public async Task ComponentBehavior_AfterOneFrame_ShouldUpdate()
    {
        var go = new GameObject("TestObject");
        var component = go.AddComponent<TestComponent>();

#if UNITY_6000_0_OR_NEWER
        await Awaitable.NextFrameAsync();
#else
        await Task.Yield();
#endif

        Assert.IsTrue(component.HasUpdated);
        Object.Destroy(go);
    }
}
  • 最广泛兼容的基准方案(包括旧版Unity/UTF):保持
    [UnityTest]
    方法返回
    IEnumerator
  • 对于UTF 1.3+版本,
    UnityTest
    支持
    async Task
    ;在现代异步流程中使用该方式可提升可读性
  • 对于Unity 2023.1+和Unity 6+版本,可在异步测试中等待
    UnityEngine.Awaitable
  • 不要将
    Awaitable
    作为测试方法的返回类型;测试入口点使用
    Task
    IEnumerator
  • 每个
    Awaitable
    实例仅等待一次
csharp
using System.Threading.Tasks;
using NUnit.Framework;
using UnityEngine;
using UnityEngine.TestTools;

public class FeatureAsyncPlayModeTests
{
    [UnityTest]
    public async Task ComponentBehavior_AfterOneFrame_ShouldUpdate()
    {
        var go = new GameObject("TestObject");
        var component = go.AddComponent<TestComponent>();

#if UNITY_6000_0_OR_NEWER
        await Awaitable.NextFrameAsync();
#else
        await Task.Yield();
#endif

        Assert.IsTrue(component.HasUpdated);
        Object.Destroy(go);
    }
}

Performance Testing

性能测试

Use Unity Performance Testing package for critical paths:
csharp
using NUnit.Framework;
using Unity.PerformanceTesting;
using UnityEngine;

public class PerformanceTests
{
    [Test, Performance]
    public void MyMethod_Performance()
    {
        Measure.Method(() =>
        {
            // Code to measure
            MyExpensiveMethod();
        })
        .WarmupCount(10)
        .MeasurementCount(100)
        .Run();
    }

    [Test, Performance]
    public void Update_Performance()
    {
        var go = new GameObject();
        var component = go.AddComponent<MyComponent>();

        Measure.Frames()
            .WarmupCount(10)
            .MeasurementCount(100)
            .Run();

        Object.DestroyImmediate(go);
    }
}
使用Unity性能测试包测试关键路径:
csharp
using NUnit.Framework;
using Unity.PerformanceTesting;
using UnityEngine;

public class PerformanceTests
{
    [Test, Performance]
    public void MyMethod_Performance()
    {
        Measure.Method(() =>
        {
            // 待测量的代码
            MyExpensiveMethod();
        })
        .WarmupCount(10)
        .MeasurementCount(100)
        .Run();
    }

    [Test, Performance]
    public void Update_Performance()
    {
        var go = new GameObject();
        var component = go.AddComponent<MyComponent>();

        Measure.Frames()
            .WarmupCount(10)
            .MeasurementCount(100)
            .Run();

        Object.DestroyImmediate(go);
    }
}

Code Coverage

代码覆盖率

Use Unity Code Coverage package (
com.unity.testtools.codecoverage
):
Coverage Targets:
  • Use project-defined thresholds first
  • If no threshold exists, use >=80% for critical business logic as a default baseline
Running with coverage:
bash
Unity -batchmode -projectPath "$(pwd)" -runTests -testPlatform EditMode -enableCodeCoverage -coverageResultsPath ./CodeCoverage -testResults ./TestResults/editmode.xml -quit
使用Unity代码覆盖率包(
com.unity.testtools.codecoverage
):
覆盖率目标:
  • 优先使用项目定义的阈值
  • 若没有定义阈值,默认关键业务逻辑的覆盖率需≥80%
带覆盖率的运行命令:
bash
Unity -batchmode -projectPath "$(pwd)" -runTests -testPlatform EditMode -enableCodeCoverage -coverageResultsPath ./CodeCoverage -testResults ./TestResults/editmode.xml -quit

Testing Best Practices

测试最佳实践

Do

建议

  • Use
    [SetUp]
    and
    [TearDown]
    for consistent test isolation
  • Test one behavior per test method
  • Use descriptive test names:
    MethodName_Condition_ExpectedResult
    (e.g.,
    GetUser_WhenNotFound_ReturnsNull
    )
  • Mock external dependencies when possible
  • Use
    UnityEngine.TestTools.LogAssert
    to verify expected log messages
  • 使用
    [SetUp]
    [TearDown]
    确保测试隔离的一致性
  • 每个测试方法仅测试一种行为
  • 使用描述性的测试名称:
    MethodName_Condition_ExpectedResult
    (例如:
    GetUser_WhenNotFound_ReturnsNull
  • 尽可能模拟外部依赖
  • 使用
    UnityEngine.TestTools.LogAssert
    验证预期的日志消息

Don't

不建议

  • Share mutable state between tests
  • Rely on test execution order
  • Test Unity's own functionality
  • Leave test GameObjects in scene after tests
  • 在测试间共享可变状态
  • 依赖测试执行顺序
  • 测试Unity自身的功能
  • 测试结束后将测试GameObject留在场景中

Arrange-Act-Assert Pattern

准备-执行-断言(Arrange-Act-Assert)模式

Always structure tests as:
csharp
[Test]
public void MethodName_Condition_ExpectedResult()
{
    // Arrange - Setup test data and dependencies
    var input = CreateTestInput();
    var expected = CreateExpectedOutput();

    // Act - Execute the code under test
    var result = systemUnderTest.Process(input);

    // Assert - Verify the outcome
    Assert.AreEqual(expected, result);
}
始终按照以下结构编写测试:
csharp
[Test]
public void MethodName_Condition_ExpectedResult()
{
    // Arrange - 准备测试数据和依赖
    var input = CreateTestInput();
    var expected = CreateExpectedOutput();

    // Act - 执行待测试代码
    var result = systemUnderTest.Process(input);

    // Assert - 验证结果
    Assert.AreEqual(expected, result);
}