main() blog

プログラムやゲーム、旅、愛する家族について綴っていきます。

【UE5】自動テストツールを使ってみよう!

概要

UEでは自動テストを行う環境、ツールが提供されています。

たとえば

  • アセットの設定などをチェックする
  • 各レベルの読み込みをチェックする
  • キャラクターの読み込み、アクションのチェックをする
  • ゲームプレイ中のパフォーマンステスト

などのテストが考えらますが、これらのテストを手動で行うのにはコストがかかってしまいます。
ですので、テストの自動化が行えるものは自動化していくことで人手を使用せずともテストを行うことできるようになります。

テストをする内容についてはもちろんプロジェクト側で実装する必要が出てきますが、自動テストを管理、実行するためのツールや、実装のためのフレームワークなどが用意されています。
これらのテストを自動テストして用意することでUEのエディタ上やコマンドラインなどから実行することが可能となります。
最終的にはCIに組み込んでテストできるような環境の構築を目指していきたいと思います。

UEではテストを記述する方法をいくつか提供しています。
他にもありそうですが使用されているものと思われるものをピックアップしておきます。
※Automation Dirverは現在は積極的な開発は行われていないとのことです

  • Automation Testing
  • Automation Spec
  • Automation Driver
  • Guntlet Automation Framework

今回は自動テストの使用方法と、基本的な機能と思われるAutomation Testingでのテスト方法について説明します。

UEの自動テストの機能を使用しなくてもプロジェクトで独自のテスト環境の構築も検討できると思いますが、まずはUEで提供されている機能について確認してみたいと思います。

動作環境

UnrealEngine 5.4.4

自動テストの有効化

自動テストは以前はエンジンに組み込まれていたようですが、UE5からはプラグインとして提供されているようです。
デフォルトの状態ですと自動テストの項目がツールに登録されていない状態となっています。

自動テストに関するプラグインもデフォルトの状態だと有効になっていません。

まずは自動テストに関するプラグインを有効にします。
とりあえず以下のプラグインを有効にしてみます。

  • Automation Driver Tests
  • Code Quality Unreal Test Plugin
  • Functional Testing Editor
  • Gauntlet
  • RHI Tests
  • Runtime Tests

※実際はFunctional Testing Editorを有効にすることでツールにテスト自動化が追加されるようです
※他の機能も必要になると思われるので一旦上記のプラグインを有効にしておきます

再起動を求められるのでエディタを再起動すると「プラグインが見つからないのダイアログ」が表示されるので再ビルドします。
ビルド後に起動してプラグインが有効になっていることを確認します。

[ツール]に[テスト自動化]が追加されていることを確認します。

セッションフロントエンドに"オートメーション"と"画面比較"が追加されていることを確認します。

エンジン側で用意されているテスト項目が確認できます。
テストしたい項目にチェックを入れて再生ボタンを押すことでテストが実行されます。

テストの追加方法

プロジェクト側で独自のテストを追加する方法についてです。

C++実装

C++で実装する際に以下のクラスが基底クラスとなっています。

FAutomationTestBase  
IAutomationLatentCommand

上記のクラスを継承して直接クラスを定義しなくても、以下のマクロでクラス定義が行えるようになっています。

IMPLEMENT_SIMPLE_AUTOMATION_TEST( TClass, PrettyName, TFlags )
IMPLEMENT_COMPLEX_AUTOMATION_TEST( TClass, PrettyName, TFlags )
DEFINE_LATENT_AUTOMATION_COMMAND(CommandName)
簡易テスト(SimpleTest

IMPLEMENT_SIMPLE_AUTOMATION_TESTのマクロを使用してクラスを定義します。
マクロの引数でクラス名、テスト名、フラグを指定します。
RunTestの関数をオーバーライドする必要があります。
この返却値がテスト結果となります。
trueが成功で、falseで失敗となります。

IMPLEMENT_SIMPLE_AUTOMATION_TEST(FHogeSimpleTest, "HogeTest.HogeSimpleTest", EAutomationTestFlags::EditorContext | EAutomationTestFlags::EngineFilter)
bool FHogeSimpleTest::RunTest(const FString& Parameters)
{
    UE_LOG(LogTemp, Log, TEXT("FHogeSimpleTest::RunTest()"));

    return true;
}

上記をソースのどこかに記述してビルドすることで、自動テストの項目に追加されます。
HogeTest.HogeSimpleTestが追加されていることが確認できます。

この状態でテストの実行を確認することができます。
返却値でtrueを返しているのでテストが成功となります。

falseを返すことで失敗となることが確認できます。

ここにテストコードを書いてtrue/falseを返すことで成功/失敗の結果を返すことができようになります。

複合テスト(ComplexTest)

複数のテストを行う場合に使用します。
IMPLEMENT_COMPLEX_AUTOMATION_TESTのマクロを使用してクラスを定義します。
GetTestsとRunTestをオーバーライドする必要があります。

GetTestsの関数でテスト名とテストに渡すコマンド(文字列)を設定します。
以下のような記述で実行するとRunTestが複数回実行されることが確認できます。

IMPLEMENT_COMPLEX_AUTOMATION_TEST(FHogeComplexTest, "HogeTest.HogeComplexTest", EAutomationTestFlags::EditorContext | EAutomationTestFlags::EngineFilter)
void FHogeComplexTest::GetTests(TArray<FString>& OutBeautifiedNames, TArray<FString>& OutTestCommands) const
{
    UE_LOG(LogTemp, Log, TEXT("FHogeComplexTest::GetTests()"));

    // テスト名とレベル名の文字列を登録.
    OutBeautifiedNames.Add("HogeTestMap01");
    OutTestCommands.Add("LV_TestMap01");

    OutBeautifiedNames.Add("HogeTestMap02");
    OutTestCommands.Add("LV_TestMap02");

    OutBeautifiedNames.Add("HogeTestMap03");
    OutTestCommands.Add("LV_TestMap03");
}

bool FHogeComplexTest::RunTest(const FString& Parameters)
{
    UE_LOG(LogTemp, Log, TEXT("FHogeComplexTest::RunTest() : [%s]"), *Parameters);

    // パラメータで受け取ったレベルの読み込み.
    //ADD_LATENT_AUTOMATION_COMMAND(FLoadGameMapCommand(Parameters));

    return true;
}

オートメンションの項目に追加した各項目のテストが表示されていることが確認できます。

コマンド(LatentCommand)

RunTestの関数内でADD_LATENT_AUTOMATION_COMMANDを使用してコマンドを登録することで順次処理を行うことができようになります。
コマンドはIAutomationLatentCommandのインターフェイスクラスを継承して作成します。

以下はエンジン側で用意されているコマンドの一部抜粋です。
AutomationCommon.hで定義されているので実装の参考になると思います。

// Wait for the given amount of time
FEngineWaitLatentCommand

// Execute command string
FExecStringLatentCommand

// Write a string to editor automation tests log
FEditorAutomationLogCommand

// Latent command to load a map in game
FLoadGameMapCommand

// Latent command to wait for map to complete loading
FWaitForMapToLoadCommand

// Latent command to wait for map to complete loading
FWaitForSpecifiedMapToLoadCommand

// Waits for shaders to finish compiling before moving on to the next thing.
FWaitForShadersToFinishCompilingInGame

例えば、以下の様な記述で3秒待ってからスクリーンショットを撮るコンソールコマンドを実行して、3秒待ってからLog出力を行う処理を実装することができます。
コマンドの登録のみなのでRunTestの関数自体はすぐに抜けてしまいますが、その後で登録されたコマンドが処理され、実行されていきます。
すべてのコマンドが完了した時点でテストが完了となります。

IMPLEMENT_SIMPLE_AUTOMATION_TEST(FHogeSimpleTest, "HogeTest.HogeSimpleTest", EAutomationTestFlags::EditorContext | EAutomationTestFlags::ClientContext | EAutomationTestFlags::EngineFilter)
bool FHogeSimpleTest::RunTest(const FString& Parameters)
{
    // Log出力のコマンドを登録.
    ADD_LATENT_AUTOMATION_COMMAND(FEditorAutomationLogCommand(TEXT("FHogeSimpleTest::RunTest() : start")));

    // 3秒待つコマンドを登録.
    ADD_LATENT_AUTOMATION_COMMAND(FEngineWaitLatentCommand(3.0f));

    // スクリーンショットを撮るコンソールコマンドのコマンドを登録.
    ADD_LATENT_AUTOMATION_COMMAND(FExecStringLatentCommand(TEXT("shot")));

    // 3秒待つコマンドを登録.
    ADD_LATENT_AUTOMATION_COMMAND(FEngineWaitLatentCommand(3.0f));

    // Log出力のコマンドを登録.
    ADD_LATENT_AUTOMATION_COMMAND(FEditorAutomationLogCommand(TEXT("FHogeSimpleTest::RunTest() : finish")));

    return true;
}

独自のコマンドの追加方法

DEFINE_LATENT_AUTOMATION_COMMANDのマクロを使用してプロジェクト側でコマンドを定義することができます。
Update関数をオーバーライドする必要があります。
返却値でtrueを返すと関数は終了します。
falseを返すとUpdate関数は継続されて次フレームで呼ばれます。

DEFINE_LATENT_AUTOMATION_COMMAND(FHogeLatentCommand);
bool FHogeLatentCommand::Update()
{
    UE_LOG(LogTemp, Log, TEXT("FHogeLatentCommand::Update()"));

    return true;
}

定義したコマンドは同様にADD_LATENT_AUTOMATION_COMMANDで追加できます。

IMPLEMENT_SIMPLE_AUTOMATION_TEST(FHogeSimpleTest, "HogeTest.HogeSimpleTest", EAutomationTestFlags::EditorContext | EAutomationTestFlags::ClientContext | EAutomationTestFlags::EngineFilter)
bool FHogeSimpleTest::RunTest(const FString& Parameters)
{
    // 追加したコマンドを登録.
    ADD_LATENT_AUTOMATION_COMMAND(FHogeLatentCommand());

    return true;
}

DEFINE_LATENT_AUTOMATION_COMMAND_ONE_PARAMETERを使用することでクラスに変数を持たせることできます。
マクロの引数で変数の型と変数名を指定することで関数内で使用することできます。

DEFINE_LATENT_AUTOMATION_COMMAND_ONE_PARAMETER(FHogeLatentCommand2, int, count);
bool FHogeLatentCommand2::Update()
{
    UE_LOG(LogTemp, Log, TEXT("FHogeLatentCommand2::Update() : [%d]"), count);

    if (count < 20)
    {
        // falseを返すと次のフレームでもUpdate()が呼ばれる.
        count++;
        return false;
    }

    return true;
}

また、コマンド登録時に変数の初期値も指定することができます。

IMPLEMENT_SIMPLE_AUTOMATION_TEST(FHogeSimpleTest, "HogeTest.HogeSimpleTest", EAutomationTestFlags::EditorContext | EAutomationTestFlags::ClientContext | EAutomationTestFlags::EngineFilter)
bool FHogeSimpleTest::RunTest(const FString& Parameters)
{
    // 追加したコマンドを登録.
    ADD_LATENT_AUTOMATION_COMMAND(FHogeLatentCommand2(0));

    return true;
}

パッケージ環境での自動テストの実行方法

パッケージ(もしくはステージングビルド)環境でコマンドラインから自動テストを実行する方法です。
パッケージ環境で実行する場合、IMPLEMENT_SIMPLE_AUTOMATION_TESTのフラグの指定でEAutomationTestFlags::ClientContextが必要になるので注意してください。

IMPLEMENT_SIMPLE_AUTOMATION_TEST(FHogeSimpleTest, "HogeTest.HogeSimpleTest", EAutomationTestFlags::EditorContext | EAutomationTestFlags::ClientContext | EAutomationTestFlags::EngineFilter)
bool FHogeSimpleTest::RunTest(const FString& Parameters)
{
    UE_LOG(LogTemp, Log, TEXT("FHogeSimpleTest::RunTest()"));

    return true;
}

この状態でビルドを行い、パッケージを作成します。
コマンドラインから実行ファイルの引数のオプションの-ExecCmdsで"Automation RunTest"を指定し、先ほどのテスト名を指定することで自動テストを実行することが可能です。

TestProject.exe -ExecCmds="Automation RunTests HogeTest.HogeSimpleTest;Quit" -log=outlog.txt

基本的な使用方法、テストの追加方法についてでした。
他の機能については引き続き調査していきたいと思います。

参考

historia.co.jp

historia.co.jp

dev.epicgames.com