前回は 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 の公式フォーラムに類似の質問と回答がありました。
https://forum.unity.com/threads/is-there-a-way-to-load-prefab-into-test-runner-script.888661/[UnityTest] public IEnumerator PlayerCombatTestsWithEnumeratorPasses() { GameObject.Instantiate(Resources.Load("Enemies/Enemy") as GameObject); yield return null; }
注意 Enemy.prefab から prefab を除去しました。
Resources.Load 関数でプレハブを読み込んでいるので、テスト用のフォルダに Resources フォルダを作り、そこに Hierarchy ウィンドウからシーンに配置されている Walls という 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 を追加したのに次のようにエラーがでます。
名前空間の SakuraCrowd が見つからないようです。
これは前回テストのために追加した Test Assembly Folder 内のテスト用のアセンブリ定義ファイルが関係しています。
エラーが起きたスクリプトのファイルは、上図で作成したテスト用フォルダに、テスト用アセンブリ定義ファイルと一緒に配置されています。
これは、テスト用のモジュール(dll) の中にテスト用のスクリプトのファイルも含まれていて、他のモジュールが見えない状態になっています。
そのため、デフォルトのモジュール Assembly-CSharp.dll に含まれている自作コンポーネントの名前空間 SakuraCrowd が見つけられませんでした。
デフォルトのモジュール Assembly-CSharp.dll には便利だけど厄介な特徴があります。
他のモジュールを全て参照できるけれど、他のモジュールからは参照することができないことです。
今までは、すべての自作スクリプトが Assembly-CSharp.dll というデフォルトのモジュールにあったので、他の全てのモジュールを参照できて便利でした。
しかし、テスト用のモジュールから Assembly-CSharp.dll は参照できないため、その中に入っている名前空間や自作コンポーネントを利用できなくなっています。
自作のスクリプトをモジュールとして独立させる
デフォルトのモジュール Assembly-CSharp.dll の中に自作スクリプトが配置されている限り、外部のテスト用モジュールからその機能を利用できないことがわかりました。
解決策として、自作スクリプト群用のモジュールを作り、そのモジュールをテスト用モジュールに参照させる方法があります。
モジュールを作るには、自作スクリプトが置かれているトップのフォルダにアセンブリ定義ファイルを作成します。
Project ウィンドウのそのフォルダの余白で右クリックし、ポップアップメニュー[Create]→[Assembly Definition] を選択し、アセンブリ定義ファイルを作成します。
ファイル名は SakuraCrowd にしました。これにより SakuraCrowd.dll モジュールが作成されます。
次に、テスト用アセンブリ定義ファイルを選択し、 Inspector ウィンドウで参照するアセンブリ定義ファイルを追加します。
Assembly Definition Referecnes のリストの + ボタンで項目を 1 つ増やし、先ほど作成した SakuraCrowd アセンブリ定義ファイルを設定します。
アセンブリ定義ファイルを追加したら、右下の Apply ボタンで設定を適用します。
適用しないと、いつまでも名前空間が見つからないエラーが続くので注意しましょう。
これで先ほどまでのエラーが解消し、テスト用モジュールのスクリプトから自作の名前空間や関数を利用できるようになりました。
必要に応じて自作モジュールに参照を追加
今までは参照関係など気にしなかったのですが、テスト用モジュールで利用できるように自作のスクリプトを、デフォルトのモジュール Assembly-CSharp.dll から独立させ、新たなアセンブリ定義ファイルを作成して新たなモジュール (dll) を作りました。
そのため、テスト用モジュールと同様に、今までは何も設定しなくても参照できていた外部モジュールが見つけられない状態になっています。
自作のスクリプトの中に、上図のように、using ディレクティブで名前空間が見つからないエラーが表示されていたら、その名前空間と同じアセンブリ定義ファイルの参照を自作モジュールのアセンブリ定義ファイルに追加します。Apply ボタンを押すのを忘れないようにしましょう。
自作モジュールに属するスクリプトで発生していたエラーが解消し、追加した参照のモジュールの名前空間が利用できるようになりました。
Test Runner によるテストの実行
ビルドエラーがなくなったので、メニュー[Window]→[General]→[Test Runner] を選択し、 Test Runner ウィンドウを表示します。
Test Runner ウィンドウの上側のボタンは PlayMode を選択します。
※エディタ拡張のテストの場合は EditMode を選択します。
左上の Run All または、テストしたい関数をダブルクリックすると Test Runner からテストが実行され、ログ出力とテストの結果(合格した場合は緑のチェックマーク)が表示されました。
今回はここまで
今回は、自作コンポーネントをテストモジュール内でテストさせるために、アセンブリ定義ファイルでモジュールを作ったり、参照を追加する手順を紹介しました。
テストコードのモジュール内では、デフォルトのモジュールの自作関数が呼び出せず、モジュールを分けて参照を追加することで自作関数が呼び出せるようになりました。
まだテストコード自体は実装途中なので、次回はテストコードの実装例を紹介します。
コメント