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

テストの仕方

Godotプロセスを起動し、ライブなSemantic UI Treeへ接続します。座標ではなくrole・text・idで操作結果を待ちます。

1. 必須NuGetパッケージを入れる

Godotの外部テストにはGua.Core、Gua.Testing、Gua.Testing.Godotの3パッケージが必要です。テストプロジェクトのディレクトリで次を実行して最新の安定版を解決し、projectへ書き込まれたversionをcommitします。

PowerShellpowershell
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.CoreGuaの共通型とnative runtime境界をテストprojectへ追加します。
dotnet add package Gua.TestingSemantic locator、action、待機、assertion、diagnosticsを追加します。
dotnet add package Gua.Testing.GodotGodot processの起動、bridge接続、終了処理を行うtest hostを追加します。
dotnet add package Microsoft.NET.Test.Sdkdotnet testからtest runnerを起動する基盤です。
dotnet add package NUnit[Test]やAssertなどNUnitのtest APIを追加します。
dotnet add package NUnit3TestAdapterMicrosoft.NET.Test.SdkからNUnit testを検出・実行できるようにします。

GodotがPATHにない場合は環境変数を設定します。

PowerShellpowershell
$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. シーンを起動して意味で操作

TitleScreenTests.cscsharp
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 = projectPathproject.godotがあるdirectoryをtest hostへ伝えます。
UseAvailableBridgePort = true空いているloopback portを選び、並列testで8765が衝突しにくくします。
StartupResetPolicy = GuaResetPolicy.Stricttest開始時に前sessionの未処理requestやeventがあれば、黙って消さず失敗させます。
TeardownResetPolicy = GuaResetPolicy.Stricttest終了時にも処理漏れを検査し、別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. フォーム操作

非同期アクション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");

フォーム操作の意味

記述意味
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として渡します。

空きポートでシーンを起動csharp
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 = projectPathsceneを解決するGodot projectのdirectoryです。
UseAvailableBridgePort = trueOSに空きportを選ばせ、GUA_BRIDGE_PORTとしてGodot processへ渡します。
using var hostscope終了時に接続とGodot processを確実に終了します。

2. 開始前と終了前にStrict resetを行う

前のテストが残したノード・要求・イベントを次のテストへ持ち込みません。終了時に未消費の要求やイベントがあれば、黙って削除せずテストを失敗させます。

Strict isolationcsharp
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 = trueleakの証拠を取得した後だけ非strict cleanupを行い、後続処理を妨げる残留状態を除きます。

3. Sleepではなく観測したい状態を待つ

Task.Delay(1000)では、速い環境で時間を無駄にし、遅い環境では不足します。操作後に期待するノード状態を直接待ちます。

状態ベースの待機csharp
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ではこのディレクトリをテスト成果物としてアップロードします。

失敗診断を有効化csharp
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.FullNameNUnitの完全test名をartifact directoryの識別に使います。
Path.Combine(..., "artifacts", "gua")OS依存のseparatorを正しく使って出力先を組み立てます。
GuaAssertionScope.Use(...)このscope内で失敗したGua assertionにdiagnostics sessionを関連付けます。
DiagnosticsSession = diagnosticsassertion失敗時に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の実行をまとめて行えます。

.github/workflows/godot.ymlyaml
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: stable

workflowの各部

記述意味
on: pull_request / pushpull requestとmainへのpushをtestの起動条件にします。
runs-on: windows-latest現在のGua Godot配布物に合うWindows runnerを使います。
actions/checkout@v4gameとtestのsourceをrunnerへcheckoutします。
uses: link1345/gua-tester/godot@v2.2Godot導入、Gua add-on配置、dotnet testをまとめた公開Actionを実行します。
project-path: gameproject.godotがあるdirectoryを指定します。
test-project: tests/GuaTester.Tests.csproj実行する.NET test projectを指定します。
godot-version / godot-status取得するGodotを4.7 stableへ固定します。