什么是 Gauntlet Gauntlet 官方文档
简而言之,Gauntlet 提供支持来启动多个实例来进行测试,例如常见的 Dedicated Server 测试,需要一个服务器和多个客户端
但是 Gauntlet 并不关心测试的内容,只提供一个需要开发者实现的 GauntletTestController 来控制测试的流程
所以 Gauntlet 的侧重点在于 组织单个或多个实例 ,而非测试的 内容和形式 (包括使用的测试框架,测试的内容等)
具体的测试可以由开发者自己编写的 GauntletTestController 以 任意方式 来测试 任意内容
本文的开发环境 操作系统:windows 11
引擎:UE 5.0 源码版本
IDE:Rider for Unreal
Gauntlet 的原理 两个部分 Gauntlet 分为两个部分
游戏外自动化部分,属于 AutomationTool
游戏内 Runtime 部分,属于 Gauntlet 插件,通过 Gauntlet Test Controller 驱动游戏内逻辑
其中游戏外部分,控制游戏实例的参数和启动
游戏内部分,控制具体的测试逻辑
AutomationTool 官方文档
AutomationTool 可简称 UAT
是虚幻引擎中用于自动化流水线的一套工具,主要用于 Build 项目和测试等,这套工具由 C# 编写
执行
1 "%EnginePath%\Engine\Build\BatchFiles\RunUAT"
即可执行 Automation 命令,命令的基类为
1 public abstract class BuildCommand : CommandUtils
开发者也可以自己添加 Automation 项目
在其中添加相关 Automation 中需要的自定义类型,或者继承 BuildCommand 为 UAT 添加自定义命令
Automation 项目都需要以 XXX.Automation 来命名,且放在 [ProjectDir]\Build 或 [EngineDir]\Source\Programs 目录下
Build 时需要使用源码版本的引擎,从而在每次 Build 的时候重新生成自定义 Automation 项目的 dll
Gauntlet.Automation 游戏外自动化部分 Gauntlet 正是 Automation 中的一个项目,也就是上文所说的游戏外自动化部分
RunUnreal 命令 Gauntlet 项目提供了 RunUnreal 命令来进行 Gauntlet 测试
1 public class RunUnreal : BuildCommand
命令入口
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 public override ExitCode Execute ( ){ Globals.Params = new Gauntlet.Params(this .Params); UnrealTestOptions ContextOptions = new UnrealTestOptions(); AutoParam.ApplyParamsAndDefaults(ContextOptions, Globals.Params.AllArguments); if (string .IsNullOrEmpty(ContextOptions.Project)) { throw new AutomationException("No project specified. Use -project=ShooterGame etc" ); } ContextOptions.Namespaces = "Gauntlet.UnrealTest,UnrealGame,UnrealEditor" ; ContextOptions.UsesSharedBuildType = true ; return RunTests(ContextOptions); }
测试参数 Context
1 public class UnrealTestOptions : TestExecutorOptions , IAutoParamNotifiable
其中就包含了上面所说的启动多个实例需要用到的包括 Platform 类型、Build 类型、包文件地址、多个实例的 Build Target 类型、Role 类型(客户端还是服务器)、加载的地图之类的参数
RunTest
1 public virtual ExitCode RunTests (UnrealTestOptions ContextOptions )
包含测试逻辑, ExitCode 中会返回测试是否通过
执行函数时,会先根据我们传入的 Context 来查找存在的符合条件的可执行文件,然后打开一个或多个游戏实例来进行测试
ExecuteTests
1 bool ExecuteTests (UnrealTestOptions Options, IEnumerable<ITestNode> TestList )
具体测试逻辑入口,准备好实例的 Context 信息后,会传入测试列表,执行这个函数开始测试
测试的具体CSharp类型 1 public abstract class BaseTest : ITestNode
这个就是测试的基类,包含了测试所需的基本逻辑链条,在实际使用中,需要使用其子类
1 2 public abstract class UnrealTestNode <TConfigClass > : BaseTest , IDisposable where TConfigClass : UnrealTestConfiguration , new ()
来派生出开发者自己的测试类型,并在其中指定 Gauntlet Controller 和游戏实例配置等信息
这个类型中,还包括一些测试结果相关的功能,例如输入测试报告等
在实际使用的章节我们会继续提到这个类型
我们现在只需要知道,测试的列表,是通过 UAT 的命令行传入的
启动实例
1 protected bool PrepareUnrealApp ( )
在测试 Node 开始测试的时候,这个函数会被执行,来启动游戏实例
在这个函数中,我们会发现上文所说的 两个部分 连接起来的秘密
1 2 3 SessionRole.CommandLineParams.Add("gauntlet" , TestRole.Controllers.Count > 0 ? string .Join("," , TestRole.Controllers) : null );
这里,把 Gauntlet Test Controller 通过命令行的形式,加入到了游戏实例的启动参数中!
也就是说游戏实例启动后,一定是去获取了 gauntlet=xxx,xxx,xxx 参数,来获得需要执行的 gauntlet test controller 列表
这个我们在后面再继续聊
当 Controller 准备好以后,最终执行了
1 UnrealApp = new UnrealSession(Context.BuildInfo, SessionRoles) { Sandbox = Context.Options.Sandbox };
来启动实例
我们可以看到,这个实例是以 UnrealSession 的形式存在的
UnrealSession 1 public class UnrealSession : IDisposable
Unreal Session 用于启动、监控、关闭游戏实例
Gauntlet 游戏内插件部分
可以看到,插件非常简单,基本就只有一个 Gauntlet Test Controller
并提供了两个示例 BootTest 和 Error Test
加载 Gauntlet 命令行的位置 GauntletModule.cpp
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 void FGauntletModuleImpl::OnPostEngineInit () { FCoreUObjectDelegates::PostLoadMapWithWorld.AddRaw (this , &FGauntletModuleImpl::InnerPostMapChange); FCoreUObjectDelegates::PreLoadMap.AddRaw (this , &FGauntletModuleImpl::InnerPreMapChange); FParse::Value (FCommandLine::Get (), TEXT ("gauntlet.screenshotperiod=" ), ScreenshotPeriod); FParse::Value (FCommandLine::Get (), TEXT ("gauntlet.heartbeatperiod=" ), HeartbeatPeriod); LoadControllers (); float kTickRate = 1.0f ; FParse::Value (FCommandLine::Get (), TEXT ("gauntlet.tickrate=" ), kTickRate); TickHandle = FTSTicker::GetCoreTicker ().AddTicker (FTickerDelegate::CreateLambda ([this , kTickRate](float TimeDelta) { InnerTick (kTickRate); return true ; }), kTickRate); }
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 void FGauntletModuleImpl::LoadControllers () { FString ControllerString; FParse::Value (FCommandLine::Get (), TEXT ("gauntlet=" ), ControllerString, false ); if (ControllerString.Len ()) { TArray<FString> ControllerNames; ControllerString.ParseIntoArrayWS (ControllerNames, TEXT ("," )); TSet<FString> ControllersToCreate; for (const FString& Name : ControllerNames) { UClass* TestClass = nullptr ; for (TObjectIterator<UClass> It; It; ++It) { UClass* Class = *It; FString FullName = Name; FString PartialName1 = Name + TEXT ("Controller" ); FString PartialName2 = FString (TEXT ("Controller" )) + Name; if (Class->IsChildOf<UGauntletTestController>()) { FString ClassName = Class->GetName (); if (ClassName == FullName || ClassName.EndsWith (PartialName1) || ClassName.EndsWith (PartialName2)) { bool GauntletDefault = ClassName.StartsWith (TEXT ("Gauntlet" )); TestClass = Class; if (!GauntletDefault) { break ; } } } } checkf (TestClass, TEXT ("Could not find class for controller %s" ), *Name); UGauntletTestController* NewController = NewObject<UGauntletTestController>(GetTransientPackage (), TestClass); check (NewController); UE_LOG (LogGauntlet, Display, TEXT ("Added Gauntlet controller %s" ), *Name); Controllers.Add (NewController); } } for (auto Controller : Controllers) { Controller->OnInit (); } }
可以看到,在 PostEngineInit 后,开始 LoadControllers
然后读取了命令行,并使用其名称,去获得 C++ 的相应类型
然后执行其 OnInit
GauntletTestController 简析 声明
1 2 UCLASS ()class GAUNTLET_API UGauntletTestController : public UObject
里面是一些简单的状态控制,几个处理函数都需要自己去实现
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 virtual void OnInit () {}virtual void OnPreMapChange () {}virtual void OnPostMapChange (UWorld* World) {}virtual void OnTick (float TimeDelta) {}virtual void OnStateChange (FName OldState, FName NewState) {}
正所谓什么都没写,就有无限可能(狗头。
前面说的可以测试任何内容就是这个意思,你可以通过写一个自己的 TestController 来添加任何逻辑和接入任何测试框架。
从上面的 OnInit 那里就可以简单推测出,整个 TestController 是由 GauntletModuleImpl 来驱动的,事实呢也是如此,上面几个函数的调用都是在 GauntletModuleImpl 中。
从零开始的Gauntlet实战 现在我们已经了解了 Gauntlet 的基本原理,那么现在我们从零开始使用 Gauntlet 搭建一个简单的测试
版本是 UE 5.0 源码编译版本
搭建源码环境 因为编译 Automation 项目需要源码版本的引擎,所以需要搭建源码环境
创建一个新的游戏项目 这里我们选择 First Person 模版,创建一个 C++ 项目,取名 GauntletTutorial
然后把项目依赖的引擎版本设置为源码,编译启动编辑器
打开 Plugin 设置,启用 Gauntlet 插件
因为我们测试的时候需要启动 Server 和 Client 实例,所以我们需要在项目中添加 Server Build Target 以及 Client Build Target
详见 官网文档说明
我们这里只需要正确添加 GauntletTutorialServer.Target.cs 和 GauntletTutorialClient.Target.cs 两个文件即可
制作 Automation 工程 创建项目 官网文档说明
注:5.0 以前的版本和 5.0 以后的版本使用的 .Net 依赖不同,创建方式略有不同
我这里 IDE 使用 Rider ,使用 VS 的同学应该也差不太多
选择 File -> New Solution -> .Net/.Net Core -> Class Library
填入你喜欢的项目名称,记住之前说过的两个规则
项目以 XXX.Automation 命名
项目放在 [ProjectDir]\Build 下
我们这里在 [ProjectDir]\Build 目录下创建一个 Gauntlet 目录,把我们的项目放进去
然后 Framework 选择 netcoreapp3.1
勾上 Put Solution and project in the same directory
点击创建生成项目
编写一个测试 原理部分讲到,我们的每个测试都是继承自
1 2 public abstract class UnrealTestNode <TConfigClass > : BaseTest , IDisposable where TConfigClass : UnrealTestConfiguration , new ()
的一个子类
其中,TConfigClass 是我们测试使用的配置类型,继承自 UnrealTestConfiguration
我们现在来创建一个自己的测试
修改默认创建的 Class1 ,改名为 GauntletTutorialTest,并使用 UnrealTestConfig 类型作为配置,类声明如下
1 public class GauntletTutorialTest : UnrealTestNode <UnrealTestConfig >
可以看到 IDE 提示缺少依赖,我们需要引入 Gauntlet.Automation 项目作为依赖
项目目录右键点击 Dependence ,打开 Reference
点击 Add From
选择 [EnginePath]\unrealengine\Engine\Binaries\DotNET\AutomationScripts\netcoreapp3.1\Gauntlet.Automation.dll
然后修改代码,最终如下
1 2 3 4 5 6 7 8 9 10 11 using Gauntlet;using UnrealGame;namespace GauntletTutorial.Automation { public class GauntletTutorialTest : UnrealTestNode <UnrealTestConfig > { public GauntletTutorialTest (UnrealTestContext inContext ) : base (inContext ) { } } }
我们现在创建出了一个新的测试,但如何设置测试参数呢?答案是重写函数 GetConfiguration()
1 2 3 4 /// <summary> /// Returns information about how to configure our Unreal processes. For the most part the majority /// of Unreal tests should only need to override this function /// </summary>
此函数的说明:大部分情况下,我们只需要重写这个函数就可以了
现在我们大致写一个测试配置
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 public override UnrealTestConfig GetConfiguration ( ){ UnrealTestConfig config = base .GetConfiguration(); UnrealTestRole clientRole = config.RequireRole(UnrealTargetRole.Client); UnrealTestRole serverRole = config.RequireRole(UnrealTargetRole.Server); config.MaxDuration = 60 * 5 ; config.MaxDurationReachedResult = EMaxDurationReachedResult.Success; clientRole.Controllers.Add("GauntletTestControllerErrorTest" ); serverRole.Controllers.Add("GauntletTestControllerErrorTest" ); return config; }
另外除了这些配置,我们可以看到 UnrealTestConfig 和其父类中还有很多注解为 AutoParam 的变量
这些变量都可以通过 RunUnreal 的命令行参数来自动获取并赋值
在测试中,我们指定每个 Role 都执行系统自带的 GauntletTestControllerErrorTest 来测试
至此一个简单的测试就完成了
运行编写好的测试 通过之前的原理部分,我们知道,Gauntlet Automation 是去查找打好的可执行文件来运行的,所以运行前,应该保证你测试中的 Role 实例已经打好包了,例如 Server 就要打好服务器包
在 [ProjectDir] 下新建一个 Scripts 目录
打包 新建一个批处理 Pack.bat 来进行打包
1 2 3 4 5 6 7 8 @echo on set ProjectPath=项目目录set EnginePath=源码版引擎目录"%EnginePath%\Engine\Build\BatchFiles\RunUAT" BuildCookRun -project=%ProjectPath%\GauntletTutorial.uproject -platform=Win64 -configuration=Development -build -cook -pak -stage -server -clientpause
执行后,会打出 Client 和 Server 包,因为是源码构建,可能会花比较久的时间
执行 RunUnreal 再新建一个批处理 RunGauntlet.bat
1 2 3 4 5 6 7 8 @echo on set ProjectPath=项目目录set StagingDir=%ProjectPath%\Saved\StagedBuilds\set EnginePath=源码版引擎目录"%EnginePath%\Engine\Build\BatchFiles\RunUAT" RunUnreal -project=%ProjectPath%\GauntletTutorial.uproject -build=%StagingDir% -platform=Win64 -configuration=development -test =GauntletTutorial.Automation.GauntletTutorialTest -scriptdir=%ProjectPath%pause
其中 platform 和 configuration 一定更要和打包配置一致,否则会找不到目标可执行文件
build 参数选择打出来的包的目录
test 参数填入 Gauntlet Automation 测试的全名
运行后,我们可以看到,按照配置启动了一个客户端和一个服务器,如下
然后测试结果是失败
原因是 GauntletTestControllerErrorTest 默认逻辑会在启动后立刻释放一个 check 让测试失败
传入命令行参数到 Automation 项目和游戏实例中 查看 UGauntletTestControllerErrorTest 的源码,我们可知这个测试会接受命令行参数,从而调用 check , ensure 等
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 void UGauntletTestControllerErrorTest::OnInit () { ErrorDelay = 0 ; ErrorType = TEXT ("check" ); FParse::Value (FCommandLine::Get (), TEXT ("errortest.delay=" ), ErrorDelay); FParse::Value (FCommandLine::Get (), TEXT ("errortest.type=" ), ErrorType); } void UGauntletTestControllerErrorTest::OnTick (float TimeDelta) { if (GetTimeInCurrentState () > ErrorDelay) { if (ErrorType == TEXT ("ensure" )) { UE_LOG (LogGauntlet, Display, TEXT ("Issuing ensure as requested" )); ensureMsgf (false , TEXT ("Ensuring false...." )); EndTest (-1 ); } else if (ErrorType == TEXT ("check" )) { UE_LOG (LogGauntlet, Display, TEXT ("Issuing failed check as requested" )); checkf (false , TEXT ("Asserting as requested" )); } else if (ErrorType == TEXT ("fatal" )) { UE_LOG (LogGauntlet, Fatal, TEXT ("Issuing fatal error as requested" )); } else if (ErrorType == TEXT ("gpf" )) { #ifndef PVS_STUDIO UE_LOG (LogGauntlet, Display, TEXT ("Issuing GPF as requested" )); int * Ptr = (int *)0 ; CA_SUPPRESS (6011 ); *Ptr = 42 ; #endif } else { UE_LOG (LogGauntlet, Error, TEXT ("No recognized error request. Failing test" )); EndTest (-1 ); } } }
那么我们怎么样才能从 Gauntlet 传入命令行参数到游戏呢?
官方例子 Nullrhi 参数 下面我们参考 UnrealTestConfiguration 是如何把 Nullrhi 这个参数传入游戏中的
我们回到 Automation 项目中,查看类型 我们使用的配置类型 UnrealTestConfig 的基类 Gauntlet.UnrealTestConfiguration
其中有一个函数是
1 public virtual void ApplyToConfig (UnrealAppConfig AppConfig, UnrealSessionRole ConfigRole, IEnumerable<UnrealSessionRole> OtherRoles )
这个函数的作用就是组装测试的配置,其中 UnrealAppConfig 就是实例启动的相关参数
在 ApplyToConfig 函数中有这几行代码
1 2 3 4 if (Nullrhi){ AppConfig.CommandLine += " -nullrhi" ; }
程序读取了 Nullrhi 这个成员变量,并判断如果为 True 则在 AppConfig 的命令行参数,也就是实例启动的命令行参数中加入 “ -nullrhi” ,从而让客户端不进行渲染
1 2 3 4 5 6 [AutoParam(false) ] protected bool Nullrhi { get ; set ; }
Nullrhi 成员使用了注解 [AutoParam(false)]
这个注解的作用是直接从 RunUnreal 的命令行获取参数进行初始化,这个命令行传入有两种写法
我们下面来尝试用这两种写法来使用 nullrhi 参数
修改我们的 RunGauntlet.bat
方法一
1 2 3 4 5 6 7 8 9 10 11 12 @echo on set ProjectPath=项目目录set StagingDir=%ProjectPath%\Saved\StagedBuilds\set EnginePath=引擎目录"%EnginePath%\Engine\Build\BatchFiles\RunUAT" RunUnreal -project=%ProjectPath%\GauntletTutorial.uproject ^-build=%StagingDir% -platform=Win64 -configuration=development ^ -test =GauntletTutorial.Automation.GauntletTutorialTest ^ -nullrhi ^ -scriptdir=%ProjectPath% pause
这里面,我们直接把 nullrhi 当做 RunUnreal 的命令行参数传入
方法二
1 2 3 4 5 6 7 8 9 10 11 @echo on set ProjectPath=项目目录set StagingDir=%ProjectPath%\Saved\StagedBuilds\set EnginePath=引擎目录"%EnginePath%\Engine\Build\BatchFiles\RunUAT" RunUnreal -project=%ProjectPath%\GauntletTutorial.uproject ^-build=%StagingDir% -platform=Win64 -configuration=development ^ -test =GauntletTutorial.Automation.GauntletTutorialTest(nullrhi) ^ -scriptdir=%ProjectPath% pause
这里面,我们可以在 test 参数中的的 Gauntlet 测试类型后打一个括号,传入我们的 nullrhi 参数
不管是哪种方式,修改后运行批处理,可以发现客户端没有渲染了,可知是 nullrhi 已经传入了游戏中,其完整流程为
通过命令行,参数被 AutoParam 传给了测试的配置类中的成员变量
在配置类的 ApplyToConfig 函数中,通过判断成员变量的值,并在 AppConfig 的 Commandline 中加入到游戏命令行
ApplyToConfig 也不光是可以修改命令行,其他用法也可以根据官方代码学习
尝试传入 ErrorTest 所需参数 我们使用的 Controller 中需要传入命令行参数 errortest.delay 和 errotest.type
于是乎,我们按照上面 nullrhi 的做法,先写一个新的配置类型,继承 UnrealTestConfiguration
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 using System;using System.Collections.Generic;using Gauntlet;namespace GauntletTutorial.Automation { public class GauntletTutorialConfig : UnrealTestConfiguration { [AutoParam(5) ] private float ErrorDelay { get ; set ; } [AutoParam ] private String ErrorType { get ; set ; } public override void ApplyToConfig (UnrealAppConfig AppConfig, UnrealSessionRole ConfigRole, IEnumerable<UnrealSessionRole> OtherRoles ) { base .ApplyToConfig(AppConfig,ConfigRole,OtherRoles); if (ErrorDelay > 0 ) { AppConfig.CommandLine += " -errortest.delay=" + ErrorDelay; AppConfig.CommandLine += " -errortest.type=" + ErrorType; } } } }
然后修改我们的 GauntletTutorialTest 的泛型参数为刚创建的新配置类型
1 2 3 4 5 6 7 8 9 ... public class GauntletTutorialTest : UnrealTestNode <GauntletTutorialConfig >{ public GauntletTutorialTest (UnrealTestContext inContext ) : base (inContext ) { } public override GauntletTutorialConfig GetConfiguration ( ) ...
然后修改 RunGauntlet.bat 加入参数 ErrorDelay 和 ErrorType
1 2 3 4 5 6 7 8 9 10 11 @echo on set ProjectPath=项目目录set StagingDir=%ProjectPath%\Saved\StagedBuilds\set EnginePath=引擎目录"%EnginePath%\Engine\Build\BatchFiles\RunUAT" RunUnreal -project=%ProjectPath%\GauntletTutorial.uproject ^-build=%StagingDir% -platform=Win64 -configuration=development ^ -test =GauntletTutorial.Automation.GauntletTutorialTest(ErrorDelay=20,ErrorType=ensure) ^ -scriptdir=%ProjectPath% pause
重新启动 RunGauntlet.bat ,发现 20 秒后,提示 ensure ,返回测试失败,我们的参数成功传入到游戏中
至此,我们的整个 Gauntlet 基本流程就算完成了
实战:实现一个基本的测试需求 需求 我们现在需要测试在 N 个客户端开启后登入一个服务器,确定所有客户端正确登入服务器则测试成功
创建测试配置类型 创建新的测试配置类型,其中指定一个参数 ClientCount,通过 RunUnreal 命令行传入,用于启动 N 个 Client 实例
然后用 AppConfig 通过命令行参数 -ClientCountToCheck 传入游戏中
可以看到我们这里使用的基类 Config 为 EpicGameTestConfig
使用这个的原因是里面包含了让客户端自动连接服务器的逻辑
LoginTestConfig.cs
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 using System.Collections.Generic;using Gauntlet;namespace GauntletTutorial.Automation { public class LoginTestConfig : EpicGameTestConfig { [AutoParam(1) ] public int ClientCount { get ; set ; } public override void ApplyToConfig (UnrealAppConfig AppConfig, UnrealSessionRole ConfigRole, IEnumerable<UnrealSessionRole> OtherRoles ) { base .ApplyToConfig(AppConfig, ConfigRole, OtherRoles); AppConfig.CommandLine += " -ClientCountToCheck=" + ClientCount; } } }
创建测试类型 在测试中,我们直接把配置中的 ClientCount 取出来,创建相应数量的客户端
然后服务器设置 TestController 为 GauntletTestLoginController,客户端不需要 Controller 所以不设置
设置 NoMCP 为 true,这样可以跳过 Gauntlet 内部客户端登录服务器的账号验证等步骤
如 60 秒还没测试成功,则判断为失败
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 using System.Collections.Generic;using Gauntlet;namespace GauntletTutorial.Automation { public class LoginTest : UnrealTestNode <LoginTestConfig > { public LoginTest (UnrealTestContext inContext ) : base (inContext ) { } public override LoginTestConfig GetConfiguration ( ) { LoginTestConfig config = base .GetConfiguration(); UnrealTestRole server = config.RequireRole(UnrealTargetRole.Server); server.Controllers.Add("GauntletTestLoginController" ); config.RequireRoles(UnrealTargetRole.Client, config.ClientCount); config.MaxDuration = 60 ; config.MaxDurationReachedResult = EMaxDurationReachedResult.Failure; config.NoMCP = true ; return config; } } }
创建 GauntletController 首先,在游戏项目中的 GauntletTutorial.build.cs 中
PublicDependencyModuleNames 中加入 “Gauntlet”
然后创建新 C++ 类型 UGauntletTestLoginController
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 #pragma once #include "CoreMinimal.h" #include "GauntletTestController.h" #include "GauntletTestLoginController.generated.h" UCLASS ()class GAUNTLETTUTORIAL_API UGauntletTestLoginController : public UGauntletTestController{ GENERATED_BODY () virtual void OnInit () override ; virtual void OnTick (float TimeDelta) override ; UFUNCTION () void OnClientLogin (AGameModeBase* GameMode, APlayerController* NewPlayer) ; int32 CurrentClientCount; int32 ClientCountShouldLogin; };
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 #include "GauntletTestLoginController.h" #include "GameFramework/GameModeBase.h" void UGauntletTestLoginController::OnInit () { Super::OnInit (); CurrentClientCount = 0 ; FParse::Value (FCommandLine::Get (), TEXT ("ClientCountToCheck=" ), ClientCountShouldLogin); FGameModeEvents::GameModePostLoginEvent.AddUObject (this ,&UGauntletTestLoginController::OnClientLogin); } void UGauntletTestLoginController::OnTick (float TimeDelta) { Super::OnTick (TimeDelta); if (CurrentClientCount == ClientCountShouldLogin) { EndTest (0 ); } } void UGauntletTestLoginController::OnClientLogin (AGameModeBase* GameMode, APlayerController* NewPlayer) { CurrentClientCount++; }
我们注册全局登入事件,然后判断登录的客户端数量是否能达到配置的人数
如果进入的数量和配置的数量相等了,则结束测试,返回代表成功的结果0
编写测试批处理 在 [ProjectDir]\Scripts 目录下创建 RunLoginTest.bat ,test 传入 LogintTest, 并传入参数 ClientCount 5
1 2 3 4 5 6 7 8 9 10 11 @echo on set ProjectPath=项目目录set StagingDir=%ProjectPath%\Saved\StagedBuilds\set EnginePath=引擎目录"%EnginePath%\Engine\Build\BatchFiles\RunUAT" RunUnreal -project=%ProjectPath%\GauntletTutorial.uproject ^-build=%StagingDir% -platform=Win64 -configuration=development ^ -test =GauntletTutorial.Automation.LoginTest(ClientCount=5) ^ -scriptdir=%ProjectPath% pause
打包,运行测试 运行 Pack.bat 打包
然后运行 RunLoginTest.bat 开启测试
可以看到当 6 个 device 都启动后,测试显示成功
然后查看 Server 的日志
日志在 [Engin]\GauntletTemp\DeviceCache\Win64[LocalDevice”N”]\UserDir\Saved\Logs 中
这里的服务器大概率应该是 LocalDevice0,其他的客户端应该是 LocalDevice1 ~ 5
日志中显示 5 个客户端登录成功,我们的需求完成
调试 RunUnreal 和 游戏实例 我们在学习和测试中,经常会有需要调试 Gauntlet Automation 或者在特定实例调试的情况
调试 RunUnreal 这时我们只需在 RunUAT 命令行参数中加入 -waitfordebugger
再运行 Gauntlet 测试,就会在启动时等待调试器接入
然后我们在 Automation 项目代码中打好断点,然后在 Rider 中选择 Run -> AttachToProcess
然后过滤输入 Automation ,在进程中选中 dotnet(AutomationTool.dll) 开始调试即可
调试特定游戏实例 我们知道游戏中传入参数 -waitfordebugger 就可以等待调试器接入
所以我们只需按照之前传参到特定实例的方法传入参数
然后在游戏项目中打好断点,attach 到游戏进程即可
相关学习和参考 现在我们已经了解了 Gauntlet 的基本使用
如果需要了解和学习更多的用法,可以
查看 Gauntlet Automation 项目中相关源代码,学习借鉴其中的用法
查看相关测官网文档
查看一些使用案例
可供参考的项目 DaedalicTestAutomation 插件 Github 地址
这个插件写了一套测试框架,然后也集成了使用 Gauntlet 来启动测试的方式,很有参考价值
ShooterGame 示例项目 官方示例项目中,包含了一些 Gauntlet Automation 和 Gauntlet Test Controller 的使用实例
本文项目的 Github 仓库 Github 仓库地址