動作確認環境: Unity 6 (6000.x)
「このコードを変更したら、他の部分が壊れてしまった…」そんな経験、ありませんか? 直したはずのバグが別の場所で再発する——コードが育つほど、手動確認だけでは追いつかなくなります。
Unity Test Framework は、Unity公式のテストフレームワークです。定番のテストライブラリ NUnit をUnityに統合したもので、「このメソッドにこの値を入れたら、この結果になるはず」という確認をコードとして書き残し、ボタン一つで何度でも実行できます。書いたテストは、コードを変更するたびに「壊れていないよ」と教えてくれる見張り番になります。
この記事でわかること
- いつもの「Debug.Logで動作確認」と自動テストの決定的な違い
- Edit ModeテストとPlay Modeテストの使い分け
- AAAパターンでの書き方と、テストが赤くなったときの読み方
[UnityTest]によるフレームをまたぐPlay Modeテスト- Assembly Definitionの正体と設定方法
- ゲーム開発で「何をテストすべきか」の現実的な線引き
いつものDebug.Log確認と何が違うのか
普段の開発では、Debug.Logで値を出して、再生して、目で見て「OK、動いてる」——で済ませている人がほとんどだと思います(デバッグ手法入門 のやり方です)。それで開発は進むのに、なぜわざわざテストを書くのでしょうか。
違いは1つだけです。Debug.Logの確認は「その場で消える」、テストは「確認が保存される」。

- Debug.Log確認: 「今この瞬間、自分の目で見て正しい」 ことを1回だけ確認する。確認が終わればログは消し、確認したという事実も消える
- テスト: 「この入力ならこの結果になるはず」という確認をコードとして保存する。以後、ボタン一つで何百回でも機械が再確認してくれる
これが効くのは後日です。例えばRPGのダメージ計算に会心仕様を追加したとき、うっかり「防御力が高い敵に0ダメージ」という退行バグを入れてしまったとします。Debug.Log方式では、3週間後にプレイヤーの報告で気づくかもしれません。ダメージ計算のテストを5本保存してあれば、テストを実行したその場で「防御力100のケースが失敗しています」と教えてくれます。
つまりテストとは、過去の自分がやった動作確認を、未来の自分のために自動化して残しておく仕組みです。
テストのメリット

- バグの早期発見 - コード変更後、すぐにバグを発見できる
- リファクタリングの安全性向上 - 既存機能が壊れていないか確認できる
- コードの品質向上 - テストしやすいコードは良い設計のコード
- ドキュメントとしての役割 - コードの使い方を示す
特に効いてくるのが2つ目です。テストがあると「壊したらすぐ分かる」という安心感が生まれ、コードの整理や改善に思い切って手を出せるようになります。テストのない大規模コードの改修が怖いのは、この安全網がないからです。
ゲームのどこをテストする?
「ゲームのテスト」と聞くと途方もなく感じますが、実際にテストを書く場所はかなり限定的です。 答えの決まっている「計算」 が最優先のテスト対象です。

- RPGのダメージ計算: 「攻撃力50・防御力30・会心なら結果はいくつ?」——式が複雑になるほど、退行バグの温床になります
- アーケードゲームのスコア計算: コンボ倍率・ボーナス条件の組み合わせは、手動で全パターン確認するのがまず不可能です
- アイテムのインベントリ処理: 「満杯のときに拾ったら?」「99個上限を超えたら?」という境界のバグは、テストが最も得意とする獲物です
逆に「ジャンプの気持ちよさ」「敵AIの手強さ」のような手触りはテストできません。この線引きは記事末尾のおまけで詳しく扱います。
Edit ModeテストとPlay Modeテストの違い

| 項目 | Edit Modeテスト | Play Modeテスト |
|---|---|---|
| 実行環境 | Unity Editorのみ | EditorまたはPlayer |
| 実行速度 | 高速 | 低速 |
| 用途 | ロジックのテスト | ゲームプレイのテスト |
| ライフサイクル | EditorApplication.update | Awake, Start, Update |
| アクセス可能 | Editorコード + ゲームコード | Editor実行時はEditorコードも可、Player実行時はゲームコードのみ |
インストール
注意: Test Frameworkは、通常は最初から入っています(Unity 6 ではエディタに同梱されていま す)。Package Managerでの手動インストールは不要です。
Test RunnerウィンドウはWindow > General > Test Runnerで開きます。
そもそもAssembly Definitionとは
テスト導入で最初につまずくのが、この Assembly Definition(.asmdef) という耳慣れない仕組みです。正体を知れば単純です。
Unityは通常、プロジェクト内の全スクリプトをまとめて1冊の本(Assembly-CSharpというアセンブリ=コンパイル済みコードの束)に綴じ込みます。ところがテストコードはゲームのビルドに混ぜたくないため、「テスト用の別の本」として分冊する必要があります。この分冊の単位を定義するのが.asmdefファイルです。

そして分冊すると、別の本からは本体の中身が見えなくなります。テストコードからPlayerControllerやDamageCalculatorを呼ぼうとしてもコンパイルエラーになるのはこのためです。そこで「テストの本は、本体の本を参照します」と宣言する——これがAssembly Definitionの設定の正体です。
tips: Test Runnerのテストアセンブリ作成ボタン(Unity 6 では「Create a new Test Assembly Folder in the active path」)で作ったフォルダには、テスト用.asmdefが自動生成されます。自分で設定するのは「本体への参照の追加」だけです。なお本体側も.asmdef(例:
MyGame.Runtime)で分冊されている必要があります(Assembly-CSharpのままだと参照先として選べません)。
Inspectorでの設定手順
- Tests.asmdefファイルを選択
- Assembly Definition References セクションを開く
+ボタンをクリック- 参照したい本番コードのasmdef(例:
MyGame.Runtime)を選択 - Apply をクリック
asmdefのJSON例
生成された.asmdefの中身は、おおよそ次のようになっています。ここに載せたのは主な項目だけの抜粋で、実際にはNUnitやTest Runnerへの参照も入っています。手で一から書くとそれが抜けて動かないので、生成されたものに参照を足してください。
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"]のままだと、Test Runnerの「Run on Player」でPlayer上のテストが実行できません(通常のBuild and Runでテストが自動で走るわけではありません)。
はじめてのテスト
Test Assemblyの作成
- Test Runnerウィンドウで
Edit Modeタブを選択 - Projectウィンドウで
Assetsを選んでから、テストアセンブリ作成ボタンをクリック(選択中のフォルダの中に作られます)
テスト対象のゲームコードを用意する
まず「テストされる側」を見てみましょう。冒頭から例に挙げている、RPGのダメージ計算クラスです。MonoBehaviourを継承しない普通のC#クラスなので、Edit Modeテストで一瞬で検証できます。
// ゲーム本体側のコード(テストされる側)
public class DamageCalculator
{
// ダメージ = 攻撃力 - 防御力。ただし最低1は保証する
public int Calculate(int attack, int defense)
{
int damage = attack - defense;
return damage < 1 ? 1 : damage;
}
}
テストスクリプトの作成
このクラスをテストします。テストとは要するに、テストコードがゲームのクラスを実際に呼び出して、返ってきた答えを「期待した答え」と照合するコードです。

書き方は AAAパターン ——Arrange(準備)→ Act(実行)→ Assert(検証)の3ステップが定番です。「材料を並べ、調理し、味見する」の流れです。

using NUnit.Framework;
public class DamageCalculatorTest
{
[Test]
public void Calculate_Attack50_Defense30_Returns20()
{
// Arrange(準備): 計算機と入力値を用意する
var calculator = new DamageCalculator();
int attack = 50;
int defense = 30;
// Act(実行): 実際にゲームのメソッドを呼ぶ
int damage = calculator.Calculate(attack, defense);
// Assert(検証): 「50 - 30 = 20 のはず」と答え合わせ
Assert.AreEqual(20, damage);
}
[Test]
public void Calculate_DefenseHigherThanAttack_ReturnsAtLeast1()
{
// 冒頭の退行バグ「防御力が高い敵に0ダメージ」を捕まえるテスト
var calculator = new DamageCalculator();
int damage = calculator.Calculate(attack: 30, defense: 100);
// どんなに防御が高くても、最低1ダメージは出るはず
Assert.AreEqual(1, damage);
}
}
Test Runnerで実行すると、この2つに緑のチェックが付きます。そして将来、誰かが「最低1保証」の行をうっかり消したら——2つ目のテストがその瞬間に赤くなって知らせてくれます。これが「確認の保存」の正体です。
テストが赤くなったときの読み方
緑のチェックと同じくらい大事なのが、 赤くなったテストの読み方 です。たとえば将来、誰かが「最低1保証」の行を消して、次の形になったとします。

public int Calculate(int attack, int defense)
{
int damage = attack - defense;
return damage; // 最低1保証が消えた(元: return damage < 1 ? 1 : damage;)
}
Test Runnerを再実行すると、2つ目のテストに赤い✗が付き、クリックするとこう表示されます。
Calculate_DefenseHigherThanAttack_ReturnsAtLeast1
Expected: 1
But was: -70
読み方はそのままです——「1のはず(Expected)なのに、実際は-70だった(But was)」。どのテストが・何を期待して・実際はどうだったかが数行で分かるので、闇雲にログを仕込むより先に、まずこのエラー文を読むのが近道です。行をもとに戻して再実行すれば、緑に戻ります。
なお、0どころかマイナスになると、攻撃で敵が回復してしまいます。テストはこういう退行を、実行したその場で知らせてくれます。また、同じテストを何度か続けて実行して毎回同じ結果になることも見ておいてください。この「揺れないこと」が見張り番の信頼の土台で、揺れる場合の犯人は後述の不安定なテストで捕まえられます。
テストの書き方
アサーション
Assert(検証)には、答え合わせの形に応じたバリエーションがあります。ゲームでの使いどころとセットで見ると迷いません。
// 等価性: スコア加算の結果は300のはず
Assert.AreEqual(300, scoreManager.TotalScore);
// 真偽値: 満杯のインベントリにはもう追加できないはず
Assert.IsFalse(inventory.Add(potion));
// 真偽値: レベル10でスキルが解放されているはず
Assert.IsTrue(player.HasSkill("FireBall"));
// null: 存在しないアイテムIDの検索はnullを返すはず
Assert.IsNull(itemDatabase.Find("no_such_item"));
// 例外: レベルにマイナス値を渡したら例外を投げるはず
Assert.Throws<System.ArgumentException>(() => player.SetLevel(-1));
// Constraint Model記法(NUnit 3系推奨)
Assert.That(inventory.Count, Is.EqualTo(3)); // クエスト報酬でアイテムが3つ増えたはず
Assert.That(damage, Is.InRange(90, 110)); // 乱数幅(±10%)のあるダメージが90以上110以下のはず
Constraint Model: NUnit 3系では
Assert.Thatを使った記法が中心です。より読みやすく、「乱数で揺れる値が範囲内か」のような複雑な条件も表現できます。
複数の入力パターンを一 気にテストする(TestCase)
ダメージ計算のような「入力と期待値の表」で表せるテストは、[TestCase]で1つのメソッドにまとめられます。ケースを追加したくなったら行を1本足すだけです。
// 攻撃力, 防御力, 期待ダメージ
[TestCase(50, 30, 20)] // 通常: 50 - 30 = 20
[TestCase(30, 100, 1)] // 防御が上回っても最低1(冒頭の退行バグを常時見張る)
[TestCase(10, 0, 10)] // 防御0ならそのまま通る
[TestCase(1, 1, 1)] // ぎりぎりの境界値
public void Calculate_DamageTable(int attack, int defense, int expected)
{
var calculator = new DamageCalculator();
int damage = calculator.Calculate(attack, defense);
Assert.AreEqual(expected, damage);
}
前後処理(SetUp / TearDown)
インベントリのテストを何本も書くとき、毎回「新品のインベントリ」から始めたいはずです。前のテストで追加したアイテムが残っていると、結果が汚染されるからです。この「毎回の準備・後片付け」を自動化するのが前後処理です。
public class InventoryTest
{
private Inventory inventory;
[SetUp]
public void SetUp()
{
// 各テストの前に毎回実行: 新品のインベントリ(容量10)を用意
inventory = new Inventory(capacity: 10);
}
[TearDown]
public void TearDown()
{
// 各テストの後に毎回実行: 後片付けが必要ならここで
}
[Test]
public void Add_NewItem_CountIncreases()
{
inventory.Add(new Item("Potion"));
Assert.AreEqual(1, inventory.Count); // 新品に1つ足したので必ず1
}
[Test]
public void Add_WhenFull_ReturnsFalse()
{
// このテストも「新品」から始まるので、前のテストの影響を受けない
for (int i = 0; i < 10; i++)
{
inventory.Add(new Item("Potion"));
}
Assert.IsFalse(inventory.Add(new Item("Elixir"))); // 満杯なら拒否されるはず
}
}
このほか、[OneTimeSetUp]/[OneTimeTearDown]を使うと「すべてのテストの前後に1回だけ」の処理(重いデータの読み込みなど)が書けます。
補足(テストクラスの属性): テストクラスには
[TestFixture]属性を付ける流儀もありますが、通常は省略可能です(パラメータ付き・ジェネリックのテストクラスでのみ必須)。
実開発のテストをシミュレーションしてみる
ここまでの例は1メソッドの単純な計算でした。では実開発では、どのくらいの複雑さのテストを書くことになるのでしょうか。ポイントは、 「仕様の箇条書きが、そのままテストの一覧になる」 ということです。2つのジャンルでシミュレーションしてみます。
例1:RPGの「装備・バフ込みステータス計算」
装備とバフが絡んだ最終攻撃力の計算です。仕様を日本語で書き出すと、こうなります。
- 最終攻撃力 =(基礎攻撃力 + 装備補正の合計)× バフ倍率
- 同じバフを重ねがけしても、効果は1回分だけ
- 最終値は 1〜999 の範囲に収める
この仕様の1行1行が、そのままテストになります。

public class CharacterStatsTest
{
private CharacterStats stats;
[SetUp]
public void SetUp()
{
// 毎回「素の状態のキャラ」(基礎攻撃力100・装備なし・バフなし)から始める
stats = new CharacterStats(baseAttack: 100);
}
[Test]
public void FinalAttack_NoEquipNoBuff_ReturnsBase()
{
Assert.AreEqual(100, stats.FinalAttack); // 素の状態なら基礎値そのまま
}
[Test]
public void FinalAttack_TwoWeapons_AddsBothBonuses()
{
stats.Equip(new Item("剣", attackBonus: 30));
stats.Equip(new Item("指輪", attackBonus: 20));
Assert.AreEqual(150, stats.FinalAttack); // 100 + 30 + 20
}
[Test]
public void FinalAttack_SameBuffTwice_AppliesOnlyOnce()
{
stats.AddBuff("攻撃アップ", multiplier: 1.5f);
stats.AddBuff("攻撃アップ", multiplier: 1.5f); // 重ねがけ!
Assert.AreEqual(150, stats.FinalAttack); // 100 × 1.5(×2.25になったらバグ)
}
[Test]
public void FinalAttack_ExtremeBuff_IsClampedTo999()
{
stats.Equip(new Item("伝説の剣", attackBonus: 900));
stats.AddBuff("攻撃アップ", multiplier: 10f);
Assert.AreEqual(999, stats.FinalAttack); // 上限クランプが効くはず
}
}
注目してほしいのは3本目です。「同じバフの重ねがけで効果が2乗になる」は、RPG開発で実際に頻発するバグです。そして装備・バフ・上限が絡む計算は、手動確認では組み合わせを網羅しきれません。仕様が決まった時点でこのテスト群を書いておけば、後からバフの仕様を拡張しても安心して触れます。
例2:パズルゲームの「マッチ判定」
マッチ3パズル(同色3つ揃いで消えるパズル)の判定ロジックです。実開発らしいポイントは、Arrange(盤面の準備)が大きくなること。毎回セルを1個ずつ置くのは大変なので、盤面を文字列で組み立てるヘルパーを作るのが定番テクニックです。

public class MatchFinderTest
{
// 文字列から盤面を組み立てるヘルパー(R=赤, B=青, .=空)
// Arrangeが複雑になったら、こうした補助メソッドに切り出すのが実開発の定番
private Board CreateBoard(params string[] rows)
{
return Board.FromText(rows);
}
[Test]
public void Find_ThreeInARow_DetectsMatch()
{
var board = CreateBoard(
"R R R B",
"B B R B",
". B B R");
var matches = new MatchFinder().Find(board);
Assert.AreEqual(1, matches.Count); // 上段の横RRRが1件検出されるはず
}
[Test]
public void Find_OnlyTwoInARow_NoMatch()
{
var board = CreateBoard(
"R R B B",
"B R B R");
var matches = new MatchFinder().Find(board);
Assert.AreEqual(0, matches.Count); // 2つ並びはマッチではない
}
[Test]
public void Find_LShape_DetectsAsSingleMatch()
{
var board = CreateBoard(
"R B B",
"R B .",
"R R R");
var matches = new MatchFinder().Find(board);
Assert.AreEqual(1, matches.Count); // 縦横が交差するL字は「1つのマッチ」の仕様
}
}
「L字は1マッチか2マッチか」のような仕様の曖昧な部分こそ、テストを書く過程であぶり出されます。テストを書く=仕様を1つずつ確定させる作業でもある、というのが実開発での実感です。
実開発のテストに共通すること
- 仕様の箇条書き → 1行ずつテスト名にする。テスト一覧がそのまま「仕様書」として機能する
- Arrangeが肥大化したらヘルパーに切り出す(盤面ビルダー、キャラ生成ファクトリなど)。テストコードも本体と同じくリファクタリングの対象
- バグ報告が来たら、まず「そのバグを再現するテスト」を書いてから直す。直った瞬間にテストが緑になり、再発も永久に見張ってくれる
Play Mode用の前後処理(IEnumerator版)
public class MyPlayModeTest
{
[UnitySetUp]
public IEnumerator SetUp()
{
// 非同期のセットアップ処理
yield return null;
}
[UnityTearDown]
public IEnumerator TearDown()
{
// 非同期のクリーンアップ処理
yield return null;
}
}
Play Modeテスト
ここまでのダメージ計算やインベントリは「純粋な計算」なのでEdit Modeで一瞬でテストできました。しかし「Rigidbodyを付けたオブジェクトはちゃんと落下するか」のような、物理や時間経過が絡む確認は、実際にゲームを再生しないと検証できません。それを自動化するのが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(); // 次の物理更新まで待つ(短い)
// for (int i = 0; i < 60; i++) yield return null; // 特定フレーム数(約1秒@60fps)
テストのカテゴリ分け
[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; // 必ず戻す
}
たまに赤くなる(不安定なテスト)
「実行するたびに結果が違う」テストは、多くの場合 テスト自体のバグ です(初期化の順番のずれなど、ゲーム側の本物のバグが隠れていることもあります)。放置すると「また誤報か」とテスト全体が信用されなくなるので、症状から犯人を特定して直します。
| 症状 | 犯人 | 直し方 |
|---|---|---|
| CIや遅いPCでだけ落ちる | WaitForSecondsなど決まった秒数の待機 | 「条件を満たすまで待つ+タイムアウト」方式へ(本文の落下テスト参照) |
| 10回に1回くらい落ちる | 乱数を固定していない | テスト冒頭でRandom.InitState(12345)のようにシードを固定する。乱数幅ごと検証するならIs.InRange |
| 単体では通るのに全実行だと落ちる | 前のテストの残留(staticな値・シーンに残ったオブジェクト) | SetUpで毎回初期化し、TearDownで確実に片付ける。staticやSingletonは残留しやすいと知っておく |
見張り番として信頼できる最低ラインは、同じテストを10回続けて実行して、10回とも同じ結果になることです(Test Runnerには繰り返し実行のボタンが無いので、コマンドラインの-repeat 10を使います)。
ベ ストプラクティス
- テストは小さく保つ - 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)をインストールすると、テストがどの程度コードを網羅しているか可視化できます。
- Package ManagerでCode Coverageをインストール
Window > Analysis > Code Coverageで確認- テスト実行後にレポートを生成
おまけ:先に知っておくと良いこと
テ ストの書き方が分かったら、次は「何を書くか」です。ゲーム開発ならではの現実的な線引きを知っておきましょう。
- 全部はテストしない——「計算」から始める: 「ジャンプが気持ちいいか」はテストできませんが、「ダメージ計算が正しいか」はテストできます。まずは Unityに依存しない純粋なロジック(ダメージ計算、スコア集計、インベントリの増減、セーブデータの変換)から始めるのが費用対効果の最も高い一歩です。
- テストしやすさは設計で決まる:
MonoBehaviourのUpdateに埋まったロジックは、テストの準備が重くなります。計算部分を普通のC#クラスに切り出すとEdit Modeテストで一瞬で検証できます。これは イベントとデリゲートの記事 で扱った疎結合の考え方と地続きです。 - async/awaitのテスト: Unity 2023.1以降は
async Taskのテストが書けます(本文参照)。async自体の使いどころは コルーチン vs async/await の記事 で整理しています。
判断基準は1行です。 「そのコードは、壊れたことに自分ではすぐ気づけないか」 ——気づきにくい計算・データ処理こそ、テストという見張り番を置く価値があります。
まとめ
Unity Test Frameworkは、Unityプロジェクトの品質を向上させるための強力なツールです。
- Edit Modeテスト - ロジックのテストに最適、高速実行
- Play Modeテスト - ゲームプレイのテストに最適
- AAAパターン - Arrange、Act、Assertで構造化
- 前後処理 - SetUp、TearDownで共通処理を定義
- TestCase属性 - 複数の入力値で同じテストを実行
プロジェクトの規模が大きくなるほど、テストの重要性は高まります。まずは自分のゲームの「答えが決まっている計算」を1つ選んで、テストを2本。今日の開発の締めに試してみませんか?