はじめに
Unity 6.3で、UIToolkitをテストできる「UI Test Framework」が追加された
もともとUnityにはNUnitベースの「Unity Test Framework」というツールがあった
今回のアップデートで、それとは別軸でテスト用のパッケージが追加された形になる
UI Test Frameworkは、その名の通りUIをテストするためのフレームワークで、UIのテスト時に発生する煩わしいことを回避することができる
「デバイスの違いによる解像度, DPI差分」「フォントシステム」「長押しなどの状態変化」「データバインディング時の反映待ち」「キーボードやマウスによるイベント発火」などなど、、、UIのテストがめんどくさい理由を挙げればキリがない
このフレームワークにはさまざまな機能が用意されており、ドキュメントをざっと読んだだけだが、すぐ実用できそうなぐらいには充実している
(リリースしたばっかりでしょうがないけど、ドキュメントがもっとあると嬉しいな〜
)
今回の記事ではUI Test Frameworkの基本的な使い方を説明する
参考動画と参考プロジェクト
Unite2025の公演から 「UI testing made easy with the UI Toolkit Test Framework」
UIToolkitのサンプルプロジェクトであるBagelGame
ドキュメント
はじめの一歩
今回はBagelGameを元に進めるので、事前にクローンしてUnityで開いておくこと
テストの実行
プロジェクトにはすでにテストが実装されているので、先にテストの実行方法を説明する
Window/General/Test Runnerから、テスト用のパネルを開くことができる

パネル右下のRun Allからテストを実行することができる
テスト項目を選択した状態で、Run Selectedを押下することで特定のテストを実行することもできる

基本的な使い方
大まかに以下のような使い方をする
// テスト対象のクラスではUITestFixtureを継承する
public class AutoDetect_RuntimeExample : UITestFixture
{
// NUnitの属性. 各テストの実行前に呼ばれる
[SetUp]
public void SetUp()
{
VisualTreeAsset uxml = Resources.Load<VisualTreeAsset>("UITestFrameworkDocSample");
// テスト対象となるVisualElementを, UITestFixtureから提供されるルートのVisualElementへくっつける
uxml.CloneTree(rootVisualElement);
// 反映のために、1ループ分のUIToolkitの処理を行う
simulate.FrameUpdate();
}
// NUnitの属性. テストケースであることを示す
[Test]
public void EditorAndRuntimeExampleTest()
{
// ボタンが押されたフラグを設定
var isActive = false;
var button = rootVisualElement.Q<Button>();
button.clicked += () => isActive = true;
// ボタンを押す
simulate.Click(button);
// フラグがtrueじゃなかったらAssert
Assert.IsTrue(isActive);
}
}
基本的にコメントの通りだが、重要なのは「simulate.FrameUpdate();でUIToolkitの処理を進めること」と「simulate.Click(button);では、押下イベントを発火させているのではなく、ボタンの位置を実際にマウスでクリックすることをテストしているということ」の2点
特に後者について、コードを辿っていくと分かるのだが
以下のコードのようにボタンの位置でMouseDownとMouseUpをスケジュールしている
これによって、ボタンの前面に余計なUIがあって押せないみたいなことがないようテストが行える
public static IEnumerator SimulateClick(this VisualElement ve, Vector2 position, MouseButton button = MouseButton.LeftMouse, EventModifiers modifiers = EventModifiers.None)
{
yield return ve.DispatchAndWaitForNextFrame(new EventBase[]
{
EventHelpers.MakeMouseDownAt(position, button, modifiers, 1),
EventHelpers.MakeMouseUpAt(position, button, modifiers, 1)
});
}
テストの中身を見てみる
BagelGame内の MainMenuScreenTests.csを見てみる
コメントましましにしたコードを置いておく
using NUnit.Framework;
using UnityEngine;
using UnityEngine.UIElements;
using UnityEngine.UIElements.TestFramework;
namespace Bagel.B03_MainMenu
{
public class MainMenuScreenTests : UITestFixture
{
BagelTestAssetList m_BagelTestAssetList;
// テスト前にVisualTreeAssetをロードしておく
// テストケースが何パターンあろうが、ここは1回だけでいいのでOneTimeSetUpが使われている
[OneTimeSetUp]
public void OneTimeSetUp()
{
m_BagelTestAssetList = Resources.Load<BagelTestAssetList>("BagelTestAssetList");
}
// テストケースの実行後に呼ばれる
// テストケースごとにrootVisualElementがClearされて欲しいので、OneTimeTearDownではなく、TearDownを
[TearDown]
public void TearDown()
{
rootVisualElement.Clear();
}
[Test]
public void MainMenu()
{
// テストを行うためのrootVisualElementへテスト対象をくっつける
m_BagelTestAssetList.mainMenuUxml.CloneTree(rootVisualElement);
// 反映するためのFrameUpdate
simulate.FrameUpdate();
bool playClicked = false;
bool exitClicked = false;
// VisualElementを探してきて、テスト用のフラグセットを仕込む
var mainMenuPaneManager = rootVisualElement.Q<MainMenuPaneManager>();
mainMenuPaneManager.BindUI(new MainMenuPaneManager.Callbacks
{
onPlay = () => playClicked = true,
onExit = () => exitClicked = true
});
// ボタンをクリックをスケジュール
simulate.Click(mainMenuPaneManager.playButton);
// ボタンクリックを実行
simulate.FrameUpdate();
// フラグチェック
Assert.IsTrue(playClicked);
// ボタンをクリックをスケジュール
simulate.Click(mainMenuPaneManager.exitButton);
// ボタンクリックを実行
simulate.FrameUpdate();
// フラグチェック
Assert.IsTrue(exitClicked);
}
}
}
BagelGameでは、テスト内からテスト対象となるVisualTreeAssetを参照するために、まとめておくScriptableObjectを作っている
実際に自分でテストを実装するときも、同じようなものを作ると良さそう
基本的な使い方で説明した通りの書き方をしている
おわりに
UIToolkitは今後どんどん使っていこうと思っているので、かなり嬉しい機能だった
ただ、ダイアログ周りやアニメーションのあるUIはまだ結構辛そうなので今後に期待