unity-testing について
unity-testingは、Unity標準のTest Frameworkを扱うリファレンススキルです。Unity開発者向けに、Edit ModeとPlay Modeのテスト、テストパターン、自動検証のためのコマンドライン実行を説明します。
主な機能
- 01Edit ModeとPlay Modeのテスト
- 02NUnitとUnity固有の属性
- 03コルーチンと非同期テストのパターン
- 04Test Runnerとコマンドライン実行
- 05NUnit XML形式のテスト結果
活用シーン
- 01Edit Modeで純粋なC#ロジックをテストする
- 02Play Modeでゲームプレイと物理を検証する
- 03CI/CDパイプラインでテストを実行する
- 04NUnit XML形式でテスト結果を出力する
インストール
コマンドをコピーしてターミナルで実行します。
npx skills add https:/ / github.com/ nice-wolf-studio/ unity-claude-skills --skill unity-testingインストール後、Claude Code で/unity-testingを入力して読み込まれたか確認します。
同梱ファイルreferences/test-framework.md
詳細
Unity Test Framework
Test Framework の概要
Unity Test Framework(UTF)は Unity に組み込まれたテストソリューションで、Unity 向けにカスタマイズされた NUnit(NUnit 3.5 をベースとしています)を統合しています。C# コードをEdit Mode、Play Mode、およびターゲットプラットフォーム(Standalone、Android、iOS)でテストできます。
UTF は、コルーチン、yield 命令、フレームベースの実行に対応する Unity 固有の属性([UnityTest]、[UnitySetUp]、[UnityTearDown])で NUnit を拡張しています。
主な機能:
- エディター拡張機能や純粋なロジックを対象とした Edit Mode テスト
- ゲームプレイ、物理演算、コルーチンベースのコードを対象とした Play Mode テスト
- プラットフォーム固有の検証向け Standalone プレイヤーテストビルド
- CI/CD パイプライン向けのコマンドライン実行
- NUnit XML 形式の結果出力
セットアップと構成
Test Framework のインストール
UTF は Unity に標準で付属しています。Window > General > Test Runner から Test Runner を開きます。
テストアセンブリの作成
テストコードは、NUnit を参照する独自のアセンブリ定義(.asmdef)に配置する必要があります。
Window > General > Test Runner(Create a new Test Assembly Folder をクリック)または Assets > Create > Testing > Test Assembly Folder から作成します。
これにより、次の参照があらかじめ設定された .asmdef ファイルを含む Tests フォルダーが作成されます。
nunit.framework.dllUnityEngine.TestRunnerUnityEditor.TestRunner(Edit Mode のみ)
Edit Mode アセンブリ定義(.asmdef)
{
"name": "Tests",
"references": [
"UnityEngine.TestRunner",
"UnityEditor.TestRunner"
],
"includePlatforms": ["Editor"],
"defineConstraints": ["UNITY_INCLUDE_TESTS"],
"autoReferenced": false
}
Play Mode アセンブリ定義(.asmdef)
{
"name": "Tests.PlayMode",
"references": [
"UnityEngine.TestRunner",
"UnityEditor.TestRunner",
"MyGameAssembly"
],
"includePlatforms": [],
"optionalUnityReferences": ["TestAssemblies"],
"defineConstraints": ["UNITY_INCLUDE_TESTS"],
"autoReferenced": false
}
重要: ゲームコードをテストするには、テスト対象のコードを含むアセンブリへの参照を references 配列に追加してください。
Edit Mode テストと Play Mode テストの比較
| 項目 | Edit Mode | Play Mode |
|---|---|---|
| 実行コンテキスト | EditorApplication.update コールバックループ | MonoBehaviour 上のコルーチン |
| エディターへのアクセス | エディター全体とゲームコード | ゲームコードのみ(エディター内の場合を除く) |
| コルーチンのサポート | yield return null で 1 回の更新をスキップ | yield 命令を全面的にサポート(WaitForSeconds など) |
| プラットフォームのターゲット | エディターのみ | エディターの Play Mode と Standalone プレイヤー |
| 用途 | エディター拡張機能、純粋なロジック、シリアル化 | 物理演算、ゲームプレイ、MonoBehaviour のライフサイクル |
| アセンブリのプラットフォーム | "includePlatforms": ["Editor"] | "includePlatforms": [] |
Edit Mode を使用する場合:
- エディターツール、カスタムインスペクター、ScriptableObject のロジックをテストする場合
- ランタイムシステムに依存しない純粋な C# ロジックをテストする場合
UnityEditorAPI へのアクセスが必要なテスト
Play Mode を使用する場合:
- MonoBehaviour のライフサイクル(Awake、Start、Update)をテストする場合
WaitForFixedUpdateが必要な物理演算のインタラクションをテストする場合- コルーチンや非同期ワークフローをテストする場合
- 実行時のシーン読み込みやオブジェクトのインスタンス化をテストする場合
ベストプラクティス: yield 命令が必要な場合を除き、[UnityTest] ではなく NUnit の [Test] を使用してください。[Test] はコルーチンのオーバーヘッドが不要なため、より高速に実行されます。
テストの作成
Assets > Create > Testing > C# Test Script から、または Test Runner の Create a new Test Script ボタンを使ってテストスクリプトを作成します。
基本的なテスト構造
using NUnit.Framework;
[TestFixture]
public class HealthSystemTests
{
private HealthSystem _health;
[SetUp]
public void SetUp() => _health = new HealthSystem(maxHealth: 100);
[TearDown]
public void TearDown() => _health = null;
[Test]
public void TakeDamage_ReducesHealth()
{
_health.TakeDamage(30);
Assert.AreEqual(70, _health.CurrentHealth);
}
[Test]
public void TakeDamage_CannotGoBelowZero()
{
_health.TakeDamage(150);
Assert.AreEqual(0, _health.CurrentHealth);
}
[Test]
public void Heal_IncreasesHealth()
{
_health.TakeDamage(50);
_health.Heal(20);
Assert.AreEqual(70, _health.CurrentHealth);
}
}
NUnit 属性リファレンス
| 属性 | 用途 |
|---|---|
[Test] | メソッドを同期テストとしてマークします |
[TestFixture] | クラスをテストを含むものとしてマークします |
[SetUp] | 各テストメソッドの前に実行します |
[TearDown] | 各テストメソッドの後に実行します |
[OneTimeSetUp] | フィクスチャ内のすべてのテストの前に一度実行します |
[OneTimeTearDown] | フィクスチャ内のすべてのテストの後に一度実行します |
[TestCase(args)] | インラインデータを使用するパラメーター化テストです |
[Values(args)] | テストパラメーターに値を指定します |
[Category("name")] | テストを分類してフィルタリングできるようにします |
[Ignore("reason")] | 理由を指定してテストをスキップします |
[Timeout(ms)] | テストのタイムアウトを設定します |
Unity 固有の属性
| 属性 | 用途 |
|---|---|
[UnityTest] | yield 命令をサポートする、コルーチンベースのテストです |
[UnitySetUp] | コルーチンベースのセットアップです(yield をサポートします) |
[UnityTearDown] | コルーチンベースの後処理です(yield をサポートします) |
[RequiresPlayMode] | テストの実行モードを Play Mode(true)または Edit Mode(false)に指定します |
[UnityPlatform] | テストを特定のプラットフォームに制限します |
[TestMustExpectAllLogs] | すべてのログメッセージを想定済みにする必要があります |
[PrebuildSetup] | プレイヤーのビルド前にセットアップを実行します |
[PostBuildCleanup] | プレイヤーのビルド後に後処理を実行します |
[ConditionalIgnore] | 条件に応じてテストを無視します |
[TestRunCallback] | テストの進行状況の更新を受け取ります |
一般的なアサーション
Assert.AreEqual(expected, actual); // Equality
Assert.IsTrue(condition); // Boolean
Assert.IsNotNull(obj); // Null check
Assert.Throws<ArgumentException>(() => F()); // Exception
Assert.AreEqual(1.0f, actual, 0.01f); // Float tolerance
StringAssert.Contains("sub", actualString); // String
CollectionAssert.Contains(list, item); // Collection
アサーション API の完全なリファレンスについては、references/test-framework.md を参照してください。
出典: テストの作成
MonoBehaviour のテスト
using NUnit.Framework;
using UnityEngine;
using UnityEngine.TestTools;
using System.Collections;
[TestFixture]
public class PlayerControllerTests
{
private GameObject _playerObject;
private PlayerController _player;
[UnitySetUp]
public IEnumerator SetUp()
{
_playerObject = new GameObject("Player");
_player = _playerObject.AddComponent<PlayerController>();
yield return null; // Wait one frame for Awake/Start
}
[UnityTearDown]
public IEnumerator TearDown()
{
Object.Destroy(_playerObject);
yield return null; // Wait for destruction
}
[UnityTest]
public IEnumerator Player_MovesForward_WhenInputApplied()
{
var startPos = _player.transform.position;
_player.Move(Vector3.forward);
yield return new WaitForFixedUpdate();
Assert.Greater(_player.transform.position.z, startPos.z);
}
}
コルーチンと非同期処理のテスト
Edit Mode の UnityTest(EditorApplication.update 内で yield します)
[UnityTest]
public IEnumerator EditorUtility_WhenExecuted_ReturnsSuccess()
{
var utility = RunEditorUtilityInTheBackground();
while (utility.isRunning) { yield return null; }
Assert.IsTrue(utility.isSuccess);
}
Play Mode の UnityTest(コルーチンベース)
[UnityTest]
public IEnumerator GameObject_WithRigidBody_WillBeAffectedByPhysics()
{
var go = new GameObject();
go.AddComponent<Rigidbody>();
var originalPosition = go.transform.position.y;
yield return new WaitForFixedUpdate();
Assert.AreNotEqual(originalPosition, go.transform.position.y);
}
利用可能な yield 命令(Play Mode)
| Yield 命令 | 効果 |
|---|---|
yield return null | 1 フレーム待機します |
yield return new WaitForSeconds(t) | t 秒待機します(スケールされた時間) |
yield return new WaitForSecondsRealtime(t) | 実時間で t 秒待機します |
yield return new WaitForFixedUpdate() | 次の物理更新まで待機します |
yield return new WaitForEndOfFrame() | フレームの終了まで待機します |
yield return new WaitUntil(() => cond) | 条件が true になるまで待機します |
yield return new WaitWhile(() => cond) | 条件が true の間、待機します |
LogAssert を使用したテスト
[Test]
public void LogWarning_IsExpected()
{
LogAssert.Expect(LogType.Warning, "Expected warning message");
Debug.LogWarning("Expected warning message");
}
LogAssert.NoUnexpectedReceived() を使用すると、想定外のログメッセージが出力されなかったことをアサートできます。想定済みでない限り、Debug.LogError と Debug.LogException が出力されるとテストは自動的に失敗します。
一般的なテストパターン
Arrange-Act-Assert(AAA)
[Test]
public void Inventory_AddItem_IncreasesCount()
{
// Arrange
var inventory = new Inventory(maxSlots: 10);
var item = new Item("Sword");
// Act
inventory.Add(item);
// Assert
Assert.AreEqual(1, inventory.Count);
}
パラメーター化テスト
[TestCase(100, 30, 70)]
[TestCase(100, 100, 0)]
[TestCase(100, 150, 0)]
[TestCase(50, 10, 40)]
public void TakeDamage_CalculatesCorrectly(int maxHp, int damage, int expectedHp)
{
var health = new HealthSystem(maxHp);
health.TakeDamage(damage);
Assert.AreEqual(expectedHp, health.CurrentHealth);
}
例外のテスト
[Test]
public void Inventory_AddNull_ThrowsException()
{
var inventory = new Inventory(maxSlots: 10);
Assert.Throws<ArgumentNullException>(() =>
{
inventory.Add(null);
});
}
シーンベースのテスト
[UnityTest]
public IEnumerator Scene_Loads_Successfully()
{
SceneManager.LoadScene("GameScene", LoadSceneMode.Single);
yield return new WaitUntil(() => SceneManager.GetActiveScene().name == "GameScene");
Assert.IsTrue(SceneManager.GetActiveScene().isLoaded);
}
フェイクを使用したテスト(依存性注入)
public class FakeAudioService : IAudioService
{
public bool PlaySoundCalled { get; private set; }
public void PlaySound(string name) => PlaySoundCalled = true;
}
[Test]
public void Attack_PlaysSound()
{
var fakeAudio = new FakeAudioService();
var weapon = new Weapon(fakeAudio);
weapon.Attack();
Assert.IsTrue(fakeAudio.PlaySoundCalled);
}
テストの実行
Test Runner ウィンドウから実行
Window > General > Test Runner を開きます。EditMode/PlayMode のチェックボックスを切り替えます。Run All、Run Selected、ダブルクリック、または右クリックのコンテキストメニューからテストを実行します。検索ボックスまたは結果アイコンのボタンを使ってフィルタリングします。
コマンドラインから実行(CI/CD)
# Run Edit Mode tests
Unity -runTests -batchmode -projectPath /path/to/project \
-testPlatform EditMode -testResults /path/to/results.xml
# Run Play Mode tests
Unity -runTests -batchmode -projectPath /path/to/project \
-testPlatform PlayMode -testResults /path/to/results.xml
# Run filtered tests by name and category
Unity -runTests -batchmode -projectPath /path/to/project \
-testFilter "HealthSystemTests" -testCategory "Unit;Integration" \
-testResults /path/to/results.xml
コマンドライン引数リファレンス
| 引数 | 説明 |
|---|---|
-runTests | テストを実行するために必要なフラグ |
-testPlatform | EditMode、PlayMode、または任意の BuildTarget 値 |
-testFilter | セミコロン区切りのテスト名または正規表現パターン。否定するには先頭に ! を付けます |
-testCategory | セミコロン区切りのカテゴリ名。否定するには先頭に ! を付けます |
-testResults | NUnit XML 結果の出力先パス(デフォルト:プロジェクトルート) |
-testNames | FixtureName.TestName 形式の完全なテスト名 |
-assemblyNames | 特定のテストアセンブリ名でフィルターします |
-batchmode | UI なしで実行します(CI/CD では必須) |
-runSynchronously | すべてのテストを1回のエディター更新で実行します(EditMode のみ) |
-playerHeartbeatTimeout | プレイヤーのハートビートを待つ秒数(デフォルト:600) |
IDE との統合
JetBrains Rider では、完全なデバッグ機能を利用して、IDE から直接 Unity テストを実行できます。
出典: テストの実行、コマンドラインリファレンス
アンチパターン
1. [Test] で十分な場合に [UnityTest] を使用する
[UnityTest] はコルーチンのオーバーヘッドを追加します。同期処理には [Test] を使用してください。
// BAD - unnecessary coroutine overhead
[UnityTest]
public IEnumerator Add_ReturnsSum()
{
var result = Calculator.Add(2, 3);
yield return null;
Assert.AreEqual(5, result);
}
// GOOD - simple synchronous test
[Test]
public void Add_ReturnsSum()
{
var result = Calculator.Add(2, 3);
Assert.AreEqual(5, result);
}
2. GameObject をクリーンアップしない
オブジェクトがリークすると、ほかのテストに影響します。必ず [UnityTearDown] または [TearDown] で GameObject を破棄してください。オブジェクトはセットアップで作成し、ティアダウンで破棄します。
3. プライベートメソッドを直接テストする
パブリックインターフェースを通じてテストしてください。どうしても必要な場合は、[assembly: InternalsVisibleTo("Tests")] を使用してください。
4. 時間ベースの待機時間をハードコードする
任意の WaitForSeconds ではなく、WaitUntil または WaitForFixedUpdate を使用してください。
// BAD - brittle, slow
yield return new WaitForSeconds(5f);
Assert.IsTrue(enemy.IsDead);
// GOOD - deterministic
yield return new WaitUntil(() => enemy.IsDead);
Assert.IsTrue(enemy.IsDead);
5. テストが実行順序に依存する
各テストは独立していなければなりません。[SetUp]/[TearDown] を使用して、クリーンな状態を確立してください。
6. ログエラーを無視する
予期しない Debug.LogError の呼び出しは、デフォルトでテスト失敗の原因になります。予期されるエラーには LogAssert.Expect() を使用し、LogAssert.ignoreFailingMessages = true は必要な場合に限って使用してください。
7. テストを製品用アセンブリに配置する
テストアセンブリでは UNITY_INCLUDE_TESTS の定義制約を使用し、製品ビルドから除外されます。テストコードをゲームコードのアセンブリに混在させないでください。
関連スキル
- unity-scripting -- C# スクリプトの基礎、MonoBehaviour のライフサイクル、コルーチン
- unity-editor-tools -- カスタムエディター、インスペクター、エディター拡張