unity-testing
Compare original and translation side by side
🇺🇸
Original
English🇨🇳
Translation
ChineseUnity Testing Skill
Unity 测试技能
You are a Unity testing specialist using Unity Test Framework.
你是一名使用Unity Test Framework的Unity测试专家。
First Checks
前期检查
- Read project test setup first (, asmdef test assemblies, CI scripts, and Unity version constraints)
Packages/manifest.json - Verify version before choosing async test style (
com.unity.test-frameworkbaseline vsIEnumeratorin newer UTF versions)async Task - Match existing conventions (test naming, fixture style, and coverage gates) unless the user asks to change them
- 先阅读项目的测试配置(、asmdef测试程序集、CI脚本以及Unity版本约束)
Packages/manifest.json - 在选择异步测试风格前,确认的版本(旧版UTF使用
com.unity.test-framework,新版支持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.csTests/
├── Editor/
│ ├── <Company>.<Package>.Editor.Tests.asmdef
│ └── FeatureTests.cs
└── Runtime/
├── <Company>.<Package>.Tests.asmdef
└── FeaturePlayModeTests.csEditMode 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 methods returning
[UnityTest]IEnumerator - For UTF ,
1.3+supportsUnityTest; use this for modern async flows where it improves readabilityasync Task - For Unity and Unity
2023.1+, you can await6+inside async testsUnityEngine.Awaitable - Do not use as the test method return type; use
AwaitableorTaskfor test entry pointsIEnumerator - Await each instance once only
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);
}
}- 最广泛兼容的基准方案(包括旧版Unity/UTF):保持方法返回
[UnityTest]IEnumerator - 对于UTF 1.3+版本,支持
UnityTest;在现代异步流程中使用该方式可提升可读性async Task - 对于Unity 2023.1+和Unity 6+版本,可在异步测试中等待
UnityEngine.Awaitable - 不要将作为测试方法的返回类型;测试入口点使用
Awaitable或TaskIEnumerator - 每个实例仅等待一次
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.codecoverageCoverage 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 -quitTesting Best Practices
测试最佳实践
Do
建议
- Use and
[SetUp]for consistent test isolation[TearDown] - Test one behavior per test method
- Use descriptive test names: (e.g.,
MethodName_Condition_ExpectedResult)GetUser_WhenNotFound_ReturnsNull - Mock external dependencies when possible
- Use to verify expected log messages
UnityEngine.TestTools.LogAssert
- 使用和
[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);
}