GGua 日本語リファレンスEnglishGitHub ↗
英語版が正式な参照です。内容に差異がある場合は、英語版を優先してください。
UNITY / TESTING

テストの仕方

Gua.Testing.UnityはUnity Editor Play ModeまたはWindows Mono Playerを起動し、共通のIGuaContextへ接続します。

1. テストパッケージを追加する

Gua.Testing.UnityはNuGetで公開されています。次のコマンドは最新の安定版を解決します。projectへ書き込まれたversionをcommitし、3つのGuaパッケージを同じversion系列へ揃えてください。

Unity test hostを追加powershell
dotnet add package Gua.Core
dotnet add package Gua.Testing
dotnet add package Gua.Testing.Unity

パッケージごとの役割

コマンド追加される機能
dotnet add package Gua.CoreGuaの共通型とnative runtime境界を追加します。
dotnet add package Gua.TestingGodot版と共通のSemantic locator、action、待機、assertion、diagnosticsを追加します。
dotnet add package Gua.Testing.UnityUnity Editor/Playerのbuild・起動・bridge接続・終了処理を行うhostを追加します。

2. PlayerまたはEditorを起動する

ビルドしてrendered Playerをテストcsharp
using Gua.Core;
using Gua.Testing;
using Gua.Testing.Unity;

var player = UnityPlayerBuilder.Build(
    @"game/Assets/Scenes/Title.unity");
using var host = UnitySceneTestHost.LoadRenderedPlayer(
    player, UnitySceneTestHostOptions.StrictIsolation);

GuaAssertions.GetByRole(
    host.Context, "button", "Start Game"
).ToBeVisible();

await GuaAssertions.GetById(
    host.Context, "start"
).ClickAsync();

await GuaAssertions.WaitForTextAsync(
    host.Context, "loading", "Loading..."
);

var screenshot = host.CaptureScreenshot();

Unityテストを処理順に読む

記述意味
using Gua.Core;reset policyなど共通runtime型を読み込みます。
using Gua.Testing;Semantic locator、action、待機、assertionを読み込みます。
using Gua.Testing.Unity;Unity Player builderとscene test hostを読み込みます。
UnityPlayerBuilder.Build("...Title.unity")Unity Editorをbatchmodeで起動し、指定sceneを含むWindows x64 Mono Playerを一時directoryへbuildします。
LoadRenderedPlayer(player, StrictIsolation)描画付きPlayerを起動してbridgeへ接続し、開始・終了時の状態漏れを厳格に検査します。
GetByRole(..., "button", "Start Game")UI Toolkit/uGUI/TMPの違いを意識せず、roleと名前でButtonを検索します。
.ToBeVisible()現在のSemantic snapshotでButtonが表示されていることを確認します。
GetById(..., "start").ClickAsync()固定ID startへclickを送り、Unity listenerが同期処理を終えた結果まで待ちます。
WaitForTextAsync(..., "loading", "Loading...")新しいsnapshotを再取得し、画面側の結果が実際に公開されるまで待ちます。
host.CaptureScreenshot()現在frameより後の描画完了時にUnityへPNG取得を要求します。rendered hostが必要です。
using var hostscope終了時にbridgeを閉じ、設定に従ってUnity processを終了します。
入口用途
LoadPlayer-batchmode -nographicsの既存Player。高速なsemanticテスト向け
LoadRenderedPlayer描画付き既存Player。screenshot/Visual比較向け
LoadEditor指定sceneをEditor Play Modeで起動
BuildAndLoadPlayersceneからWindows x64 Mono Playerをビルドして起動

3. Godot版と同じSemantic APIで操作する

host.Contextはengine共通のIGuaContextです。Unity固有APIでButtonを探すのではなく、Godot版と同じlocatorと状態待機を利用します。

入力・focus・状態待機csharp
var name = GuaAssertions.Query(host.Context)
    .ByRole("textbox").Within("LoginForm").Get();

await name.SetValueAsync("alice");
await name.FocusAsync();
await GuaAssertions.PressKeyAsync(host.Context, "Enter");

await GuaAssertions.GetById(host.Context, "RememberMe")
    .SetCheckedAsync(true);
await name.WaitForValueAsync("alice");

各Semantic操作の意味

記述意味
Query(host.Context).ByRole("textbox")Unity UIをtextboxという共通roleで検索します。
.Within("LoginForm").Get()LoginForm配下に範囲を絞り、ちょうど1件の一致を要求します。
SetValueAsync("alice")TextField、uGUI InputField、TMP_InputFieldのいずれでも共通actionとして値を設定します。
FocusAsync()対象をUnityのfocus経路へ設定し、続くglobal key入力の宛先にします。
PressKeyAsync(..., "Enter")focus中のtextboxへkey-down/key-up gestureを送ります。
SetCheckedAsync(true)UI ToolkitまたはuGUI/TMPのcheckbox/toggleをchecked状態にします。
WaitForValueAsync("alice")action結果だけでなく、次の公開frameで値が観測されるまで待ちます。

安定したUnityテストにする4原則

目的に合うhostを選ぶ

Semantic testはheadless、screenshot/Visualはrendered PlayerまたはEditorを使います。

状態を待つ

Unityのframe時間を固定sleepで推測せず、期待するnode・text・valueを待ちます。

testごとに分離する

空きportとStrict resetでprocess間の衝突や未消費requestを検出します。

失敗証拠を残す

UI Tree、操作履歴、Unity log、process情報、必要ならPNGを保存します。

Visual比較と操作記録

Gua.Testing.VisualGua.Testing.Recordingはengine共通パッケージなので、Gua.Testing.Unityhost.Contextでも利用できます。Visual比較にはPNGを取得できるLoadRenderedPlayerまたはEditor Play Modeを使います。Recordingはsemantic actionを扱うためheadless Playerでも利用できます。

Unity hostで共通パッケージを使うcsharp
using Gua.Testing.Recording;
using Gua.Testing.Visual;

var comparison = await GuaVisualAssertions.ExpectScreenshotAsync(
    host.Context, "title-screen", new() {
        BaselineDirectory = "baselines",
        ArtifactDirectory = "artifacts/gua",
    });

var recorder = new GuaRecorder(host.Context);
await recorder.ClickAsync(new(Id: "start"));

VisualとRecordingの記述

記述意味
using Gua.Testing.Visual;engine非依存のPNG baseline比較APIを読み込みます。
ExpectScreenshotAsync(host.Context, "title-screen", ...)UnityへPNGを要求し、title-screenというtest名のreview済みbaselineと比較します。
BaselineDirectory = "baselines"期待画像をversion管理するdirectoryです。通常実行では暗黙更新しません。
ArtifactDirectory = "artifacts/gua"不一致時のactual、expected、diff、manifestを保存します。
new GuaRecorder(host.Context)同じUnity contextへ送るSemantic操作をrecordingへ記録します。
recorder.ClickAsync(new(Id: "start"))startへのclickを実行し、request-correlated完了を待って再生可能なstepとして残します。

並列実行と分離

UseAvailableBridgePortは既定でtrueです。ホストがloopbackの空きポートを予約し、子プロセスへGUA_BRIDGE_PORTを渡すため、固定8765の衝突を避けられます。

ただし番号を選んだ直後にlistenerを閉じてからUnityを起動するため、bind完了までを予約する仕組みではありません。極端な並列負荷では小さな競合余地が残ります。

明示オプションcsharp
var options = new UnitySceneTestHostOptions {
    UnityExecutablePath = @"C:/Program Files/Unity/Hub/Editor/6000.5.3f1/Editor/Unity.exe",
    ProjectPath = @"C:/src/game",
    UseAvailableBridgePort = true,
    ConnectTimeout = TimeSpan.FromSeconds(60),
    TeardownResetPolicy = GuaResetPolicy.Strict,
    CaptureDiagnosticsBeforeTeardown = true,
};

Unity host optionの意味

option意味
UnityExecutablePath使用するUnity.exeを明示します。省略時は環境変数とHub installを探索します。
ProjectPathAssetsとProjectSettingsを含むUnity project rootです。
UseAvailableBridgePort = trueloopbackの空きportを選び、GUA_BRIDGE_PORTとして子processへ渡します。
ConnectTimeout = 60秒Unity起動後、bridgeへ接続できるまで待つ上限です。
TeardownResetPolicy = Strict終了時の未消費request/eventを黙って捨てず失敗として報告します。
CaptureDiagnosticsBeforeTeardown = trueteardown失敗時、Unity processを終了する前に証拠を取得します。

診断とスクリーンショット

CreateDiagnosticsSession()はUI Tree、操作履歴、runtime diagnosticsに加え、-logFileで得たUnity log、PID、bridge URL、log pathをartifactへ含めます。stdout/stderrを別々にはredirectしません。必要ならcaptureScreenshot: trueを指定します。Strict teardownは未消費request/event等を黙って消さず失敗として報告します。

assertion失敗時にUnity証拠を保存csharp
using var diagnostics = host.CreateDiagnosticsSession(
    TestContext.CurrentContext.Test.FullName,
    outputDirectory: "artifacts/gua",
    captureScreenshot: true);

using var assertionScope = GuaAssertionScope.Use(
    new GuaAssertionOptions {
        DiagnosticsSession = diagnostics,
    });

GuaAssertions.GetByRole(
    host.Context, "button", "Start Game"
).ToBeVisible();

診断設定の意味

記述意味
host.CreateDiagnosticsSession(...)共通Gua diagnosticsへUnity process固有のlogとmetadataを加えるsessionを作ります。
TestContext...Test.FullNameNUnitのtest名をartifact識別子にします。
outputDirectory: "artifacts/gua"失敗証拠の保存先を指定します。
captureScreenshot: truesemantic情報に加えてon-demand PNGも取得します。headless hostでは利用できません。
GuaAssertionScope.Use(...)scope内のGua assertion失敗とdiagnostics sessionを関連付けます。
.ToBeVisible()失敗時は元のassertionを主例外として保ったまま証拠を保存します。

SceneTimeoutは現状、主にscreenshot待機の既定値です。Scene遷移を自動で待つAPIではありません。