Unity Test Runner(PlayMode)で[Scenes In Build]を使用しない方法4選

2023/10/24に公開

PlayModeでシーンのテストを行う場合、
[File] > [Build Settings] > [Add Open Scenes] > [Scenes In Build]
という手順でシーンを追加し、LoadSceneでシーンを呼び出してテストを行う方法があります。

しかし、この方法は以下の点が個人的に受け入れがたいため、別の方法を調査しまとめました。

  • ビルドにテスト用のシーンが含まれてしまう。
  • テストを作るたびにシーンを追加するのが手間。

0. 使用バージョン

1. 前提

本記事では、Buildしてテストする場合(実機でのテスト)については想定していません。
具体的には以下の画像の左側で行うテストを想定しています。

また本記事では、ヒエラルキー上に複数のシーンが配置される状況を扱います。
詳しくは公式記事をご覧ください。

2. PlayModeで[Scenes In Build]を使用しない方法

以下、具体的な方法です。

2.1 自動で作られるシーン(InitTestScene)を使う

PlayModeで[Run All]等を実行すると、[InitTestSceneN...N]というシーン(N...NはDateTime.Now.Ticks)が自動でロードされます。
このシーンを利用します。
後述の[UnityTearDown]処理の問題もあり、あまりお勧め出来ないように思います。

[UnitySetUp]等での準備は不要です。

例えばnew GameObject()を行えば、InitTestSceneのシーンに追加されます。

[UnityTest]
public IEnumerator Test_1()
{
    var scene = SceneManager.GetActiveScene();
    Assert.That(scene.name, Does.StartWith("InitTestScene"));
    Assert.That(scene.rootCount, Is.EqualTo(1));

    var name = Guid.NewGuid().ToString();
    var go = new GameObject(name);
    Assert.That(GameObject.Find(name), Is.EqualTo(go));
    Assert.That(scene.rootCount, Is.EqualTo(2));

    yield break;
}

2.1.1 [Code-based tests runner]を消してはいけない

追加したGameObjectなどは[UnityTearDown]で削除することで、InitTestSceneのシーンを再利用してテストを続けることが可能です。

しかし、このInitTestSceneのシーンには最初から[Code-based tests runner]というGameObjectが存在しています。
このGameObjectはPlayModeでのテストに必要なオブジェクトなので、削除を行ってはいけません。

したがって、InitTestSceneのシーンを再利用する場合は、以下のようにするとよいでしょう。
なお、このような削除処理を行わない場合、テストで追加したGameObjectは次のテストでもシーン上に残ったままとなります。

[UnityTearDown]
public IEnumerator TearDown()
{
    var scene = SceneManager.GetActiveScene();
    foreach (var go in scene.GetRootGameObjects())
    {
	if (go.name == "Code-based tests runner") continue;
	GameObject.Destroy(go);
    }
    yield break;
}

2.1.2 Assets直下に残っていたら消す

余談ですが、このInitTestSceneというシーンは、コンパイルエラーなどでテストが止まるとAssets直下に残ります。
残っていた場合は削除してしまってよいでしょう。

2.2 空のシーンを作って使う

SceneManager.CreateScene()を使用します。

シーンファイルを作るまでもない、ちょっとした物理挙動などを確認したい場合に便利です。

[UnitySetUp]で準備する場合は以下のようにするのがよいでしょう。

[UnitySetUp]
public IEnumerator Setup()
{
    scene = SceneManager.CreateScene(
	"TestScene",
	//Guid.NewGuid().ToString(),
	new CreateSceneParameters(
	    LocalPhysicsMode.None
	)
    );
    SceneManager.SetActiveScene(scene);
    yield break;
}

CreateSceneにてテスト中のヒエラルキーに[TestScene]というシーンが追加されます。
しかしこのヒエラルキーには既にActiveなInitTestSceneシーンがあるので、SetActiveSceneにて追加したシーンをActiveにします。
例えばnew GameObject()Activeなシーンに追加されるので、Activeにしておく方が便利です。

シーンを追加しているので、テスト後はシーンをUnloadする必要があります。
[UnityTearDown]でUnloadする場合は以下のようにするとよいでしょう。

[UnityTearDown]
public IEnumerator TearDown()
{
    SceneManager.UnloadSceneAsync(scene);
    yield return null;
}

UnloadSceneAsyncは非同期なので、yield returnで1フレーム挟んでおくとよいでしょう。
これによりシーンが削除され、次のテストのCreateSceneで名前が重複しなくなります。
名前の重複を避けるためにシーン名をGUIDにしてもよいでしょう。

2.3 既存シーンを開いて使う(Single Scene)

EditorSceneManager.LoadSceneInPlayMode()を使用します。

シーンファイルを作ってテストできるので、複雑なテストに対応できるのが利点です。

[UnitySetUp]で準備する場合は以下のようにするとよいでしょう。

[UnitySetUp]
public IEnumerator Setup()
{
    scene = EditorSceneManager.LoadSceneInPlayMode(
	"Assets/Path/To/MyTestScene.unity",
	new LoadSceneParameters(LoadSceneMode.Single)
    );
    yield break;
}

LoadSceneMode.Singleを指定しているので、ヒエラルキーの既存シーンは全て閉じられるので注意が必要です。
自動で作られるInitTestSceneシーンも閉じられますが、テスト自体には影響ないようです。

この方法でテストを行った場合、テスト後にはテストを終了したシーンがそのまま残ります。

2.4 既存シーンを開いて使う(Multi Scene)

前項と同じくEditorSceneManager.LoadSceneInPlayMode()を使用します。

[UnitySetUp]で準備する場合は以下のようにするとよいでしょう。

[UnitySetUp]
public IEnumerator Setup()
{
    scene = EditorSceneManager.LoadSceneInPlayMode(
	"Assets/Path/To/MyTestScene.unity",
	new LoadSceneParameters(LoadSceneMode.Additive)
    );
    yield return null;
    SceneManager.SetActiveScene(scene);
    yield break;
}

注意点はyield return nullで1フレームおいてからSetActiveSceneする事です。
同フレームでActiveにしようとするとLoadされていないという旨のエラーとなります。

この場合は、自動で作られるInitTestSceneシーンが残っています。
したがって、テスト後にテスト用のシーンをUnloadできます。

[UnityTearDown]でUnloadする場合は以下のようにするとよいでしょう。

[UnityTearDown]
public IEnumerator TearDown()
{
    SceneManager.UnloadSceneAsync(scene);
    yield break;
}

ヒエラルキーに同じシーンを複数Loadすることは可能なので、yield return nullで1フレーム入れる必要はありません。
しかし、1フレーム入れた方がきれいではあるでしょう。

3. 感想

個人的には「2.2 空のシーンを作って使う」と「2.4 既存シーンを開いて使う(Multi Scene)」がいいと感じています。
理由は、両方ともTearDownでUnloadSceneAsyncを使用できるので、同じTextFixture内でもテストを共存させられるためです。

それでは良きTest Runner(PlayMode)ライフを!

Discussion