やらなイカ?

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

Unityプロジェクトでテスト設計+テストファースト開発のための Agent Skills

昨年、Unityプロジェクトでも Claude Code に自走させるワークフロー という記事を投稿しました。 その後も .claude/ 下を公開したリポジトリのメンテナンスを継続していましたが、改めて Agent Skills/ Claude Code プラグインとして公開しました。

github.com

実装計画スキル

代表スキルは /plan-feature <SPEC> で、これをプランモードで使用すると実装前にテスト設計を行い、プランファイルにテストケースを含めてくれます。また実装をテストファースト(TDDではなく)で進めるワークフローもプランファイルに含めます。

このスキルおよびワークフローには、次の特徴があります。

  • テスト設計
    • 保守性の高いテスト:AIが書きがちな、冗長なテスト、アサーションのないテスト、不要なテストダブル(モック/ スタブなど)を削減
    • 手動検証は最低限:統合テストレイヤでUI操作を含めたテストを実施、目視検証が必要なテストは画像解析による検証を実施*1。アニメーションや手触り感などの観点のみ人間に評価を移譲
    • プラン時点でテストケースをレビュー可能:検証内容も含めてプランファイルに出力されるので、テストケースをレビューすることでプランが仕様を満たしているかどうか判断できる
  • テストファースト
    • テストコードを先に実装・実行して、テストが失敗することを確認します。テストファーストには次の利点があります
      • テストコードの正当性(少なくとも、ちゃんと失敗できるテストである)が担保される
      • プロダクトコード(ゲーム本体コード)実装の完了基準を定義できる
      • テスト駆動開発(TDD)と比べ、変更ループが少なくトークン消費も抑えられる
  • コーディングガイドライン
    • Unity Test Framework向けテスト実装についてのガイドライン(非同期テストは UnityTest 属性でなく async + Test 属性を使う、など)
    • Unity向けコード実装についての最低限のガイドライン(Unity 2021.1以降ではオブジェクトプーリングを自前実装しないで ObjectPool<T> を使う、など)
    • Scene/ Prefab の編集は、エディタスクリプトをアドホックに書いてUnityエディタで実行
    • ScriptableObject などの複雑でないアセットファイルを直接編集するガイドライン
  • 内部品質(保守性・可読性など)
    • テスト可能なコードであるという点で、最低限の内部品質は確保
    • ワークフローの最後に、決定論的ツールによる静的解析を実行(後述)

インストールと設定

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

Claude Code プラグインとしてプロジェクトスコープにインストールする場合は、次のスラッシュコマンドを実行します。

/plugin marketplace add nowsprinting/unity-coding-skills
/plugin install unity-coding-skills@nowsprinting-unity-coding-skills --scope project

その他、スキル単位でインストールするツールなどが使えるはずです。

MCPサーバの設定

一部のスキルは、JetBrains RiderビルトインのMCPサーバ及び、Unityエディタを操作する拡張を使用します。 設定方法は次の記事を参照してください。 なお、設定ファイルを手書きする場合は、MCPサーバの名称は必ず jetbrains にしてください。

www.nowsprinting.com

Riderを使いたくない場合、一部のスキルだけ書き換えてもらえれば動作はするはずです。 しかし、JetBrains MCPサーバにはファイル検索などCoding Agentを効率化するツールも含まれていますので、Riderの使用をお勧めします。 無料トライアルもあります。

静的解析の設定

強制したいコーディングルールは、コンパイラやRoslynアナライザで warning 以上の診断になるように設定しておくと、ワークフローの最終フェーズで修正されます。

たとえば、(AIがやりがちな)変更で使わなくなった型やメンバを削除せず放置するのを禁止するには、次の設定を .editorconfig ファイルに追加します。

resharper_unused_type_local_highlighting = warning
resharper_unused_type_global_highlighting = warning
resharper_unused_member_global_highlighting = warning
resharper_unused_member_local_highlighting = warning

.editorconfig ファイルについて詳しくは次の記事を参照してください。

www.nowsprinting.com

またRiderを使用できる場合、プラグインによる診断も対象になります。 (これもAIが書きがちな)長く複雑なメソッドを分割させるために、サイクロマティック/ コグニティブ複雑度を診断するプラグインが利用できます。 詳しくは次の記事を参照してください。

www.nowsprinting.com

注意事項

  • ドキュメントに "TBD" と書いてある仕様は無視されます(詳細を詰められたりしない)
  • Scene/ Prefab 編集スクリプトは、コミットも削除もしません(中身を見たいことがあるため)
  • スキルにはコーディング規約を含まないため、たとえば割とカジュアルにリフレクションが使われたりします。まず静的解析で縛ることを考え、決定論的に書けないものはプロジェクト固有のガイドラインを書くなどしてください
  • 構造的な問題や落とし穴などの観点は人間によるコードレビューが必要です
  • コード品質は既存のコードベースに引っ張られます。既存プロジェクトに導入する場合は、まず /refine-tests スキルでテストコードをリファインすることをお勧めします

使用感・メトリクス

主に、Slay the Spireクローンの制作に使用したときのもの。

  • プルリクエストにするくらいの粒度で使用して、プラン承認後1〜2時間(最近遅いので)自走して、実装・テストを完了します
    • UIレイアウトは雑なものですが、ちゃんと操作できるものが出来上がります(他のGameObjectによってブロックされていないことなどもテスト済み)
    • バグは出ますが仕様定義の漏れが支配的で、仕様レビューを厳密にすることで防げるはず(本プラグイン対象外)
  • テストケースレビューだけで、コードレビューは(テストコードもプロダクトコードも)省略することがほとんど*2。テストケースレビューも全ケースではなく、統合テストや受け入れテスト*3を中心にレビューしています
  • 内部品質は十分とは言えませんが、Claude Codeで開発イテレーションを繰り返しても崩壊しない程度の品質はキープできています*4
  • ステートメントカバレッジは 97.5% *5
  • code : test ratio は 1 : 5.0 *6

参考

本プラグインのスキルがベースとしているテスト設計・実装、メトリクス、Roslynアナライザなどについて詳しくは、次の同人誌を参照してください。 テストケースをレビューする際の観点・判断基準や、スキルをカスタマイズする助けになるはずです。

www.nowsprinting.com

*1:スクリーンショットを撮るテストコードに検証観点を残しているので、目視検証を含めたリグレッションテストを指示すれば再実行も可能。将来的に初回パスしたら以降は正解画像との画像比較によるビジュアルリグレッションテストに変換する案もあり

*2:スキル改善のために見ることはあります

*3:まだ精度が低いのですが、要求に対応したテストに「受け入れテスト」マーカーがつきます

*4:仕事で書くライブラリなどのコードは、ここから温かみのある手作業でリファクタリングしています

*5:ゲームの特性と、I/Oや通信がなく例外処理がほぼないためカバレッジは高め

*6:AIはテストを書きすぎる傾向があるので、この code : test ratio を低く抑えることが直近の課題

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