Unreal Engine 4 的自動化測試
最近幾年做測試似乎變成一個非常熱門的議題,而且也逐漸的被大多開發者了解到做測試的優點。不過,一般的軟體可以做測試倒是沒有什麼問題,那麼遊戲該怎麼做測試呢?
我自己認為這是一個很難探討的問題,大部份的遊戲就基於不確定性而變得有趣。在充滿不確定的情境下,要做測試就變得非常困難了。
不過,還是有像是基本的公式計算、數值檢查等等可以做基本的檢查,雖然無法完全的對遊玩上做完整的測試。但是至少可以確保功能上與數值上是以正確的數值做計算。
那麼,就來談談 Unreal Engien 4 的自動化測試工具 Automation Tools
吧!
根據官方文件的介紹,一共有五種功能包含在這個工具內。
- Unit Test - 單元測試
- Feature Test - 功能測試
- Smoke Test - 快速測試(我認為是這樣翻譯)
- Content Stress Test - (內容)壓力測試
- Screen Shot Comparison - 截圖比較
不過我個人認為前面三種類似都很相似,而在 Unreal Engine 裡面硬要去區分似乎也是有點困難的。
像是 Unit Test 的範例,使用的是 Somke Test 的模式
撰寫測試
這部分的測試比較接近 Unit Test 的形式,主要是針對遊戲中的 API 做測試。
首先,我們要在 [Project]/Private
目錄下新增一個 Tests
目錄,預設的測試檔案都會從這個目錄偵測。
官方並沒有指出修改目錄的方式,因此也只能這樣使用。
假設我要測試的是名為 NPCTalk
的功能,我的測試檔名就命名為 NPCTalkTest.cpp
基本上跟大部份的測試工具一樣。
1// 假設有一個 NPCTalk 的 Class 實作了一個 speak() 的靜態方法
2FString NPCTalk::speak() {
3 return TEXT("Hello World!");
4}
那麼,我們的測試檔案就可以這樣寫。
1#include "NPCTalk.h"
2#include "AutomationTest.h"
3
4IMPLEMENT_SIMPLE_AUTOMATION_TEST(FNPCTalkTest, "NPC.Talk.Speak", EAutomationTestFlags::ATF_SmokeTest);
5
6bool FNPCTalkTest::RunTest(const FString &Parameters)
7{
8 TestEqual(TEXT("NPC should says: Hello World!"), NPCTalk::speak(), TEXT("Hello World!"));
9
10 return true;
11}
如此一來,當我們執行測試的時候,就可以檢查傳回的字串是否跟預期的一項是否為 Hello World!
了!
IMPLEMENT_SIMPLE_AUTOMATION_TEST
是預先寫好的巨集,用來幫助我們產生測試用的 Class 參數依序為:名稱、識別名稱(會在 Frontend 上顯示)、測試類型。
測試類型基本上分為:遊戲、編輯器、命令列三種,詳細的請參考 AutomationTest.h 這個檔案。
這邊強烈建議用有自動補全的編輯器,因為官方文件基本上完全都沒有解釋使用的方式。
對 Actor 做測試
這部分就比較接近功能測試,用來檢測某些物件是否正常。不過缺點就是要做這些測試通常只有兩個選項,直接在遊戲地圖測試,或者製作專用的測試地圖。
這要依照測試情境去決定,像是只是要測試某一種類的怪是否正常,卻得把場景上數百隻怪都測試一遍,除了費時之外也很不方便。
1#include "MyActor.h"
2
3
4// Sets default values
5AMyActor::AMyActor()
6{
7 // Set this actor to call Tick() every frame. You can turn this off to improve performance if you don't need it.
8 PrimaryActorTick.bCanEverTick = true;
9
10}
11
12// Called when the game starts or when spawned
13void AMyActor::BeginPlay()
14{
15 Super::BeginPlay();
16
17}
18
19// Called every frame
20void AMyActor::Tick( float DeltaTime )
21{
22 Super::Tick( DeltaTime );
23
24}
25
26float AMyActor::CalcDamage() {
27 float randRatio = FMath::FRandRange(0.8, 1.2);
28 return 100.0f * randRatio;
29}
這邊我寫了一個簡易的 Acotr 並且時做了一個叫做 CalcDamage
的方法,隨機傳回 80 ~ 120 的傷害數值。
接下來我們會針對場景上所有為 MyActor
類型的物件做檢測。
因此,我們需要先創建一張新的地圖,並且把這些 Actor 放進場景。
如上圖所示,我在一張叫做 TestMap
的地圖中放置了三個類型是 MyActor
的物件。
接下來存擋即可,這樣我們就可以針對這三個物件做測試了!
1#include "AutomationTest.h"
2#include "AutomationCommon.h"
3#include "MyActor.h"
4
5DECLARE_LOG_CATEGORY_EXTERN(GameTest, All, All);
6DEFINE_LOG_CATEGORY(GameTest);
7
8IMPLEMENT_SIMPLE_AUTOMATION_TEST(FMyActorTest, "MyActor.CalcDamage", EAutomationTestFlags::ATF_Game);
9
10const float ACTOR_DAMAGE = 100.0f;
11const float MIN_DAMAGE_RATIO = 0.8f;
12const float MAX_DAMAGE_RATIO = 1.2f;
13
14bool FMyActorTest::RunTest(const FString &Parameters)
15{
16 UGameplayStatics::OpenLevel(GWorld, TEXT("TestMap"));
17 for(TObjectIterator<AMyActor> ActorIterator; ActorIterator; ++ActorIterator) {
18 AMyActor* MyActor = *ActorIterator;
19 if(MyActor) {
20 float actorDamage = MyActor->CalcDamage();
21 bool bIsDamageValid = actorDamage >= (ACTOR_DAMAGE * MIN_DAMAGE_RATIO) &&
22 actorDamage <= (ACTOR_DAMAGE * MAX_DAMAGE_RATIO);
23 TestTrue(TEXT("Damage should in the range 80.0 ~ 120.0"), bIsDamageValid);
24 } else {
25 UE_LOG(GameTest, Error, TEXT("No My Actor found"));
26 return false;
27 }
28 }
29
30 return true;
31}
基本上就跟前一段的測試差不多,不過這邊將 Flag 限制為 ATF_Game
表示只有在 Standlone Mode
的時候才能夠運行這個測試(因為 Actor 必須實際在遊戲中執行才能表現出正常的運作)
就理論上來說,也許能夠做到像是自動操作玩家打怪這類型的測試。 AI 可控制的並不限定是一般的怪,玩家也屬於 Pawn 是可被 AI 控制而非玩家的輸入設備(鍵盤、搖桿等)
上述程式碼的寫法是參考 Unreal 論壇的這篇文章去實作的,也是我唯一看到比較完整關於 Automation Tools 的使用範例。
直接用 TObjectIterator 就可以拿到場景物件蠻神奇的,不過大概就跟一般遊戲執行時呼叫一樣吧 XD
因為是 Standlone Mode
才能執行的測試,我們需要借助 Unreal Frontend
去幫我們開啟遊戲。
我試過用 Command Line / Editor 去開啟,都無法正常使用,這算是最方便使用的解法了! Mac/Windows 都在引擎的 Binaries 目錄下,稍微找一下就可以找到了(一般放在對應的作業系統目錄中)
進去 Project Launcher 分頁,先把左上角的 Project 設定成要測試的專案,然後按下 Launch
就會開啟一個獨立的遊戲出來。
開啟遊戲視窗後,進到 Session Frontend 裡面,選下面的 Automation
分頁,再點選 Find Workers
確保有抓到目前要測試的遊戲。
我的截圖有一個 Instance 是 Timeout 狀態,那是因為我上一次跑完關掉後就會呈現這樣的狀態(無法清除掉蠻痛苦的⋯⋯)
勾選好要跑的測試之後(預設只會跑幾個比較快的,一般是 Somke Test)讓他執行,如果順利通過就會像我這樣顯示綠色的。 同時也會發現遊戲地圖會切換到最後一個跑的測試地圖上,簡單說要做這類的測試會很花時間(苦笑)
可以看到功能測試佔用的時間很長,搭配持續整合工具(Jenkins)之類的可能可以解決這個問題。 昨天參加 Epic Games 的演講,官方人員告訴我是可行的。不過跟自動化測試一樣,很少人討論(折衷方案是利用區網空閒的電腦,丟 Frontend 去跑,但是作業系統不同的關係我無法測試這個方法)
小結
目前測試這個功能就到這邊,大致上不難應用。稍微熟悉的話至少能幫遊戲做一部分的測試,我想之後應該能節省掉不少時間吧!
雖然還有像是開發 Plugin 時能不能做測試等等的問題,以及整合 CI 後該怎麼跑,不過就現況來說已經算是讓人挺滿意了!
另外就是 Smoke Test 在 Editor 就可以用裡面的 Frontend 跑,不用特地單獨開 Frontend 來跑。