Unity 自作スクリプトを Test Framework でテストする(1)

前回Unity の Release 版パッケージ Test Framework を用いて、簡単なテストの処理を実行し、結果を確認しました。

今回は、自作コンポーネント用テストコードを書いてテストするための方法を紹介していきます。
※ Unity は 2021.3.14f1、Test Framework パッケージは Version 1.1.33、Visual Studio は Community 版 Version 17.2.5、 OS は Windows 10 です。

テストコードのスケルトン

前回、メニュー [Create]→[Testing]→[C# Test Script] を選択し、テンプレートが最初から記述されたテストコードを書く C# のファイルを作成しました。

前回は、数値が等しいかどうかという、using ディレクティブを追加しないでできるテストコード[Test] 属性の関数に実装しました。
今回は Unity 用の自作コンポーネントのスクリプトに対するテストコードなので [UnityTest] 属性の関数に実装することにします。

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

public class TestSCTilemapExtensions
{
    // A Test behaves as an ordinary method
    [Test]
    public void TestSCTilemapExtensionsSimplePasses()
    {
        // Use the Assert class to test conditions
        Assert.That(1, Is.EqualTo(1));  // 追加したテスト項目。 「1 は 1 と同じでしたか?」
    }

    // A UnityTest behaves like a coroutine in Play Mode. In Edit Mode you can use
    // `yield return null;` to skip a frame.
    [UnityTest]
    public IEnumerator TestTestSCTilemapExtensionsWithEnumeratorPasses()
    {
        // Use the Assert class to test conditions.
        // Use yield to skip a frame.
        yield return null;
    }
}

[Test] 属性と [UnityTest] 属性の違い

[Test] 属性は .NET Framework のテストで利用される NUnit で定義されています。
戻り値の型は void です。

[UnityTest] 属性Unity で定義されています。
戻り値の型は IEnumrator です。
PlayMode ではコルーチンとして処理され、EditMode ではフレーム経過を踏まえてテストできます。
戻り値により再び続きから呼び出してもらうこともできるので、数フレーム経過後などの確認も行えます。
参照:Unity Test Runner でのテストの作成と実行 – Unity マニュアル

テストコードの追加実装

さきほどのテスト用スクリプトの [UnityTest] 属性の関数にテストコードを実装します。

どのようにシーンのデータを利用するか

今回のテスト対象Tilemap を利用する SCTilemapShadow.cs コンポーネントです。
これは、他の Tilemap コンポーネントに配置されたマップチップ群をコピーし、それらを上下反転+下に少しずらすことでマップチップの影を表現する自作コンポーネントです。

そのため、あらかじめシーンでマップチップ群を配置した Tilemap コンポーネントテストに必要です。
テスト用のスクリプトの中で Tilemap を new で生成して、ランダムにマップチップを追加したものを使うという方法もあるかもしれませんが、今後様々なテストで、シーン上で設定されたデータを持つコンポーネントを参照したくなると思うので、その方法について調べました。

シーンからプレハブに保存したものをロードする

シーンの Tilemap ゲームオブジェクトをテスト用スクリプトから利用するにはプレハブに保存すれば良いのではないかと思いました。
そこで、プレハブをテスト中にロードすることができないかを調べると Unity の公式フォーラムに類似の質問と回答がありました。

        [UnityTest]
        public IEnumerator PlayerCombatTestsWithEnumeratorPasses()
        {
            GameObject.Instantiate(Resources.Load("Enemies/Enemy") as GameObject);
            yield return null;
        }
https://forum.unity.com/threads/is-there-a-way-to-load-prefab-into-test-runner-script.888661/
注意 Enemy.prefab から prefab を除去しました。

Resources.Load 関数プレハブ読み込んでいるので、テスト用のフォルダResources フォルダを作り、そこに Hierarchy ウィンドウからシーンに配置されている Walls という Tilemap コンポーネントを持つゲームオブジェクトをドラッグ&ドロップし、プレハブ作成しました。

Unity Project ウィンドウのテスト用フォルダに Resources フォルダを作り、シーンからTilemapのプレハブを作成しました.。

テストコードを UnityTest 属性の TestTestSCTilemapExtensionsWithEnumeratorPasses 関数内に実装しました。

using System.Collections;
using System.Collections.Generic;
using NUnit.Framework;
using UnityEngine;
using UnityEngine.TestTools;
using UnityEngine.Tilemaps;
using SakuraCrowd.SCTilemap;

public class TestSCTilemapExtensions
{
    // A Test behaves as an ordinary method
    [Test]
    public void TestSCTilemapExtensionsSimplePasses()
    {
        // Use the Assert class to test conditions
        Assert.That(1, Is.EqualTo(1));  // 追加したテスト項目。 「1 は 1 と同じでしたか?」
    }

    // A UnityTest behaves like a coroutine in Play Mode. In Edit Mode you can use
    // `yield return null;` to skip a frame.
    [UnityTest]
    public IEnumerator TestTestSCTilemapExtensionsWithEnumeratorPasses()
    {
        // Use the Assert class to test conditions.
        // Use yield to skip a frame.
        GameObject prefabWalls = Resources.Load("Walls") as GameObject;
        if (prefabWalls == null)
        {
            Debug.Log("prefabWalls is null");
        }
        GameObject walls = GameObject.Instantiate(prefabWalls);
        yield return null;
        Tilemap wallsTilemap = walls.GetComponent<Tilemap>();
        if (wallsTilemap == null)
        {
            Debug.Log("wallsTilemap is null");
        }
        else
        {
            Debug.Log("wallsTilemap count = " + wallsTilemap.SCGetTileCount());
        }
        Assert.That(wallsTilemap, Is.Not.Null);
        yield return null;
    }
}

テストコード内の wallsTilemap.SCGetTileCount() 関数Tilemap の関数ではなく自作の関数内で引用 this を使って作成した、 Tilemap 用の拡張メソッドです。
拡張メソッドについては、「C#関数で使える引用this、逐語的識別子@、where (テンプレ)について | Compota-Soft-Press」を参照してください。

テスト用スクリプトから自作の名前空間が見つからない理由

自作コンポーネントの機能を使うために、名前空間 SakuraCrowd.SCTilemap を追加したのに次のようにエラーがでます。

Unity Test Framework のテスト用スクリプトに自作コンポーネントのある名前空間を追加しようとするとエラーになりました。


名前空間の SakuraCrowd が見つからないようです。
これは前回テストのために追加した Test Assembly Folder 内のテスト用のアセンブリ定義ファイルが関係しています。

Test Framework パッケージにより Tests Assembly Folder を選択しフォルダとテスト用アセンブリ定義ファイルが作成されました。テスト用のスクリプトも同じフォルダにあります。

エラーが起きたスクリプトのファイルは、上図で作成したテスト用フォルダに、テスト用アセンブリ定義ファイルと一緒に配置されています。

名前空間が見つからないエラーがおきたテスト用のスクリプトのファイルはテスト用アセンブリ定義ファイルと同じフォルダにあります。

これは、テスト用のモジュール(dll) の中にテスト用のスクリプトのファイルも含まれていて、他のモジュールが見えない状態になっています。
そのため、デフォルトのモジュール Assembly-CSharp.dll に含まれている自作コンポーネントの名前空間 SakuraCrowd が見つけられませんでした。

デフォルトのモジュール Assembly-CSharp.dll には便利だけど厄介な特徴があります。
他のモジュールを全て参照できるけれど、他のモジュールからは参照することができないことです。

Unity のアセンブリ定義ファイルの説明図 公式 ScriptCompilation より 参照:https://docs.unity3d.com/ja/2021.1/Manual/ScriptCompilationAssemblyDefinitionFiles.html

今までは、すべての自作スクリプトが Assembly-CSharp.dll というデフォルトのモジュールにあったので、他の全てのモジュールを参照できて便利でした。
しかし、テスト用のモジュールから Assembly-CSharp.dll は参照できないため、その中に入っている名前空間や自作コンポーネントを利用できなくなっています。

unity のデフォルトのモジュール Assembly-CSharp.dll とアセンブリ定義ファイルで作成したモジュールは参照できる範囲が異なります。

自作のスクリプトをモジュールとして独立させる

デフォルトのモジュール Assembly-CSharp.dll の中に自作スクリプトが配置されている限り、外部のテスト用モジュールからその機能を利用できないことがわかりました。

解決策として、自作スクリプト群用のモジュールを作り、そのモジュールをテスト用モジュールに参照させる方法があります。
モジュールを作るには、自作スクリプトが置かれているトップのフォルダにアセンブリ定義ファイルを作成します。
Project ウィンドウそのフォルダの余白で右クリックし、ポップアップメニュー[Create]→[Assembly Definition] を選択し、アセンブリ定義ファイル作成します。
ファイル名は SakuraCrowd にしました。これにより SakuraCrowd.dll モジュールが作成されます。

unity 自作スクリプト群のトップのフォルダで右クリックしメニュー Create→Assembly Definition を選択します。

次に、テスト用アセンブリ定義ファイルを選択し、 Inspector ウィンドウ参照するアセンブリ定義ファイルを追加します。
Assembly Definition Referecnes のリストの + ボタンで項目を 1 つ増やし、先ほど作成した SakuraCrowd アセンブリ定義ファイルを設定します。

Unity テスト用アセンブリ定義ファイルの設定でアセンブリ定義の参照リストの項目を1つ増やし自作モジュールを追加します。

アセンブリ定義ファイルを追加したら、右下の Apply ボタンで設定を適用します。
適用しないと、いつまでも名前空間が見つからないエラーが続くので注意しましょう。

unity アセンブリ定義ファイルに別のアセンブリ定義ファイルの参照を追加した後は右下の Apply ボタンを押して適用させます。

これで先ほどまでのエラーが解消し、テスト用モジュールのスクリプトから自作の名前空間や関数を利用できるようになりました。

Unity 参照できるようにアセンブリ定義ファイルを作成し、テスト用アセンブリ定義ファイルに参照を追加すると自作の名前空間が認識されました.

必要に応じて自作モジュールに参照を追加

今までは参照関係など気にしなかったのですが、テスト用モジュールで利用できるように自作のスクリプトを、デフォルトのモジュール Assembly-CSharp.dll から独立させ、新たなアセンブリ定義ファイルを作成して新たなモジュール (dll) を作りました。

そのため、テスト用モジュールと同様に、今までは何も設定しなくても参照できていた外部モジュールが見つけられない状態になっています。

unity アセンブリ定義ファイルを追加し自作スクリプトのモジュールを独立させたため、他のパッケージの名前空間が見つからなくなりました。

自作のスクリプトの中に、上図のように、using ディレクティブで名前空間が見つからないエラーが表示されていたら、その名前空間と同じアセンブリ定義ファイルの参照を自作モジュールのアセンブリ定義ファイルに追加します。Apply ボタンを押すのを忘れないようにしましょう。

アセンブリ定義ファイルにこのモジュールのスクリプトが依存するモジュールのアセンブリ定義ファイルの参照を追加します。


自作モジュールに属するスクリプトで発生していたエラーが解消し、追加した参照のモジュールの名前空間が利用できるようになりました。

アセンブリ定義ファイルに参照を追加すると、自作モジュールのスクリプトから他のモジュールの名前空間が見つけられました。

Test Runner によるテストの実行

ビルドエラーがなくなったので、メニュー[Window]→[General]→[Test Runner] を選択し、 Test Runner ウィンドウを表示します。

Test Runner ウィンドウの上側のボタンは PlayMode を選択します。
※エディタ拡張のテストの場合は EditMode を選択します。

左上の Run All または、テストしたい関数をダブルクリックすると Test Runner からテストが実行され、ログ出力とテストの結果(合格した場合は緑のチェックマーク)が表示されました。

Unity の Test Runner で自作のテスト(UnityTest 属性)が合格しました.

今回はここまで

今回は、自作コンポーネントテストモジュール内でテストさせるために、アセンブリ定義ファイルモジュールを作ったり、参照を追加する手順を紹介しました。

テストコードのモジュール内では、デフォルトのモジュールの自作関数が呼び出せず、モジュールを分けて参照を追加することで自作関数が呼び出せるようになりました。

まだテストコード自体は実装途中なので、次回はテストコードの実装例を紹介します。

参照サイト Thank You!

コメント

Ads Blocker Image Powered by Code Help Pro

お願い - Ads Blocker Detected

このサイトは広告を掲載して運営しています。

ポップアップを閉じて閲覧できますが、よろしければ

このサイト内の広告を非表示にする拡張機能をオフにしていただけませんか?

━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━

We have detected that you are using extensions to block ads. Please support us by disabling these ads blocker.

タイトルとURLをコピーしました