やらなイカ?

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

UI Test Helper パッケージ v1.0.0 リリース

Unity Test Framework でUIを操作するテストを書くときに便利なライブラリ UI Test Helper パッケージのバージョン 1.0.0 をリリースしました。

元々モンキーテスト用のライブラリ(旧称 Monkey Test Helper)として開発していましたが、モンキーに限らずUIテストに使えるAPIが増えてきたので改名しました*1

github.com

インストールは、GitHubリポジトリ指定や、openupm.comから可能です。 openupm-cli であれば次のコマンドでインストールできます。

openupm add com.nowsprinting.test-helper.ui

以下、v1.0.0で実装している機能を簡単に紹介します*2

TOC

GameObjectの検索

GameObjectFinderクラスでScene上のGameObjectを検索できます。GameObject.Findと異なる次のような特徴があります。

  • GameObjectが見つからない場合、指定したタイムアウトまでポーリング*3
  • ユーザーが到達可能(カメラからのレイキャストが通ること)を条件にできる*4
  • 操作可能な状態を条件にできる*5
  • GameObjectのパスで検索するとき、globのワイルドカード書式が使用できる
  • Button を text および textureファイル名で検索できる(ButtonMatcherを使用)
  • ルーセルやスクロール領域内のオブジェクトも検索できる(uGUIのScrollRectであればUguiScrollRectPaginatorを使用)

使用例

using NUnit.Framework;
using TestHelper.UI;

[TestFixture]
public class MyIntegrationTest
{
    [Test]
    public async Task パスで検索()
    {
        var finder = new GameObjectFinder(5d); // 5 seconds timeout
        var result = await finder.FindByPathAsync("/**/Confirm/**/Cancel", reachable: true, interactable: true);
        var cancelButton = result.GameObject;
    }

    [Test]
    public async Task ボタンのテキストで検索()
    {
        var finder = new GameObjectFinder();
        var matcher = new ButtonMatcher(text: "Click Me");
        var result = await finder.FindByMatcherAsync(matcher, reachable: true, interactable: false);
        var button = result.GameObject;
    }

    [Test]
    public async Task ScrollRect内のボタンを検索()
    {
        var finder = new GameObjectFinder();
        var matcher = new NameMatcher("Button_10");

        var scrollView = GameObject.Find("Scroll View");
        var scrollRect = scrollView.GetComponent<ScrollRect>();
        var paginator = new UguiScrollRectPaginator(scrollRect);

        var result = await finder.FindByMatcherAsync(matcher, paginator: paginator);
        var button = result.GameObject;
    }
}

GameObjectの操作

操作に応じたオペレーターが定義・実装されており、uGUIのイベントシステムを使用してクリックなどのイベントをGameObjectに送って操作します。

たとえば UguiClickOperator であれば、次のイベントを再現します。

  1. OnPointerEnter
  2. OnSelect*6
  3. OnPointerDown
  4. OnInitializePotentialDrag
  5. OnPointerUp
  6. OnPointerClick
  7. OnPointerExit

使用例

using NUnit.Framework;
using TestHelper.UI;

[TestFixture]
public class MyIntegrationTest
{
    [Test]
    public async Task InputFieldに文字入力してSubmitButtonをクリック()
    {
        var finder = new GameObjectFinder();

        var message = await finder.FindByNameAsync("Message", interactable: true);
        var inputOperator = new UguiTextInputOperator();
        await inputOperator.OperateAsync(message.GameObject, "Hello, Hurry?");

        var submit = await finder.FindByNameAsync("SubmitButton", interactable: true);
        var clickOperator = new UguiClickOperator();
        await clickOperator.OperateAsync(submit.GameObject);
    }
}

モンキーテスト

画面上の操作可能なUI要素をでたらめに操作します。 テスト実行時間や操作間隔、また使用する操作の種類は MonkeyConfig で指定できます。

なお、モンキーテストで操作させたくないUI要素を無視させる IgnoreAnnotation のほか、操作する座標をずらしたり入力する文字種類の指定を行うアノテーションコンポーネントを利用できます。

使用例

using System;
using System.Threading.Tasks;
using NUnit.Framework;
using TestHelper.UI;

[TestFixture]
public class MyIntegrationTest
{
    [Test]
    public async Task モンキーテスト()
    {
        var config = new MonkeyConfig
        {
            Lifetime = TimeSpan.FromMinutes(2),
            DelayMillis = 200,
            SecondsToErrorForNoInteractiveComponent = 5,
        };

        await Monkey.Run(config);
    }
}

エディター拡張

パスのコピー

ヒエラルキーウィンドウでGameObjectを右クリックして Copy to Clipboard > Hierarchy Path でGameObjectのパスをクリップボードにコピーできます。

インスタンスIDのコピー

ヒエラルキーウィンドウでGameObjectを右クリックして Copy to Clipboard > Instance ID でGameObjectのインスタンスIDをクリップボードにコピーできます。

カスタムUIフレームワークへの対応

uGUIに準拠していないカスタムUIフレームワークを使用している場合、ビルトイン機能をそのまま使用できない場合があります。 UI Test Helperは次の拡張ポイントを用意しており、必要に応じてゲームタイトル側に実装することで紹介したAPI(モンキーテストも含め)をそのまま利用できます。

  • オブジェクトが操作可能かを判断する関数
  • オブジェクトを無視するべきかを判断する関数
  • ユーザーがオブジェクトに到達可能かを判断する関数
  • 検索に使用するMatcherの実装
  • 検索に使用するPaginatorの実装
  • Operatorの実装

関連

www.nowsprinting.com

www.nowsprinting.com

www.nowsprinting.com

www.nowsprinting.com

*1:前々から機会を伺っていたのですが、CEDEC2025登壇に合わせてやりました

*2:詳しくはリポジトリのREADMEを参照してください

*3:固定秒数待機するのはアンチパターンです

*4:スマートフォンゲームでよくある、別のオブジェクトで隠してタップを受け付けない状態では待つ

*5:uGUIであればInteractableで判断します

*6:オブジェクトがSelectableのときのみ。なお、OnDeselectはuGUI側で発行されるため再現していません