在游戏开发中,手动测试很快就会变得重复并且容易出错。当您开发新功能或尝试修复错误时,您是否发现自己处于这些看似无休止的测试周期中?
通过自动化代码测试,您可以将更多时间花在创意游戏开发上,而将更少的时间花在重复(但重要)的 QA 任务上,以确保添加、删除或更改代码不会破坏您的项目。
Unity 可帮助您使用 Unity 测试框架为您的游戏创建、管理和运行自动化测试。
Unity 测试框架 (UTF) 允许您在 编辑 和 播放 模式下测试项目代码。您还可以针对各种平台(例如独立平台、iOS 或 Android)编写测试代码。
通过使用 包管理器将 UTF 添加到您的项目中来安装它。
在底层,UTF 与 NUnit集成,后者是 .NET 语言的著名开源测试库。
您可以使用 UTF 编写两大类测试:编辑模式和播放模式:
编辑模式测试在 Unity 编辑器中运行,可以访问编辑器和游戏代码。这意味着您可以测试自定义编辑器扩展或使用测试来修改编辑器中的设置并进入播放模式,这对于调整检查器值然后使用许多不同设置运行自动测试很有用。
播放模式测试让您在运行时练习您的游戏代码。测试通常使用[UnityTest]属性作为协同程序运行。这使您可以测试可跨多个框架运行的代码。默认情况下,播放模式测试将在编辑器中运行,但您也可以在为各种目标平台构建的独立播放器中运行它们。
要遵循此示例,您需要从 Unity Asset Store 安装 Starter Assets – Third Person Character Controller 包 ,并将其导入到新项目中。
通过 窗口 > 包管理器安装 UTF。在包管理器中的 Unity Registry 下搜索测试框架。确保选择版本 1.3.3(撰写本文时的最新版本)。
安装 UTF 后,使用文本编辑器打开 Packages/manifest.json 文件,并在依赖项后添加可测试部分,如下所示:
,
"testables": [
"com.unity.inputsystem"
]
保存文件。当您需要引用 Unity.InputSystem.TestFramework 程序集来测试和模拟玩家输入时,这将会很有用。
返回编辑器并允许安装新版本。
单击 窗口 > 常规 > 测试运行器 以查看 测试运行器 编辑器窗口。
在本教程的这一部分,重点将放在创建播放模式测试上。您不需要使用“测试运行器”窗口中的“创建测试程序集文件夹”选项,而是可以使用“项目”窗口来创建它们。
突出显示项目资产文件夹的根目录,右键单击并选择 创建> 测试> 测试程序集文件夹。
添加了一个 测试 项目文件夹,其中包含一个 Tests.asmdef(程序集定义)文件。这对于测试引用您的游戏模块和依赖项是必需的。
角色控制器代码将在测试中引用,并且也需要程序集定义。接下来,您将设置一些程序集定义和引用,以便于模块之间的测试。
右键单击 Assets/StarterAssets/InputSystem 项目文件夹,然后选择 “创建”>“程序集定义”。将其命名为描述性的名称,例如 StarterAssetsInputSystem。
选择新的 StarterAssetsInputSystem.asmdef 文件,然后使用 Inspector 将 Assembly Definition Reference 添加到 Unity.InputSystem。单击“应用”。
右键单击 Assets/StarterAssets/ThirdPersonController/Scripts 项目文件夹,然后选择 “创建”>“程序集定义”。将其命名为描述性的名称,例如 ThirdPersonControllerMain。
与上一个程序集定义一样,在 Inspector 中打开 ThirdPersonControllerMain 并选择以下引用:
- Unity.InputSystem
- StarterAssetsInputSystem
单击 “应用”。
要模拟输入系统的各个部分,您需要在测试中引用它。此外,您还需要在为第三人称控制器代码创建的程序集中引用 StarterAssets 命名空间。
在检查器中打开 Tests.asmdef 并添加对以下程序集定义的引用:
- UnityEngine.TestRunner
- UnityEditor.TestRunner
- Unity.InputSystem
- Unity.InputSystem.TestFramework
- ThirdPersonControllerMain
单击“应用”。
您的第一个测试将涵盖从第三人称控制器包加载和移动主角的一些基础知识。
首先使用一个简单的测试环境场景和一个要使用的角色预制资源来设置新项目。
打开名为 Assets/StarterAssets/ThirdPersonController/Scenes/Playground.unity 的 场景,并使用 文件 > 另存为 菜单将其副本保存到此新路径:Assets/Scenes/SimpleTesting.unity
如果您注意到游戏视图中的粉红色材质,请使用渲染管道转换器将材质从内置渲染管道升级到通用渲染管道 (URP)。请参阅 本文以获得快速概览。
在项目资产文件夹中创建一个名为 Resources 的新文件夹。注意:文件夹名称“Resources”在这里很重要,因为它允许使用 UnityResources.Load() 方法。
将场景视图中的 PlayerArmatureGameObject 拖放到新的 Resources 文件夹中,并在出现提示时选择创建 Original Prefab。将预制资产重命名为 Character。
这将是您今后测试中使用的基本角色预制件。
从新的 SimpleTesting 场景中移除 PlayerArmature 游戏对象,并将更改保存到场景。
对于初始测试设置的最后一步,转到 文件>构建设置,然后选择添加打开场景以将 场景/SimpleTesting 场景添加到构建设置中。
选择项目资产文件夹中的测试文件夹。右键单击并选择 创建>测试>C# 测试脚本。
将新脚本命名为 CharacterTests。在 IDE 中打开脚本进行仔细查看。
初始类文件提供了两个方法存根,演示了一些测试基础知识。
接下来,您将确保测试加载“以测试为中心”的游戏场景。这应该是一个包含测试您关注的系统或组件所需的最低限度的内容的场景。
更新 CharacterTests 类以添加两个新的 using 语句,并实现 InputTestFixture 类:
using UnityEngine.InputSystem;
using UnityEngine.SceneManagement;
公共类 CharacterTests:InputTestFixture
在 CharacterTests 类的顶部添加两个私有字段:
GameObject 角色 = Resources.Load<GameObject>("角色");
键盘键盘;
角色字段将存储从资源文件夹加载的角色预制件的引用。键盘 将保存对输入系统提供的键盘输入设备的引用。
通过在 CharacterTests 类中提供自己的方法,覆盖基础 InputTestFixture 类的 Setup() 方法:
公共覆盖无效设置()
{
SceneManager.LoadScene("Scenes/SimpleTesting");
base.Setup();
keyboard = InputSystem.AddDevice<Keyboard>();
var mouse = InputSystem.AddDevice<Mouse>();
Press(mouse.rightButton);
Release(mouse.rightButton);;
}
Setup() 方法运行基类 Setup() 方法,然后通过加载测试场景和初始化键盘输入设备来设置您自己的 CharacterTests 类。
添加鼠标输入纯粹是为了让第三人称控制器开始接收来自模拟/虚拟键盘设备的输入。这几乎就像一个“设置焦点”的动作。
对于您的第一个测试,您将从预制件中实例化角色并断言它不为空。将以下方法添加到您的测试类:
[测试]
public void TestPlayerInstantiation()
{
游戏对象 characterInstance = 游戏对象.Instantiate(character, Vector3.zero, Quaternion.identity);
Assert.That(characterInstance, !Is.Null);
}
当您在那里时,您可能想要清理示例模板测试方法。删除 CharacterTestsSimplePasses 和 CharacterTestsWithEnumeratorPasses 方法。
保存脚本并返回编辑器中的 测试运行器 窗口。突出显示 TestPlayerInstantiation 测试并单击 Run Selected。
绿色复选标记表示测试通过。您已断言该角色可以从资源中加载、实例化到测试场景中,并且此时不为空。
您可能已经注意到,此测试使用的是 [Test] 注释,而不是 [UnityTest] 注释。UnityTest 属性允许协同程序在多个帧上运行测试。在这种情况下,您只需实例化角色并断言它已被加载。
一般来说,您应该在编辑模式下使用 NUnit Test 属性而不是 UnityTest 属性,除非您需要产生特殊指令、需要跳过一帧或在播放模式下等待一定时间。
接下来,您将使用 UnityTest,因为您断言按住前进控制器键会使角色前进。
将下面提供的新测试方法添加到您的 CharacterTests 类。
出现了两种新的测试辅助方法;Press() 和 Release()。这些都是由 InputTestFixture 基类提供的,通过模拟 InputSystem 控制的按下和释放来帮助您。
TestPlayerMoves() 方法执行以下操作:
在位置处从角色预制件实例化角色实例(X:0,是:0,Z:0)
按住虚拟键盘上的向上箭头键 1 秒钟,然后松开
再等待 1 秒(让角色减速并停止移动)
断言角色已经移动到 Z 轴上大于 1.5 个单位的位置。
保存文件,返回测试运行器并运行新测试。
接下来,您将通过添加一个简单的 Player Health 组件来测试自定义的 Monobehaviour 脚本。
在 Assets/StarterAssets/ThirdPersonController/Scripts下创建一个新脚本。将其命名为 PlayerHealth。
在 IDE 中打开脚本并用下面提供的代码替换内容。
这里添加了很多新代码。总结一下,这个脚本会判断玩家角色是否处于坠落状态。如果在坠落状态下撞击地面一次,那么角色的生命值就会减少10%。
在 Assets/Resources下找到 Character Prefab。打开预制件并添加新的 PlayerHealth 脚本组件。
接下来,您将使用测试场景来断言玩家从壁架上掉下来后其健康状况会下降。
使用 [UnityTest] 属性,您可以编写一个用于测试跌落伤害的播放模式测试。当掉落超过 0.2 秒时,玩家应该受到 0.1f 伤害(相当于最大生命值的 10%)。
在 SimpleTesting 场景中,您将看到通向壁架的楼梯。这是一个测试平台,用于在其上生成角色并测试 PlayerHealth 脚本。
再次打开 CharacterTests.cs 并添加一个名为 TestPlayerFallDamage 的新测试方法:
[UnityTest]
公共 IEnumerator TestPlayerFallDamage()
{
// 在测试场景中足够高的区域生成角色
游戏对象 characterInstance = 游戏对象.Instantiate(character, new Vector3(0f, 4f, 17.2f), Quaternion.identity);
// 获取对 PlayerHealth 组件的引用并断言当前处于完全健康状态 (1f)
var characterHealth = characterInstance.GetComponent<PlayerHealth>();
Assert.That(characterHealth.Health, Is.EqualTo(1f));
// 走下悬崖,等待坠落
Press(keyboard.upArrowKey);
产生返回新的WaitForSeconds(0.5f);
Release(keyboard.upArrowKey);
产生返回新的WaitForSeconds(2f);
// 断言由于跌落伤害而损失了 1 点生命值
Assert.That(characterHealth.Health, Is.EqualTo(0.9f));
}
您还需要在类文件的最顶部添加对 StarterAssets 命名空间的 使用 引用:
使用 StarterAssets;
上述测试遵循测试中常见的典型 安排、操作、断言(AAA)模式:
单元测试方法的Arrange部分初始化对象并设置传递给被测方法的数据的值。
Act部分使用安排好的参数调用被测试的方法。在这种情况下,当玩家跌落到地面时,调用被测方法由物理交互来处理。
Assert 部分验证被测方法的操作是否按预期运行。
返回编辑器,运行新的测试。在播放模式下,您将看到角色走下边缘、跌倒(超过 0.2 秒的阈值才可归类为跌倒)并在撞击地面后受到伤害。
测试不仅可以用于测试代码更改不会破坏功能,还可以作为文档或指针,帮助开发人员在调整设置时考虑游戏的其他方面。
一旦您开始构建一套测试,下一步就是在构建完成后自动运行它们。构建后运行的自动化单元和集成测试有助于尽早捕获回归或错误。它们还可以作为 云中远程自动构建系统的一部分运行。
通常,您需要以自定义格式捕获测试运行结果,以便与更广泛的受众共享结果。为了在 Unity 编辑器之外捕获测试结果,您需要分离构建和运行过程。
在您的测试项目文件夹中创建一个名为 SetupPlaymodeTestPlayer的新脚本。
SetupPlaymodeTestPlayer 类将实现 ITestPlayerBuildModifier 接口。您将使用它来覆盖并“挂钩”到ModifyOptions 方法,该方法接收构建的播放器选项,并允许您修改它们。
使用System.IO;
using UnityEditor;
using UnityEditor.TestTools;
[集会:TestPlayerBuildModifier(typeof(SetupPlaymodeTestPlayer))]
公共类SetupPlaymodeTestPlayer:ITestPlayerBuildModifier
{
公共 BuildPlayerOptions 修改选项(BuildPlayerOptions playerOptions)
{
playerOptions.options &= ~(BuildOptions.AutoRunPlayer | BuildOptions.ConnectToHost);
var buildLocation = Path.GetFullPath("TestPlayers");
var fileName = Path.GetFileName(playerOptions.locationPathName);
如果 (!string.IsNullOrEmpty(fileName))
buildLocation = Path.Combine(buildLocation, fileName);
playerOptions.locationPathName = buildLocation;
返回玩家选项;
}
}
当在播放模式下运行测试时,此自定义 Player Build 修改器脚本执行以下操作(运行位置:在播放器上):
禁用内置播放器的自动运行并跳过尝试连接到正在运行的主机的播放器选项
将构建路径位置更改为项目内的专用路径(TestPlayers)
完成后,您现在可以预期构建完成后将位于 TestPlayers 文件夹中。这现在完成了构建修改并切断了构建和运行之间的链接。
接下来,您将实施结果报告。这将允许您将测试结果写入自定义位置,以备自动生成和发布报告。
在您的测试项目文件夹中创建一个名为 ResultSerializer(如下所示)的新脚本。该类将使用对 TestRunCallback 的程序集引用并实现 ITestRunCallback 接口。
ITestRunCallback 的实现包含一个自定义的 RunFinished 方法,该方法设置播放器构建并进行测试,以将测试结果写入名为 testresults.xml的 XML 文件。
通过结合 SetupPlaymodeTestPlayer.cs 和 ResultSerializer.cs,构建和运行过程现在被分开。运行测试将会把结果输出到位于播放器平台的 Application.persistentDataPath 位置的 testresults.xml 中。
要使用这些钩子类中的某些类型,您需要添加对 Tests.asmdef的额外引用。更新它以添加 UnityEditor.UI.EditorTests 程序集定义引用。
在播放器中运行测试现在将在 TestPlayers 文件夹中的项目下产生播放器构建输出,并在 Application.persistentDataPath 位置产生 testresults.xml 文件。
Unity 测试框架课程
测试框架包中包含 一个测试课程, 其中包含示例练习,可帮助您了解有关使用 Unity 进行测试的更多信息。请务必使用包管理器获取课程的项目文件。
使用 包管理器>软件包:Unity 注册中心>测试框架,找到示例下拉列表并导入课程练习。
练习将被导入到您的项目中并位于 Assets/Samples/Test Framework下。每个示例都包含一个供您使用的练习文件夹,以及一个可供您在练习过程中与自己的工作进行比较的解决方案。
使用 UTF 对您的代码进行 QA
这次 Unite Copenhagen 讨论 对 UTF 进行了更详细的介绍,并提供了一些其他有趣的测试定制用例。请务必检查一下,看看还有什么可能。
Unity中的调试
阅读以下文章来加速 Unity 中的调试工作流程:
- Microsoft Visual Studio Code
高级技术电子书
Unity提供了许多高级指南来帮助专业开发人员优化游戏代码。创建 C# 样式指南:编写更简洁、可扩展的代码 汇集行业专家关于如何创建代码样式指南的建议,以帮助您的团队开发干净、可读且可扩展的代码库。
另一个受我们用户欢迎的指南是 《70多条关于如何提高 Unity 生产力的技巧》。它包含了许多节省时间的技巧,可帮助您通过 Unity 2020 LTS 改善日常工作流程,其中包括经验丰富的开发人员可能会错过的技巧。
文档
进一步探索最新的 TestRunner API,了解其他 UTF 自定义属性,并通过 UTF 文档发现更多生命周期。
在 Unity 最佳实践中心查找 Unity 的所有高级电子书和文章。