やらなイカ?

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

Gameplay MCP Server for Unity を公開しました

Unity製ゲームのランタイムに組み込んでAIエージェントにゲームをプレイさせるためのMCPサーバパッケージを公開しました。 Unityエディタを操作するMCPサーバは多数公開されていますが、これは操作対象がゲーム本体で、エディタ内だけでなくプレイヤービルドでも動作するものです*1

基本的なツールの実装を終えた状態で、バージョンは 0.3.0 です*2

github.com

MCPサーバ部分には MCP C# SDK を採用、ゲームタイトルで自由にカスタムツールを追加できます。 uGUI要素の操作は DeNA/Anjin でも使用されている UI Test Helper を使用しており、GameObjectの出現を(固定時間でなく)ポーリングで待ったり、ユーザの操作をブロックするオブジェクトがあるときは操作できないと判断するなど、UIテストに必要な機能を備えています。また、ゲームタイトル固有のカスタムUIフレームワークにも対応できます。

ビルトインツール

ビルトインで提供しているツールは次の5つです。

list_available_actions

画面に表示されている操作可能な*3 GameObject と、可能な操作(クリック、ドラッグなど)の組み合わせを返します。 モデルはこの中から次のアクションを選択できます。

invoke_action

対象 GameObject と操作を指定して、操作を実行します。

inspect_game_object

GameObject を、name, path*4, text*5, texture*6 を指定して検索し、プロパティを取得します。 表示まで時間がかかる場合でも GameObject の出現を一定時間ポーリングして待ちます。 操作の結果、表示されるはずの GameObject を確認するのに使用できます。

take_screenshot

スクリーンショットを撮影して返します*7

list_scenes

ロードされているSceneを返します。簡易的なゲームの状態確認ツールです。

カスタムツールの実装

ゲームタイトル固有のカスタムツールを追加できます。たとえば次のような用途を想定しています。

  • 現在のゲームの状態を返す
  • uGUI以外の操作
  • デバッグコマンドの実行

カスタムツールは、次のように MCP C# SDK の [McpServerToolType] および [McpServerTool] 属性を配置することで認識されます。

// Assets/Scripts/Runtime/MyGameTools.cs (custom tools created by the game title)
// Just add [McpServerToolType] and it's registered automatically!
[McpServerToolType]
public class MyGameTools
{
    [McpServerTool(Name = "get_player_status", ReadOnly = true, Destructive = false)]
    [Description("Returns the player's current status as JSON.")]
    public async Task<string> GetPlayerStatus(CancellationToken ct = default)
    {
        await UniTask.SwitchToMainThread(ct);
        var player = GameObject.FindWithTag("Player");
        return JsonSerializer.Serialize(new { hp = player.GetComponent<Health>().Current });
    }
}

なお、たとえばゲームの状態を返すツールを実装したことによって list_scenes ツールが不要になったときは、次のように無効化できます。

var config = new McpConfig();
config.DisabledTools.Add("mygame.list_scenes");

使用方法

インストール

あらかじめ MCP C# SDK v1.0.0 以降をインストールします。 NuGetパッケージなので NuGetForUnity などを利用してください*8。NuGetForUnityの使いかたについては過去記事を参照してください。

www.nowsprinting.com

続いて Gameplay MCP Server for Unity パッケージをOpenUPMからインストールします*9

  1. Project Settings ウィンドウを Editor > Project Settings で開き、Package Manager タブを選択
  2. Scoped Registries 下の + ボタンをクリックして次のように入力します
    1. Name: package.openupm.com
    2. URL: https://package.openupm.com
    3. Scope(s): com.nowsprinting and com.cysharp
  3. Package Manager ウィンドウを Window > Package Manager で開き、My Registries タブを選択
  4. Gameplay MCP を選択して Install ボタンをクリック

MCPサーバの起動

MCPサーバは次のコードで起動できます。 [RuntimeInitializeOnLoad] 属性などでゲーム起動時に常に起動したり、デバッグメニューから起動するなどします。

var config = new McpConfig
{
    OperatorPool = new OperatorPool()
        .Register<UguiClickOperator>()
        .Register<UguiDragAndDropOperator>()
        .Register<UguiTextInputOperator>()
};
var server = new McpServer(config);
server.StartAsync().Forget();

McpConfigには使用するオペレーターのほか、GameObjectを探す GameObjectFinder、操作可能の判断に使用するストラテジ関数などを指定できます。 これらはUI Test Helperパッケージの機能なので、詳細はUI Test Helperのドキュメントを見てください。

なお、サーバの待ち受けポートは McpConfig のほか、コマンドライン引数でも指定できます。マルチプレイヤーでも安心。

AIエージェントの設定

次のように設定します。Claude Codeの例:

{
  "mcpServers": {
    "gameplay": {
      "type": "http",
      "url": "http://localhost:8010/mcp"
    }
  }
}

Agent Skills

MCPサーバだけでも操作手順を指示する形であれば動くはずですが、ゲームを自律的プレイさせるには、ゲームのルール、目的などをAgent Skillsとして与える必要があります。

また、uGUIの操作ができなかったときなどの問題の切り分けのために、UI Test Helperのトラブルシュート資料もスキル化しておくと役立つはずです。

今後の展望

あといくつかツールを追加*10するほか、UI Test Helperの Paginator 関連の機能追加などを予定しています。

*1:IL2CPPビルドでも動作しますが、仕組み上WebGLは対象外です

*2:中途半端ですが、なんとなく今週アナウンスしようという気持ちになたっため

*3:interactable=true かつ、カメラからレイキャストが通る(ほかのオブジェクトにブロックされていない)もの

*4:ヒエラルキーのパス

*5:ボタンなどの表示テキスト

*6:ボタンなどのテクスチャファイル名

*7:スクリーンショットは補助的な、結果確認用ツールの位置づけなのですが、エージェントは割と安易に使ってきます。無駄なトークン消費を抑えるためには、Agent Skillsで用途を限るように書くなどして抑制してください

*8:UnityNuGetには登録済みで、レジストラへの反映待ち

*9:git urlでもインストールできますが、その場合は依存パッケージを手動でインストールしてください

*10:persistentDataPathアクセスなどを予定

MCP Server Extension for Unity v1.0.0 リリース

Unity Editor 向けの新しい MCPサーバ、MCP Server Extension for Unity をリリースしました。 JetBrains Riderにインストールするプラグインで、RiderにビルトインされているMCP Serverにツールを追加する拡張機能です*1

特徴は次の3点です。

  • UnityプロジェクトへのUPMパッケージインストールなし*2で利用可能
  • ビルトインの MCP Server を設定済みであれば、追加設定なしで利用可能
  • 複製したワークスペース*3からの同時使用も追加設定なしで可能

提供するツールは次の4つ。MCP Serverは21のツールを提供していますが、それに+4されます。

  • run_unity_tests: Unity Test Runnerでテストを実行します
  • run_method_in_unity: 任意のstaticメソッドを実行します*4。このツールで、sceneおよびprefabファイルを編集できます
  • get_unity_compilation_result: アセットをリフレッシュし、コンパイル結果を取得します
  • unity_play_control: 再生モードを操作します

プラグインページはこちら。

plugins.jetbrains.com

リポジトリはこちら。

github.com

利用方法

プラグインのインストール

  1. Settings > Plugins を開く
  2. Marketplace タブで "mcp unity" などで検索
  3. Install をクリック

MCPサーバの設定

ビルトインの MCP Server を設定済みであれば、すぐ使えます。

未設定の場合は

  1. Settings > Tools > MCP Server を開く
  2. Enable MCP Server をクリック
  3. お使いのCoding Agentに応じた Auto-Configure をクリック

詳しくは MCP Server ドキュメントを参照してください。

Agent Skills

必須ではありませんが、テスト実行ツールで指定するテストモードやアセンブリ名の求め方を指示するAgent Skillを設定すると便利です。 リポジトリのREADMEにサンプルを掲載していますので、参考にしてください。

アーキテクチャ

JetBrains IDEs 2025.2以降でビルトインされているMCPサーバには、カスタムプラグインでツールを拡張できる仕組みがあります。 Coding Agentとのインタフェースはこれを利用しています。個別に設定する手間を省けるほか、複数ワークスペースからの同時使用も考慮されており、ツールの実装に集中できます。

Rider向けプラグインは、JetBrains IDEsプラグインとして振る舞うためのKotlinフロントエンド層と、C#バックエンド層の2レイヤ構成です。 本プラグインで提供しているツールは、RiderのUnityプラグインが備えている(開かれている)インタフェースを呼び出し、Unityプラグインを経由してUnity Editor側のRider Editorパッケージと通信し、Unity Editorを操作しています。

この構造のため、Rider Editorパッケージの機能の範囲でしかツールを提供できません。従って今後機能追加の予定はなく、バグフィックス中心のメンテナンスになります。 また近い将来、JetBrains公式で同じ機能が提供されるはずです。そうなれば、このプラグインの公開も終了するつもりです。IDEバージョンの追随がめんどくさいので早く手放したい(本音)。

所感

コードはほぼClaude Codeさんが書きました。メンテしない前提なので内部品質は気にしない、ということで、狭義のvibe coding寄りで。 でもテストは書かせているしテストケースをレビューしているので、vibe codingとは言えない。普段の自分からはだいぶ寄っているという程度です。

Plan modeでテスト設計させたスキル*5やPlanファイルもリポジトリに入れてあるので、雰囲気わかっていただけるかと。

関連記事

www.nowsprinting.com

www.nowsprinting.com

*1:従って、正しくは「MCPサーバ」そのものは提供していません

*2:ビルトインのRider Editorパッケージは使用します

*3:cp, git clone, git worktree, claude --worktree などによる

*4:引数なしのメソッドのみで、戻り値は受け取れません

*5:Unity向けの移植なのでだいぶ適当

Test Helper v1.4.0 : FLIPによるTexture2D比較子

Unity Test Frameworkによるテストを書くときに便利な Test Helper パッケージの v1.4.0 をリリースしました。 本バージョンでは、ビジュアルリグレッションテストなどに便利なカスタム比較子 FlipTexture2dEqualityComparer を追加しました。

github.com

FlipTexture2dEqualityComparer

FlipTexture2dEqualityComparer は、2つの Texture2D を制約モデルで検証するときに使用できる比較子(comparer)です。 次のように使用します。

[Test]
public async Task MyTestMethod()
{
  // テスト実行(省略)

  // スクリーンショットを撮影
  await Awaitable.EndOfFrameAsync();
  var actual = ScreenCapture.CaptureScreenshotAsTexture();

  // 期待する画像をロード
  var expected = AssetDatabase.LoadAssetAtPath<Texture2D>(ExpectedImagePath);

  // 比較子を使って検証
  var comparer = new FlipTexture2dEqualityComparer(meanErrorTolerance: 0.01f);
  Assert.That(actual, Is.EqualTo(expected).Using(comparer));
}

通常、オブジェクト同士を Is.EqualTo で比較すると参照の比較になりますが、Using モディファイアで比較子を渡すことで比較方法を指定できます。 FlipTexture2dEqualityComparer はFLIP(後述)を使用して2つの Texture2D を比較し、平均エラー値がしきい値(上例では 0.01f)より高ければテストを失敗させます。

また失敗時には、エラー値から生成したマップファイルを出力します。差異の大きいところが暖色になるマグママップ(ヒートマップ)です。

FlipBinding.CSharp

FlipTexture2dEqualityComparer を使用するには、Test Helperパッケージのほかに、FlipBinding.CSharp NuGetパッケージのインストールが必要です。

FlipBinding.CSharp は、FLIP(後述)の C#バインディングAPIで、株式会社サイバーエージェント SGEコア技術本部が公開しているOSSです。 Windows, macOS, Linuxで動作します。

www.nuget.org

FlipBinding.CSharp は、UnityNuGet(OpenUPM)もしくは NuGetForUnity からインストールできます。 UnityNuGet および NuGetForUnity の使いかたは次の記事を参照してください。

www.nowsprinting.com

なお、UnityNuGet(OpenUPM)以外 からインストールしたときは、カスタムスクリプティングシンボル ENABLE_FLIP_BINDING の設定も必要です。

FLIP

FLIP は、NVIDIA Research Projects が公開している画像比較アルゴリズムです。 Python および C++ 実装がOSSとして公開されています。

github.com

FLIP は人間の目が認識する差異を評価してくれるアルゴリズムで、従来手法のようにパラメタ調整に悩むことなく、しきい値の決定だけで精度の高いビジュアルリグレッションテストを運用できるのが特徴です。 FLIP によるビジュアルリグレッションテストについて詳しくは、次の清原氏によるブログ記事および CEDEC 2025 の講演資料が参考になります。

blog.sge-coretech.com

cedil.cesa.or.jp


Unity Test Framework によるテストについては同人誌を頒布していますので、こちらも参考にしてください。

ikagoya.booth.pm

ikagoya.booth.pm

UnityではRoslynアナライザーのどのバージョンを使えばいいのか

Unity 2020.2 からUnityエディターでRoslynアナライザー(およびソースジェネレーター)がサポートされましたが、Unityエディタに同梱されているMicrosoft.CodeAnalysis.CSharpのバージョンよりも新しいバージョンでコンパイルされたアナライザーは動作しません。

どのバージョンが動作するのか、過去記事などで紹介したものもありますが、できるだけ最新のバージョンを使いたいと考えると都度確認せざるを得ず、とても面倒です。 そこで、Unity の各バージョンに対してどのバージョンが使用できるかを網羅したリファレンスリポジトリを作りました。

github.com

アナライザーごとのUnityで動作するバージョン情報

たとえばIDisposableAnalyzersページを見ると、最新バージョンは4.0.8ですが、 Unity 2022.2以降ではv4.0.4、 Unity 2022.2未満ではv3.4.15 を使用しなければならないことがわかるようになっています。

Version Microsoft.CodeAnalysis.CSharp Unity 2020.2 Unity 2021.2 Unity 2022.2 Unity 6000.0
4.0.8 4.5.0.0
4.0.7 4.5.0.0
4.0.6 4.5.0.0
4.0.5 4.5.0.0
4.0.4 4.0.0.0
4.0.3 4.0.0.0
4.0.2 4.0.0.0
4.0.1 4.0.0.0
4.0.0 4.0.0.0
3.4.15 3.5.0.0

なお、Microsoft.CodeAnalysis.CSharp以外の依存はチェックしていませんのでご注意ください。 たとえばNUnit.Analyzersは最新のv4.11.2までUnity 2022.2以降で動作するように見えますが、v4系はNUnit 4が前提であり、Unity Test FrameworkではNUnit 3ベースのv3.9.0*1までしか使えません。

アナライザーの追加・更新

アナライザーの検証および表の生成はGitHub Actionsワークフローになっており、リポジトリに無いアナライザも次の手順で確認できます。

  1. リポジトリをフォークします
  2. フォークリポジトリGitHub Actions workflowを実行します
    1. リポジトリのページで Actions > Check Roslyn Analyzer Versions を開く
    2. Run workflow ドロップダウンをクリック
    3. NuGetのパッケージIDを入力 (e.g., Microsoft.Unity.Analyzers)
    4. Run workflow ボタンをクリック
  3. ワークフローが実行され、新しいブランチにMarkdownファイルがpushされます

ぜひ実行結果をプルリクエストで送ってください。

なお、一度アナライザのページを追加すると、定期的に自動更新されるようになっています。

UnityエディタのMicrosoft.CodeAnalysis.CSharpバージョン確認

Unityエディタに同梱されているMicrosoft.CodeAnalysis.CSharpバージョンを確認するツールを tools/check-unity-roslyn-version.ps1 に置いてあります。

実行結果(すべてのUnityバージョンではない)は docs/unity_roslyn_versions.md に残しています。

こちらはまだ手動実行ですが、そのうち自動化したいと思っています。

関連記事

UnityプロジェクトへのRoslynアナライザー導入方法は、同人誌および過去記事で紹介しています。

www.nowsprinting.com

www.nowsprinting.com

www.nowsprinting.com

www.nowsprinting.com

*1:v3.10.0からNUnit 4に依存

Unity Test Framework完全攻略ガイド 第3版

3年ぶりの改版となる『Unity Test Framework完全攻略ガイド 第3版』を、コミックマーケット107の2日目(水曜日)南i-17b「いか小屋」で頒布します。

第3版では、Unity Test Frameworkパッケージの最新APIへの追随だけでなく、本書で扱う範囲を広げています。 第1章は大幅にリライトし、ユニットテスト以外のテスト活動も含めた全体像や自動テストの保守性にも言及しています。 また追加した第14章「継続的インテグレーション」でCI環境の構築方法を解説しているほか、テストコードの保守・運用や実装テクニックについての一般知識も盛り込みました。

想定読者

UnityでC#スクリプトを書けるレベルのソフトウェアエンジニア(プログラマー)を想定しています。UnityやC#言語自体の解説はしていません。 テスト、特にユニットテストに関しては、入門レベルからカバーしています。

また読者の属する開発プロジェクトの規模を、個人から小規模と想定しています*1CEDECなどで発表されるエンドツーエンド(E2E)テストの自動化は難易度も高くコストもかかるものですが、開発者テストは個人や小規模チームでの開発でも効果を発揮します。むしろQA担当者の大量投入という選択肢のないプロジェクトでこそ有効活用すべき技術です。

目次の紹介

各章の内容(第2版からの差分)は次のとおりです。

第1章 テストとは何か

第2版から大幅にリライトしています。 「テスト」と、ゲーム業界で使われる「デバッグ」という言葉の違い、CEDEC2025のセッション『E2Eだけがテスト自動化じゃない! Unity製ゲームの開発者テスト チュートリアル』でお話したシフトレフト、階層化テスト戦略、また自動テストの保守性にも言及しています。

第2章 Unity Test Frameworkの基本

Unity Test Frameworkパッケージを使ってユニットテストを書き、実行する手順を、順序立てて説明しています。 第3版では「2.6 よいテストコードを書くヒント」を追加しています。

第3章 テストモード

Edit ModeテストとPlay Modeテストの違い、それぞれに固有のAPIを紹介しています。 第2版までは個別の章でしたが統合しました*2

第4章 非同期処理のテスト

第3版ではUnity Test Framework v1.3でサポートされたasync/awaitによる非同期テストを追加し、従来からあるUnityTest属性によるテストについても制限事項をアップデートしています。

第5章 アサーション

NUnit3の制約モデル(Assert.That)によるアサーション(テスト実行結果の検証)について、検証対象や目的に応じた最適な書きかたやTips、アンチパターンを紹介しています。

第3版では、制約モデルの記述例および失敗メッセージ例をいくつか本文中に記述するようにしました。 またTipsに分散していた特殊なアサーションに関する記述を本章にまとめました。

第6章 パラメタライズドテスト

ひとつのメソッドを引数を様々に変えてテストしたいときに使用できるパラメタライズドテストの使いかたを紹介しています。

第3版では、Unity Test Framework v1.4で追加されたParametrizedIgnore属性および、Unity Test Frameworkの問題が解消されたTestFixture属性によるパラメタライズドテストについての解説を追加しました。

第7章 テストダブル

テスト対象が何らかのコンポーネントに依存しているとき(内部で生成した疑似乱数やサーバからのレスポンスに応じて結果が変わるなど)、その依存コンポーネントを偽物(スタブ、スパイ、モックなど)に置き換えてテストするテクニックを紹介しています。

第8章 Unity Test Framework Tips

テストの前処理・後処理、実行プラットフォーム制限、カテゴライズ、タイムアウト時間の変更、MonoBehaviourのテスト方法などを紹介しています。

第9章 Scene・アセット・ファイルの使用

Edit Modeテスト・Play Modeテスト(エディター実行)・Play Modeテスト(プレイヤー実行)それぞれで、Sceneファイルをロードしてテストに使用する方法を紹介しています。

第3版では、Scene以外のテスト用アセットをプレイヤー実行で使う方法などを追加しています。

第10章 UPMパッケージのテスト

Unity Package manager(UPM)パッケージのテストを実装・実行する方法紹介しています。

第11章 Unity Test Frameworkの拡張

第3版で追加した章です。 NUnitの属性、制約、比較子(Comparer)をゲームタイトルで拡張して使用する方法を紹介しています。

第12章 テストの実行方法

UnityエディターのTest Runnerウィンドウ、Jet Brains Rider、コマンドラインからのテスト実行方法・オプションを紹介しています。

第13章 コードカバレッジ

Unity Code Coverageパッケージを用いてコードカバレッジ(テストがプロダクトコードのどの部分をカバーしているかの指標)を採取する方法を紹介しています。

第14章 継続的インテグレーション

第3版で追加した章です。 GitHub Actionsを用いたCI環境の構築方法、octocovによるコードカバレッジの可視化などを紹介しています。

第15章 テスト実装のヒント

壊れやすい「実装のテスト」ではなく、変更に強い「仕様のテスト」を書くためのアプローチを紹介しています。 テストケースを導出するためのテスト技法(同値分割法・境界値分析・デシジョンテーブル・組み合わせテスト)、再現テスト、テスト駆動開発(TDD)。

第3版ではランダム性をもつテスト対象をどうテストするかのアプローチを追加しています。

付録A Test Helperパッケージ

第3版で追加した付録です。 筆者の公開しているUnity Test Framework拡張集であるTest Helperパッケージを紹介しています。

付録B Roslynアナライザー

第3版で追加した付録です。 Roslynアナライザーによる静的解析について紹介しています。

付録C JetBrains Rider Tips

IDEにRiderを使う場合の、コグニティブ複雑度の計測(CognitiveComplexityプラグイン)、テスト用Live Templateの紹介。

付録D OpenUPM-CLI

本書で紹介しているUPMパッケージをUnityプロジェクトにインポートするのに便利なopenupm-cliコマンドの紹介。

表紙

いかの足が2本から3本に!

スペックと頒布価格

変更点を特記していない章も、前提バージョンの変更や記述の見直しなどを実施しています。 原稿の行ベースで見ると第2版の4,623行に対して、+5,296行、-2,612行、差し引き(純増)+2,684行、計7,307行。 ページ数は152から220と68ページ増。

しかしなんと、お値段据え置き ¥2,000 で頒布します。たぶん増刷はしません。 当日残ればBOOTHで通販します。

また電子版はこれまで同様、第2版以前を購入いただいていれば追ってアップデートします。

スペースの場所

2日目(水曜日)南i-17b「いか小屋」です。

既刊の在庫も持っていきます。

www.nowsprinting.com

www.nowsprinting.com

関連

www.nowsprinting.com

www.nowsprinting.com

*1:ユニットテストを書くにあたって規模は関係ないのですが、CI環境としてGitHub Actionsを紹介しているあたりが小規模を意識しています

*2:単に目次の都合で大きな意味はありません

Unity 6 で追加された Roslyn Analyzer 関連機能

Unity 6 のマニュアルを見ていたら、Roslyn Analyzer についての機能追加があったので紹介します。

docs.unity3d.com

Microsoft.CodeAnalysis.Csharp 4.3

アナライザおよびコードジェネレーターをビルドするときに使用する Microsoft.CodeAnalysis.Csharp のバージョンが4.3になったことが明記されました。

なお、実際は Unity 2022.3.12f1 から 4.3 に上がっていたことが知られています。 参考:neue cc - 2022年(2024年)のC# Incremental Source Generator開発手法

オープンソースのアナライザについては、こちらの記事も参照してください。[2026/4/9追記]

www.nowsprinting.com

Report analyzer diagnostics

アナライザおよびコードジェネレーターの実行時間を Editor.log に出力する機能です。

Windows では Edit > Preferences、macOS では Unity > Settings で Preferencesウィンドウを開き、Diagnostics タブを選択します。

最初は警告が表示されますが、I understand, show me the settings ボタンを押すと設定項目が表示されます。

Code を展開して EnableDomainReloadTimings をonにすると有効化できます。 この機能が有効な間、Consoleウィンドウに次の警告が出ます。

Diagnostic switches are active and may impact performance or degrade your user experience. Switches can be configured through the Diagnostics section in the Preferences window. EnableDomainReloadTimings: True

この状態でコンパイルが走ると、Editor.log に次のようにアナライザごとに使用した時間と割合が出力されます。 これが対象のアセンブリ(DLL)ごとに出ます。

[1134/1140  0s] Csc Library/Bee/artifacts/200b0aEDbg.dag/TestHelper.UI.Tests.dll (+2 others)

Total analyzer execution time: 1.084 seconds.
NOTE: Elapsed time may be less than analyzer execution time because analyzers can run concurrently.

Time (s)    %   Analyzer
   0.754   69   IDisposableAnalyzers, Version=4.0.4.0, Culture=neutral, PublicKeyToken=3feb74da3c492280
   0.258   23      IDisposableAnalyzers.FieldAndPropertyDeclarationAnalyzer (IDISP002, IDISP006, IDISP008)
   0.252   23      IDisposableAnalyzers.CreationAnalyzer (IDISP004, IDISP014)
   0.181   16      IDisposableAnalyzers.AssignmentAnalyzer (IDISP001, IDISP003, IDISP008)
   0.036    3      IDisposableAnalyzers.LocalDeclarationAnalyzer (IDISP001, IDISP007)
   0.009   <1      IDisposableAnalyzers.DisposeCallAnalyzer (IDISP007, IDISP016, IDISP017)
   0.008   <1      IDisposableAnalyzers.ReturnValueAnalyzer (IDISP005, IDISP011, IDISP012, IDISP013)
   0.006   <1      IDisposableAnalyzers.ArgumentAnalyzer (IDISP001, IDISP003)
   0.001   <1      IDisposableAnalyzers.MethodReturnValuesAnalyzer (IDISP015)
   0.001   <1      IDisposableAnalyzers.SuppressFinalizeAnalyzer (IDISP024)
   0.001   <1      IDisposableAnalyzers.ClassDeclarationAnalyzer (IDISP025, IDISP026)
  <0.001   <1      IDisposableAnalyzers.DisposeMethodAnalyzer (IDISP009, IDISP010, IDISP018, IDISP019, IDISP020, IDISP021, IDISP023)
  <0.001   <1      IDisposableAnalyzers.UsingStatementAnalyzer (IDISP007)
  <0.001   <1      IDisposableAnalyzers.FinalizerAnalyzer (IDISP022, IDISP023)
  <0.001   <1      IDisposableAnalyzers.SemanticModelCacheAnalyzer (SyntaxTreeCacheAnalyzer)

   0.330   30   nunit.analyzers, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null
   0.102    9      NUnit.Analyzers.TestCaseSourceUsage.TestCaseSourceUsesStringAnalyzer (NUnit1002, NUnit1011, NUnit1015, NUnit1016, NUnit1017, NUnit1018, NUnit1019, NUnit1020, NUnit1029, NUnit1030)
   0.051    4      NUnit.Analyzers.DisposeFieldsInTearDown.DisposeFieldsAndPropertiesInTearDownAnalyzer (NUnit1032)
   0.046    4      NUnit.Analyzers.TestMethodAccessibilityLevel.TestMethodAccessibilityLevelAnalyzer (NUnit1026)
   0.031    2      NUnit.Analyzers.ValueSourceUsage.ValueSourceUsageAnalyzer (NUnit1021, NUnit1022, NUnit1023, NUnit1024, NUnit1025)
   0.016    1      NUnit.Analyzers.TestMethodUsage.TestMethodUsageAnalyzer (NUnit1005, NUnit1006, NUnit1007, NUnit1012, NUnit1013, NUnit1014, NUnit1027)
   0.012    1      NUnit.Analyzers.StringConstraintWrongActualType.StringConstraintWrongActualTypeAnalyzer (NUnit2024)
   0.009   <1      NUnit.Analyzers.SameAsOnValueTypes.SameAsOnValueTypesAnalyzer (NUnit2040)
   0.008   <1      NUnit.Analyzers.ComparableTypes.ComparableTypesAnalyzer (NUnit2041, NUnit2042)
   0.007   <1      NUnit.Analyzers.SameAsIncompatibleTypes.SameAsIncompatibleTypesAnalyzer (NUnit2020)
   0.006   <1      NUnit.Analyzers.EqualToIncompatibleTypes.EqualToIncompatibleTypesAnalyzer (NUnit2021)
   0.004   <1      NUnit.Analyzers.WithinUsage.WithinUsageAnalyzer (NUnit2047)
   0.004   <1      NUnit.Analyzers.ParallelizableUsage.ParallelizableUsageAnalyzer (NUnit1008, NUnit1009, NUnit1010)
   0.004   <1      NUnit.Analyzers.NullConstraintUsage.NullConstraintUsageAnalyzer (NUnit2023)
   0.004   <1      NUnit.Analyzers.DelegateRequired.DelegateRequiredAnalyzer (NUnit2044)
   0.004   <1      NUnit.Analyzers.SameActualExpectedValue.SameActualExpectedValueAnalyzer (NUnit2009)
   0.003   <1      NUnit.Analyzers.MissingProperty.MissingPropertyAnalyzer (NUnit2022)
   0.003   <1      NUnit.Analyzers.IgnoreCaseUsage.IgnoreCaseUsageAnalyzer (NUnit2008)
   0.003   <1      NUnit.Analyzers.SomeItemsIncompatibleTypes.SomeItemsIncompatibleTypesAnalyzer (NUnit2026)
   0.003   <1      NUnit.Analyzers.ClassicModelAssertUsage.ClassicModelAssertUsageAnalyzer (NUnit2001, NUnit2002, NUnit2003, NUnit2004, NUnit2005, NUnit2006, NUnit2015, NUnit2016, NUnit2017, NUnit2018, NUnit2019, NUnit2027, NUnit2028, NUnit2029, NUnit2030, NUnit2031, NUnit2032, NUnit2033, NUnit2034, NUnit2035, NUnit2036, NUnit2037, NUnit2038, NUnit2039)
   0.002   <1      NUnit.Analyzers.ConstActualValueUsage.ConstActualValueUsageAnalyzer (NUnit2007)
   0.002   <1      NUnit.Analyzers.TestCaseUsage.TestCaseUsageAnalyzer (NUnit1001, NUnit1003, NUnit1004)
   0.002   <1      NUnit.Analyzers.UpdateStringFormatToInterpolatableString.UpdateStringFormatToInterpolatableStringAnalyzer (NUnit2050)
   0.001   <1      NUnit.Analyzers.ValuesUsage.ValuesUsageAnalyzer (NUnit1031)
   0.001   <1      NUnit.Analyzers.CollectionAssertUsage.CollectionAssertUsageAnalyzer (NUnit2049)
   0.001   <1      NUnit.Analyzers.StringAssertUsage.StringAssertUsageAnalyzer (NUnit2048)
  <0.001   <1      NUnit.Analyzers.DiagnosticSuppressors.NonNullableFieldOrPropertyIsUninitializedSuppressor ()
  <0.001   <1      NUnit.Analyzers.DiagnosticSuppressors.AvoidUninstantiatedInternalClassSuppressor ()
  <0.001   <1      NUnit.Analyzers.DiagnosticSuppressors.DereferencePossiblyNullReferenceSuppressor ()
  <0.001   <1      NUnit.Analyzers.UseAssertMultiple.UseAssertMultipleAnalyzer (NUnit2045)
  <0.001   <1      NUnit.Analyzers.ConstraintUsage.SomeItemsConstraintUsageAnalyzer (NUnit2014)
  <0.001   <1      NUnit.Analyzers.DiagnosticSuppressors.TypesThatOwnDisposableFieldsShouldBeDisposableSuppressor ()
  <0.001   <1      NUnit.Analyzers.NonTestMethodAccessibilityLevel.NonTestMethodAccessibilityLevelAnalyzer (NUnit1028)
  <0.001   <1      NUnit.Analyzers.ConstraintUsage.ComparisonConstraintUsageAnalyzer (NUnit2043)
  <0.001   <1      NUnit.Analyzers.ConstraintUsage.StringConstraintUsageAnalyzer (NUnit2011, NUnit2012, NUnit2013)
  <0.001   <1      NUnit.Analyzers.ConstraintUsage.EqualConstraintUsageAnalyzer (NUnit2010)
  <0.001   <1      NUnit.Analyzers.ContainsConstraintWrongActualType.ContainsConstraintWrongActualTypeAnalyzer (NUnit2025)
  <0.001   <1      NUnit.Analyzers.UseCollectionConstraint.UseCollectionConstraintAnalyzer (NUnit2046)
[           0s] Csc Library/Bee/artifacts/200b0aEDbg.dag/TestHelper.UI.Tests.dll (+2 others) [CacheWrite 00000000000000000000000000000004]

Tips: Editor.log は、Consoleウィンドウ右上の > Open Editor Log で開けます(macOSの場合コンソールappで開きます)。

Additional files

Assets フォルダ下に拡張子 .additionalfile のファイルを置くと、ファイルパスを Additional files としてアナライザに渡すことができます。 たとえば Foo.DemoAnalyzers.additionalfile というファイルを置くと、csproj ファイルに次のように追加されます。

<ItemGroup>
    <AdditionalFiles Include="Assets/Foo.DemoAnalyzers.additionalfile" />
</ItemGroup>

ただし、ファイル名は Filename.[Analyzer Name].additionalfile と決まっているため、たとえば BannedApiAnalyzers のように規定のファイル名しか受け付けないアナライザには使用できません。

参考:How to use Microsoft.CodeAnalysis.BannedApiAnalyzers

これについては BannedApiAnalyzers に Issue #78124 が立っていますが、Unity側でなんとかしてほしいというのはそれはそう*1

なおこの機能、マニュアルに記載されたのは Unity 6.0 ですが、APIリファレンスを見ると Unity 2021.3 からあったようです。

Roslyn global config file

記事タイトルから逸脱しますが、この機能は Unity 6.2 時点でもマニュアルには未記載、APIリファレンスには Unity 2021.3 から記載されていたものです。

拡張子 .globalconfig のファイルを置くと、アナライザの重大度(severity)を設定できます。つまり ruleset ファイル を置き換えるものです。

ファイルフォーマットは、先頭に is_global = true を書く以外は EditorConfig の重大度設定と同じ*2で、たとえば次のように書きます。

is_global = true
dotnet_diagnostic.IDISP001.severity = error

なお、置き場所やファイル名にはルールがあります。 Default.globalconfig(全アセンブリに効く)や Assembly-CSharp.globalconfig(Assembly-CSharpに効く)は Assets 直下に、 アセンブリ個々にはアセンブリ定義ファイル(.asmdef)と同じディレクトリに任意の名前で置きます。

また、csproj ファイルに GlobalAnalyzerConfigFiles として追加されるため、IDE でもUnityエディタと同じ設定を共有できます。 しかし、JetBrains Rider には本稿執筆時点で次のバグがあり、対応が待たれます。[10/7 追記]

参考:

*1:BannedApiAnalyzers 側で拡張子だけ許容すれば使えるようにはなるのですが、それを既存のアナライザ全部に対してやるのかという話

*2:ファイルタイプやパスによるセクション分けはできず、またコードスタイルを書いても無視されます

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と同じ方式ですが、正直やめてほしい