やらなイカ?

たぶん、iOS/Androidアプリの開発・テスト関係。

UI Test Framework パッケージ v1.0 ファーストインプレッション

先日リリースされたUnity 6.3から、UI Toolkit(旧UIElements)を使ったUIのテストをサポートするUI Test Frameworkパッケージ(com.unity.ui.test-framework)が使えるようになりました。

docs.unity3d.com

UI Toolkitは徐々に存在感を増しながらも、テスタビリティがとても低いという致命的な問題がありました。 UI Test Frameworkパッケージはまだ完全とは言えなませんが、ランタイムもUI Toolkitで作るという話が少し現実味を帯びたように思います*1

本記事は、少しさわってみた範囲のメモ書きです。

前提:UI Toolkitの何が問題なのか

UI ToolkitはuGUIと異なり、GameObjectと紐づくのはルート要素であるUIDocumentコンポーネントだけであり、配下のUI要素は別空間です。 EventSystemInputModuleでは干渉できません。

従って、UI要素を操作するテストを書くにはUI ToolkitのAPIに頼らざるを得ません。 そのAPI、UI要素の検索はQuery<T>で可能ですが、クリックなどのイベントを送るSendEventはテストコードから呼んでも動作しません*2。 ほか、ListViewchildCountselectedItemが取得できないといった問題もあります。

環境

  • Unity 6000.3.0b1
  • UI Test Frameworkパッケージ(com.unity.ui.test-framework)v1.0.0

UI Test Frameworkパッケージのインストール

UI Test Frameworkパッケージは、Unity 6.3以降でインストールできます。 Package Managerウィンドウを開き、Unity Registryから検索してインストールします。

UI Test Frameworkパッケージの概要

UI Test Frameworkパッケージには、 UnityEditor.UIElements.TestFrameworkUnityEngine.UIElements.TestFrameworkの2つの名前空間があります。 基本的なAPIは共通のベースクラスに実装されており、エディタ側にはコンテキストメニューやインスペクターウィンドウのサポートがあります。

テストクラスは、EditorUITestFixtureもしくはRuntimeUITestFixtureを継承*3することでPanelSimulatorなどシミュレータ系のAPIを使ったテストを書くことができます。

ボタンをクリックするテストの例

[TestFixture]
public class ButtonTest : RuntimeUITestFixture // 継承
{
    [Test]
    [LoadScene("../../../Scenes/Button.unity")]
    public async Task ボタンをクリック_ラベルの数字がインクリメントすること()
    {
        // Arrange
        var uiDocument = Object.FindAnyObjectByType<UIDocument>();
        var root = uiDocument.rootVisualElement;
        var button = root.Query<Button>("increment_button").First();
        var label = root.Query<Label>("counter_text").First();
        Assume.That(label.text, Is.EqualTo("0")); // ラベルの初期値は "0"

        // Act
        simulate.Click(button);

        // Assert
        Assert.That(label.text, Is.EqualTo("1")); // ボタンクリックでインクリメントされることを検証
    }
}

Arrangeでは、Sceneファイルをロードし、GameObjectに紐づいたUIDocumentを取得、配下のボタンとラベルを取得しています。 ここまでは通常のUI ToolkitのAPIです。

Actで使用しているsimulateRuntimeUITestFixtureのプロパティで、型はPanelSimulatorです。 Clickメソッドを使ってbuttonのクリック操作を行います。 PanelSimulatorには、クリックするスクリーン座標を渡すClickオーバーロードがあるほか、DragAndDropKeyDownなどのメソッドが実装されています。

なお、Clickは同期メソッドになっていますが、テストメソッドは非同期(async TestもしくはUnityTest属性)でないと動作しません。

UITestFrameworkRuntimeOptionsアセット

RuntimeUITestFixtureのテストを実行するには、UITestFrameworkRuntimeOptionsアセットファイルが必要です。 このファイルは、コンテキストメニューから Create > UI Toolkit > UI Tests Runtime Optionsで生成できます。これをResourcesフォルダ下に置きます。

UITestFixtureにrootVisualElementをセットする

正確には、RuntimeUITestFixtureが保持するPanelSimulatorが保持するrootVisualElementです。 TestFixture(テストクラス)がひとつのrootVisualElementからなるUI要素のツリーを保持するようになっています。

前述のClickメソッドなどはrootVisualElement未設定でも動作しますが、FrameUpdateメソッド系を使用するとき例外が出ます。

rootVisualElementは直接設定するのではなく、SetUIContentメソッドにUIDocumentを渡して設定します。 先のコードのArrangeフェーズを書き換えると次のようになります。

var uiDocument = Object.FindAnyObjectByType<UIDocument>();
SetUIContent(uiDocument);

var button = rootVisualElement.Query<Button>("increment_button").First();
var label = rootVisualElement.Query<Label>("counter_text").First();

所感

少し触っただけですが、クリックなどのイベントが動作するだけでも大きく前進したと言えます。 またランタイムでなくエディタに目を向けると、IMGUIはほぼテスト不能でしたのでUI Toolkitに乗り換えも検討しようかという気持ちになりました。

未検証ですが、MenuSimulatorPopupMenuSimulatorContextMenuSimulatorInspectorTestUtilityといったクラスは気になっています。

ただ全体に、ユニットテスト向けという印象を受けました。UIに対するユニットテストはROIが低くなるので避けたほうがよく、UI操作は画面遷移を伴う統合テストで使いたいのですが。

関連

www.nowsprinting.com

www.nowsprinting.com

*1:Unityさんが課題意識を持っててくれたことがわかっただけでも収穫ですね

*2:プレイヤーループでUI Toolkitが動作するフェーズが特殊なのが原因かと思うのですが未調査

*3:New Input SystemパッケージのTest Frameworkと同じ方式ですが、正直やめてほしい