1. 必須NuGetパッケージを入れる
Godotの外部テストにはGua.Core、Gua.Testing、Gua.Testing.Godotの3パッケージが必要です。テストプロジェクトのディレクトリで次を実行して最新の安定版を解決し、projectへ書き込まれたversionをcommitします。
dotnet add package Gua.Core
dotnet add package Gua.Testing
dotnet add package Gua.Testing.Godot
# NUnitを使う場合
dotnet add package Microsoft.NET.Test.Sdk
dotnet add package NUnit
dotnet add package NUnit3TestAdapterコマンドごとの役割
| コマンド | 追加される機能 |
|---|---|
dotnet add package Gua.Core | Guaの共通型とnative runtime境界をテストprojectへ追加します。 |
dotnet add package Gua.Testing | Semantic locator、action、待機、assertion、diagnosticsを追加します。 |
dotnet add package Gua.Testing.Godot | Godot processの起動、bridge接続、終了処理を行うtest hostを追加します。 |
dotnet add package Microsoft.NET.Test.Sdk | dotnet testからtest runnerを起動する基盤です。 |
dotnet add package NUnit | [Test]やAssertなどNUnitのtest APIを追加します。 |
dotnet add package NUnit3TestAdapter | Microsoft.NET.Test.SdkからNUnit testを検出・実行できるようにします。 |
GodotがPATHにない場合は環境変数を設定します。
$env:GODOT_EXECUTABLE = "C:\path\to\Godot_v4.7-stable_mono_win64_console.exe"
dotnet test YourGame.Tests.csproj実行コマンドの意味
| 記述 | 意味 |
|---|---|
$env:GODOT_EXECUTABLE = "...console.exe" | このPowerShell processと子processに、test hostが起動すべきGodot実行ファイルを指定します。 |
dotnet test YourGame.Tests.csproj | 指定したtest projectをbuildし、検出されたNUnit testを実行します。 |
2. シーンを起動して意味で操作
using Gua.Testing;
using Gua.Testing.Godot;
using var host = GodotSceneTestHost.Load(
"res://Main.tscn",
new GodotSceneTestHostOptions {
ProjectPath = projectPath,
UseAvailableBridgePort = true,
StartupResetPolicy = GuaResetPolicy.Strict,
TeardownResetPolicy = GuaResetPolicy.Strict,
});
GuaAssertions.GetByRole(
host.Context, "button", "Start Game"
).ToBeVisible();
await GuaAssertions.GetById(
host.Context, "start"
).ClickAsync();
await GuaAssertions.WaitForTextAsync(
host.Context, "loading", "Loading..."
);テストコードを処理順に読む
| 記述 | 意味 |
|---|---|
using Gua.Testing; | locator、action、待機、assertion APIを読み込みます。 |
using Gua.Testing.Godot; | Godot process用test hostとoption型を読み込みます。 |
using var host = GodotSceneTestHost.Load(...) | Main.tscnを別Godot processで開き、bridge接続完了まで待ちます。usingによりtest終了時にprocessと接続を破棄します。 |
"res://Main.tscn" | Godot project内で起動するsceneのresource pathです。 |
ProjectPath = projectPath | project.godotがあるdirectoryをtest hostへ伝えます。 |
UseAvailableBridgePort = true | 空いているloopback portを選び、並列testで8765が衝突しにくくします。 |
StartupResetPolicy = GuaResetPolicy.Strict | test開始時に前sessionの未処理requestやeventがあれば、黙って消さず失敗させます。 |
TeardownResetPolicy = GuaResetPolicy.Strict | test終了時にも処理漏れを検査し、別testへの持ち越しを検出します。 |
GetByRole(..., "button", "Start Game") | 座標ではなくrole=buttonかつ名前=Start GameのControlを検索します。 |
.ToBeVisible() | そのControlが現在のsnapshotで表示状態だと確認します。 |
GetById(..., "start").ClickAsync() | 固定ID startへclick要求を送り、Godot adapterから同じrequest IDの完了結果が返るまで待ちます。 |
WaitForTextAsync(..., "loading", "Loading...") | 最新snapshotを再取得しながら、ID loadingのtextがLoading...になるまで待ちます。 |
3. フォーム操作
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");フォーム操作の意味
| 記述 | 意味 |
|---|---|
GuaAssertions.Query(host.Context) | 接続中のGodot UI Treeに対するSemantic queryを組み立てます。 |
.ByRole("textbox") | 候補をtextbox roleに絞ります。 |
.Within("LoginForm") | ID LoginFormの子孫だけを検索範囲にします。 |
.Get() | 候補がちょうど1件ならlocatorを返し、0件または複数なら曖昧さを報告します。 |
name.SetValueAsync("alice") | textboxへ値変更を要求し、hostの処理完了まで待ちます。 |
name.FocusAsync() | 同じtextboxへfocus actionを送り、次のglobal key入力先にします。 |
PressKeyAsync(host.Context, "Enter") | 現在focus中のControlへEnterのkey-down/key-upを配送します。 |
GetById(..., "RememberMe").SetCheckedAsync(true) | checkboxを固定IDで取得し、checked=trueへ変更します。 |
name.WaitForValueAsync("alice") | action完了だけで終えず、公開されたvalueが実際にaliceになるまで待ちます。 |
安定したテストにする4原則
UseAvailableBridgePort = trueで並列テストの衝突を避けます。
開始・終了時のStrict resetで未消費要求やイベント漏れを検出します。
固定sleepではなく、状態・値・安定スナップショットを待ちます。
UI Tree、ログ、stdout/stderr、任意のPNGを診断成果物へ残します。
1. テストごとに空きポートを確保する
固定の8765を共有すると、並列実行時に一方のGodotだけがポートを取得します。ホストに空きポートを予約させ、子プロセスへGUA_BRIDGE_PORTとして渡します。
using var host = GodotSceneTestHost.Load(
"res://Main.tscn",
new GodotSceneTestHostOptions
{
ProjectPath = projectPath,
UseAvailableBridgePort = true,
});
// 予約されたURLは子プロセスとRemoteContextへ自動設定されます。空きポート設定の意味
| 記述 | 意味 |
|---|---|
GodotSceneTestHost.Load("res://Main.tscn", ...) | 指定sceneを起動し、Gua bridgeへ接続したhostを返します。 |
ProjectPath = projectPath | sceneを解決するGodot projectのdirectoryです。 |
UseAvailableBridgePort = true | OSに空きportを選ばせ、GUA_BRIDGE_PORTとしてGodot processへ渡します。 |
using var host | scope終了時に接続とGodot processを確実に終了します。 |
2. 開始前と終了前にStrict resetを行う
前のテストが残したノード・要求・イベントを次のテストへ持ち込みません。終了時に未消費の要求やイベントがあれば、黙って削除せずテストを失敗させます。
using var host = GodotSceneTestHost.Load(
"res://Main.tscn",
new GodotSceneTestHostOptions
{
ProjectPath = projectPath,
UseAvailableBridgePort = true,
StartupResetPolicy = GuaResetPolicy.Strict,
TeardownResetPolicy = GuaResetPolicy.Strict,
CaptureDiagnosticsBeforeTeardown = true,
CleanupAfterLeakReport = true,
});Strict isolation optionの意味
| option | 意味 |
|---|---|
StartupResetPolicy = GuaResetPolicy.Strict | 開始時に選択対象のqueueがdirtyならresetせず失敗します。 |
TeardownResetPolicy = GuaResetPolicy.Strict | 終了時の未消費request/eventをtest leakとして報告します。 |
CaptureDiagnosticsBeforeTeardown = true | 終了検査に失敗した場合、processを破棄する前にUI Treeやlogを保存します。 |
CleanupAfterLeakReport = true | leakの証拠を取得した後だけ非strict cleanupを行い、後続処理を妨げる残留状態を除きます。 |
3. Sleepではなく観測したい状態を待つ
Task.Delay(1000)では、速い環境で時間を無駄にし、遅い環境では不足します。操作後に期待するノード状態を直接待ちます。
await GuaAssertions.GetById(host.Context, "start")
.ClickAsync();
// 表示されるまで最新スナップショットを再取得します。
await GuaAssertions.WaitForVisibleAsync(
host.Context,
"loading",
timeout: TimeSpan.FromSeconds(3),
pollInterval: TimeSpan.FromMilliseconds(20));
// 描画後の状態が3フレーム変化しないことも確認できます。
await GuaAssertions.WaitForStableSnapshotAsync(
host.Context,
stableFrames: 3);状態待機の意味
| 記述 | 意味 |
|---|---|
ClickAsync() | click要求のrequest IDに対応するhost完了結果まで待ちます。画面変化そのものの完了ではありません。 |
WaitForVisibleAsync(..., "loading", ...) | ID loadingがvisibleになるまで新しいsnapshotをpollします。 |
timeout: TimeSpan.FromSeconds(3) | 3秒以内に条件を満たさなければtimeoutとして失敗します。 |
pollInterval: TimeSpan.FromMilliseconds(20) | snapshotを再確認する間隔です。固定sleep後の一度きりの確認とは異なります。 |
WaitForStableSnapshotAsync(..., stableFrames: 3) | Semantic snapshotが3frame連続で変化しないことを待ち、遷移途中を避けます。 |
4. アサーション失敗時の診断成果物を設定する
失敗時に最終UI Tree、操作履歴、ログ、Godotのstdout/stderrをartifacts/guaへ保存します。CIではこのディレクトリをテスト成果物としてアップロードします。
using var diagnostics = host.CreateDiagnosticsSession(
TestContext.CurrentContext.Test.FullName,
outputDirectory: Path.Combine(
TestContext.CurrentContext.WorkDirectory,
"artifacts", "gua"));
using var assertionScope = GuaAssertionScope.Use(
new GuaAssertionOptions
{
DiagnosticsSession = diagnostics,
});
// 失敗すると、主例外を保ったまま診断ファイルが保存されます。
GuaAssertions.GetByRole(
host.Context, "button", "Start Game"
).ToBeVisible();診断設定の意味
| 記述 | 意味 |
|---|---|
host.CreateDiagnosticsSession(...) | このGodot hostに紐づく失敗証拠の収集sessionを作ります。 |
TestContext.CurrentContext.Test.FullName | NUnitの完全test名をartifact directoryの識別に使います。 |
Path.Combine(..., "artifacts", "gua") | OS依存のseparatorを正しく使って出力先を組み立てます。 |
GuaAssertionScope.Use(...) | このscope内で失敗したGua assertionにdiagnostics sessionを関連付けます。 |
DiagnosticsSession = diagnostics | assertion失敗時にUI Tree、履歴、log、process情報を保存する対象を指定します。 |
.ToBeVisible() | 失敗した場合も元のassertion例外を主原因として保ち、診断取得失敗は副次情報にします。 |
GitHub Actionsを使う場合は、テスト後にartifacts/gua/**をactions/upload-artifactへ渡すと、失敗した実行から診断ファイルを取得できます。
GitHub Actionsで実行する
公開Actionのlink1345/gua-tester/godot@v2.2を使うと、Windows runnerへのGodot導入、最新の配布済みGuaアドオンの配置、GODOT_EXECUTABLEの設定、dotnet testの実行をまとめて行えます。
name: Godot Gua UI tests
on:
pull_request:
push:
branches: [main]
jobs:
test:
runs-on: windows-latest
steps:
- uses: actions/checkout@v4
- name: Run Gua UI tests
uses: link1345/gua-tester/godot@v2.2
with:
project-path: game
test-project: tests/GuaTester.Tests.csproj
godot-version: "4.7"
godot-status: stableworkflowの各部
| 記述 | 意味 |
|---|---|
on: pull_request / push | pull requestとmainへのpushをtestの起動条件にします。 |
runs-on: windows-latest | 現在のGua Godot配布物に合うWindows runnerを使います。 |
actions/checkout@v4 | gameとtestのsourceをrunnerへcheckoutします。 |
uses: link1345/gua-tester/godot@v2.2 | Godot導入、Gua add-on配置、dotnet testをまとめた公開Actionを実行します。 |
project-path: game | project.godotがあるdirectoryを指定します。 |
test-project: tests/GuaTester.Tests.csproj | 実行する.NET test projectを指定します。 |
godot-version / godot-status | 取得するGodotを4.7 stableへ固定します。 |