【Unity】Unity Test Framework入門:テストでバグを未然に防ぐ

作成: 2026-02-05

Unity公式のTest Frameworkを使った自動テストの書き方を解説。Edit ModeテストとPlay Modeテストの使い分け、アサーション、前後処理など実践的な内容を紹介します。

概要

「このコードを変更したら、他の部分が壊れてしまった…」そんな経験、ありませんか?

Unity Test Framework は、Unity公式のテストフレームワークです。NUnit をUnityに統合したもので、自動テストを記述・実行できます。Edit ModeテストとPlay Modeテストの2種類をサポートしています。

テストのメリット

  • バグの早期発見 - コード変更後、すぐにバグを発見できる
  • リファクタリングの安全性向上 - 既存機能が壊れていないか確認できる
  • コードの品質向上 - テストしやすいコードは良い設計のコード
  • ドキュメントとしての役割 - コードの使い方を示す

Edit ModeテストとPlay Modeテストの違い

項目Edit ModeテストPlay Modeテスト
実行環境Unity EditorのみEditorまたはPlayer
実行速度高速低速
用途ロジックのテストゲームプレイのテスト
ライフサイクルEditorApplication.updateAwake, Start, Update
アクセス可能Editorコード + ゲームコードEditor実行時はEditorコードも可、Player実行時はゲームコードのみ

インストール

注意: Unity 2019.2以降では、Test Frameworkはデフォルトでインストールされています。Package Managerでの手動インストールは不要です。

Test RunnerウィンドウはWindow > General > Test Runnerで開きます。

Assembly Definitionの設定

テストスクリプトが本番コードを参照する場合、Assembly Definition(.asmdef)の設定が必要です。

Inspectorでの設定手順

  1. Tests.asmdefファイルを選択
  2. Assembly Definition References セクションを開く
  3. +ボタンをクリック
  4. 参照したい本番コードのasmdef(例:MyGame.Runtime)を選択
  5. Apply をクリック

asmdefのJSON例

Edit Modeテスト用:

{
    "name": "Tests.EditMode",
    "references": ["MyGame.Runtime"],
    "includePlatforms": ["Editor"],
    "defineConstraints": ["UNITY_INCLUDE_TESTS"]
}

Play Modeテスト用:

{
    "name": "Tests.PlayMode",
    "references": ["MyGame.Runtime"],
    "includePlatforms": [],
    "defineConstraints": ["UNITY_INCLUDE_TESTS"]
}

重要: Play Modeテストでは"includePlatforms": [](空配列)にする必要があります。["Editor"]のままだとビルドに含まれず、Player上でテストが実行できません。

はじめてのテスト

Test Assemblyの作成

  1. Test RunnerウィンドウでEdit Modeタブを選択
  2. Create EditMode Test Assembly Folderボタンをクリック

テストスクリプトの作成

using NUnit.Framework;

public class MyFirstTest
{
    [Test]
    public void Add_TwoPlusTwo_ReturnsFour()
    {
        // Arrange(準備)
        int a = 2;
        int b = 2;

        // Act(実行)
        int result = a + b;

        // Assert(検証)
        Assert.AreEqual(4, result);
    }
}

テストの書き方

アサーション

// 等価性のテスト
Assert.AreEqual(expected, actual);
Assert.AreNotEqual(expected, actual);

// 真偽値のテスト
Assert.IsTrue(condition);
Assert.IsFalse(condition);

// nullのテスト
Assert.IsNull(obj);
Assert.IsNotNull(obj);

// 例外のテスト
Assert.Throws<ArgumentException>(() => SomeMethod());

// Constraint Model記法(NUnit 3系推奨)
Assert.That(result, Is.EqualTo(4));
Assert.That(list, Has.Count.EqualTo(3));
Assert.That(value, Is.InRange(1, 10));

Constraint Model: NUnit 3系ではAssert.Thatを使った記法が推奨されています。より読みやすく、複雑な条件も表現できます。

テストクラスの属性

[TestFixture]  // 省略可能だが、パラメータ付きテストクラスで必要
public class MyTest
{
    [Test]
    public void SomeTest() { }
}

// ジェネリックテストクラスの例
[TestFixture(typeof(int))]
[TestFixture(typeof(string))]
public class GenericTest<T>
{
    [Test]
    public void TypeTest() { }
}

前後処理

public class MyTest
{
    [OneTimeSetUp]
    public void OneTimeSetUp() { } // すべてのテスト前に1回

    [SetUp]
    public void SetUp() { } // 各テスト前に実行

    [TearDown]
    public void TearDown() { } // 各テスト後に実行

    [OneTimeTearDown]
    public void OneTimeTearDown() { } // すべてのテスト後に1回
}

Play Mode用の前後処理(IEnumerator版)

public class MyPlayModeTest
{
    [UnitySetUp]
    public IEnumerator SetUp()
    {
        // 非同期のセットアップ処理
        yield return null;
    }

    [UnityTearDown]
    public IEnumerator TearDown()
    {
        // 非同期のクリーンアップ処理
        yield return null;
    }
}

Play Modeテスト

UnityTest属性

using NUnit.Framework;
using UnityEngine;
using UnityEngine.TestTools;
using System.Collections;

public class MyPlayModeTest
{
    private GameObject testObject;

    [UnitySetUp]
    public IEnumerator SetUp()
    {
        testObject = new GameObject("TestObject");
        yield return null;
    }

    [UnityTearDown]
    public IEnumerator TearDown()
    {
        // テストが途中で失敗しても確実にクリーンアップ
        if (testObject != null)
        {
            Object.Destroy(testObject);
        }
        yield return null;
    }

    [UnityTest]
    public IEnumerator GameObject_WithRigidbody_FallsDown()
    {
        // Arrange
        testObject.AddComponent<Rigidbody>();
        var initialPosition = testObject.transform.position;

        // Act - 条件が満たされるまで待機(タイムアウト付き)
        float timeout = 3f;
        float elapsed = 0f;
        while (testObject.transform.position.y >= initialPosition.y && elapsed < timeout)
        {
            elapsed += Time.deltaTime;
            yield return null;
        }

        // Assert
        Assert.Less(elapsed, timeout, "Timed out waiting for object to fall");
        Assert.Less(testObject.transform.position.y, initialPosition.y);
    }
}

// 待機パターンの比較
// yield return new WaitForSeconds(1.0f);        // CI/CDで遅延の原因になりがち
// yield return new WaitForFixedUpdate();        // 物理演算1ステップ(高速)
// for (int i = 0; i < 60; i++) yield return null;  // 特定フレーム数(約1秒@60fps)

複数の入力値をテスト

[TestCase(1, 2, 3)]
[TestCase(2, 3, 5)]
[TestCase(3, 4, 7)]
public void Add_Test(int a, int b, int expected)
{
    var calculator = new Calculator();
    int result = calculator.Add(a, b);
    Assert.AreEqual(expected, result);
}

テストのカテゴリ分け

[Category("Combat")]
[Test]
public void Damage_AppliedCorrectly() { }

[Category("Inventory")]
[Test]
public void Item_AddedToInventory() { }

// コマンドラインでカテゴリ指定実行:
// Unity.exe -batchmode -runTests -testCategory Combat -projectPath /path/to/project

async/awaitテスト(Unity 2023.1以降)

// Unity 2023.1以降で使用可能
[Test]
public async Task AsyncOperation_Completes()
{
    var result = await SomeAsyncMethod();
    Assert.IsNotNull(result);
}

よくある問題と解決策

テストが認識されない

  • Assembly Definitionが正しく設定されているか確認
  • テストメソッドに[Test]または[UnityTest]属性が付いているか確認
  • テストクラスがpublicであるか確認

エラーログでテストが失敗する

LogAssert.Expect()を使用してエラーログを期待する:

// 正しい: Expectを先に呼ぶ
LogAssert.Expect(LogType.Error, "Error message");
Debug.LogError("Error message");  // この後に発生するログを期待

// 間違い: ログが先に出力されるとテストが失敗する
// Debug.LogError("Error message");
// LogAssert.Expect(LogType.Error, "Error message");  // 手遅れ

重要: LogAssert.Expect()は必ずログ出力の に呼び出してください。順序を間違えるとテストが失敗します。

意図的にエラーを発生させるテストの場合:

[SetUp]
public void SetUp()
{
    // テスト中のエラーログによるテスト失敗を無視
    LogAssert.ignoreFailingMessages = true;
}

[TearDown]
public void TearDown()
{
    LogAssert.ignoreFailingMessages = false;  // 必ず戻す
}

ベストプラクティス

  • テストは小さく保つ - 1つのテストで1つのことだけをテスト
  • テスト名は分かりやすく - メソッド名_条件_期待結果のパターン
  • AAAパターンに従う - Arrange、Act、Assert
  • テストは独立させる - 他のテストに依存しない
  • TearDownでクリーンアップ - テスト失敗時もリソースを確実に解放
  • テストも定期的にリファクタリングする

CI/CDでのテスト実行

コマンドラインからテストを実行できます。

# Windows
Unity.exe -batchmode -runTests -testPlatform EditMode -projectPath /path/to/project

# macOS
/Applications/Unity/Hub/Editor/{version}/Unity.app/Contents/MacOS/Unity -batchmode -runTests -testPlatform EditMode -projectPath /path/to/project

# 結果をXMLで出力
Unity.exe -batchmode -runTests -testResults ./results.xml -projectPath /path/to/project

CI/CD統合: GitHub Actions、Jenkins、GitLab CIなどで自動テストを実行し、プルリクエストごとにテストを実行することで品質を維持できます。{version}は使用するUnityのバージョン(例:2022.3.10f1)に置き換えてください。

テストカバレッジの確認

Code Coverage パッケージ(com.unity.testtools.codecoverage)をインストールすると、テストがどの程度コードを網羅しているか可視化できます。

  1. Package ManagerでCode Coverageをインストール
  2. Window > Analysis > Code Coverageで確認
  3. テスト実行後にレポートを生成

まとめ

Unity Test Frameworkは、Unityプロジェクトの品質を向上させるための強力なツールです。

  • Edit Modeテスト - ロジックのテストに最適、高速実行
  • Play Modeテスト - ゲームプレイのテストに最適
  • AAAパターン - Arrange、Act、Assertで構造化
  • 前後処理 - SetUp、TearDownで共通処理を定義
  • TestCase属性 - 複数の入力値で同じテストを実行

プロジェクトの規模が大きくなるほど、テストの重要性は高まります。今こそテストを書き始める絶好のタイミングです。