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

テストの仕方

Gua.Testing.UnityはUnity Editor Play Modeまたはdesktop 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

ゲーム側と関連Guaパッケージの版を揃えます。取得後の版の記録・固定はバージョンの管理を参照してください。

パッケージごとの役割

コマンド追加される機能
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で起動し、Autoまたは指定platformの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から指定desktop 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");

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

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

各Semantic操作の意味

記述意味
Query(host.Context).ByRole("textbox")Unity UIをtextboxという共通roleで検索します。
.Within("LoginForm")LoginForm配下へ絞ります。各actionが最新treeで厳密な単一一致を解決します。
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状態にします。
ByValue("alice").ResolveAsync()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.VisualとGua.Testing.Recordingはengine共通パッケージなので、Gua.Testing.Unityのhost.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ではありません。

操作できるUIを待つ

Gua.Testingのlocatorは使用時に最新のtreeから厳密な単一一致を解決します。actionはvisible/enabledと対応actionを待ち、requestIdに対応するhost completionを待ちます。複数一致は即時失敗です。解決・enqueue・completionは同じtimeoutを共有し、enqueue拒否やhost失敗は自動再試行しません。

クリック後に次の画面を待つcsharp
using Gua.Testing;

var play = GuaAssertions.Query(host.Context).ByRole("button", "Play");
await play.ClickAsync(timeout: TimeSpan.FromSeconds(5));
await GuaAssertions.Query(host.Context).ById("game-screen").ResolveAsync();

ResolveAsyncは次のnodeの存在を待ちます。テストで必要な状態のassertionも加えます。locator APIとtimeout規則。

ピクセルを使わず意味的な状態を比較する

Gua.Testing.Snapshotsを追加すると、UIのID、階層、text、value、stateをreview済みJSON baselineと比較できます。headlessでもhost.Contextで使えます。描画結果も調べる場合はGua.Testing.Visualを別途追加します。導入・mask・baseline更新の例を参照してください。

意味的な比較を追加するpowershell
dotnet add package Gua.Testing.Snapshots

Gua.Testing Traceでテストの証拠を保存する

TraceはGua.Testingの機能です。追加のTraceパッケージは不要です。テストscopeに設定し、実際の結果で完了させ、新しい出力先にoffline HTML reportを生成します。

テストを記録して確認するcsharp
using Gua.Testing;

await using var trace = new GuaTraceSession(new() {
    OutputDirectory = "artifacts/traces",
    SavePolicy = GuaTraceSavePolicy.Always,
});
using var scope = GuaAssertionScope.Use(new() { Trace = trace });
try {
    await GuaAssertions.Query(host.Context).ById("play").ClickAsync();
    await trace.CompleteAsync(GuaTraceOutcome.Passed);
} catch {
    await trace.CompleteAsync(GuaTraceOutcome.Failed);
    throw;
}
var report = GuaTraceReport.WriteHtml(trace.ArtifactPath, "artifacts/report.html");
// Check report.Succeeded and trace.Status separately from the test result.

Traceは証拠を記録し、flowの再生や結果の変更はしません。UI/WorldやObserve値が必要な場合は明示的に取得し、記録前に機密情報の扱いを決めます。Traceの取得API・Inspectorで保存済みTraceを開くを参照してください。

hostを選ぶときの対応範囲

Unity 6000.5.3f1のWindows/Linux/Intel macOS/Apple Silicon macOS desktop Mono Playerは実装・検証済みです。Editor Play ModeはWindows、Linux、Apple M4で検証済み、native Intel macOS Editorは実装済み・未検証です。Unity APIと制限を参照してください。