1. テストパッケージを追加する
Gua.Testing.UnityはNuGetで公開されています。次のコマンドは互換性のある最新安定版を取得します。projectへ書き込まれたversionをcommitし、3つのGuaパッケージを同じversion系列へ揃えてください。
dotnet add package Gua.Core
dotnet add package Gua.Testing
dotnet add package Gua.Testing.Unityゲーム側と関連Guaパッケージの版を揃えます。取得後の版の記録・固定はバージョンの管理を参照してください。
パッケージごとの役割
| コマンド | 追加される機能 |
|---|---|
dotnet add package Gua.Core | Guaの共通型とnative runtime境界を追加します。 |
dotnet add package Gua.Testing | Godot版と共通のSemantic locator、action、待機、assertion、diagnosticsを追加します。 |
dotnet add package Gua.Testing.Unity | Unity Editor/Playerのbuild・起動・bridge接続・終了処理を行うhostを追加します。 |
2. PlayerまたはEditorを起動する
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 host | scope終了時にbridgeを閉じ、設定に従ってUnity processを終了します。 |
| 入口 | 用途 |
|---|---|
LoadPlayer | -batchmode -nographicsの既存Player。高速なsemanticテスト向け |
LoadRenderedPlayer | 描画付き既存Player。screenshot/Visual比較向け |
LoadEditor | 指定sceneをEditor Play Modeで起動 |
BuildAndLoadPlayer | sceneから指定desktop Mono Playerをビルドして起動 |
3. Godot版と同じSemantic APIで操作する
host.Contextはengine共通のIGuaContextです。Unity固有APIでButtonを探すのではなく、Godot版と同じlocatorと状態待機を利用します。
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原則
Semantic testはheadless、screenshot/Visualはrendered PlayerまたはEditorを使います。
Unityのframe時間を固定sleepで推測せず、期待するnode・text・valueを待ちます。
空き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でも利用できます。
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完了までを予約する仕組みではありません。極端な並列負荷では小さな競合余地が残ります。
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を探索します。 |
ProjectPath | AssetsとProjectSettingsを含むUnity project rootです。 |
UseAvailableBridgePort = true | loopbackの空きportを選び、GUA_BRIDGE_PORTとして子processへ渡します。 |
ConnectTimeout = 60秒 | Unity起動後、bridgeへ接続できるまで待つ上限です。 |
TeardownResetPolicy = Strict | 終了時の未消費request/eventを黙って捨てず失敗として報告します。 |
CaptureDiagnosticsBeforeTeardown = true | teardown失敗時、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等を黙って消さず失敗として報告します。
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.FullName | NUnitのtest名をartifact識別子にします。 |
outputDirectory: "artifacts/gua" | 失敗証拠の保存先を指定します。 |
captureScreenshot: true | semantic情報に加えて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失敗は自動再試行しません。
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更新の例を参照してください。
dotnet add package Gua.Testing.SnapshotsGua.Testing Traceでテストの証拠を保存する
TraceはGua.Testingの機能です。追加のTraceパッケージは不要です。テストscopeに設定し、実際の結果で完了させ、新しい出力先にoffline HTML reportを生成します。
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と制限を参照してください。