[Unity] C#クラス(スクリプト)のテスト方法

2021年12月27日

クラスを単体でテストする手法です。
きちんと機能するように設計されているかをテストします。
これは組み込みの機能を使うため、自分でテストを行うことの自動化とも言えます

テストしたいクラスのサンプル

次のクラスをテストすることを考えてみましょう
このクラスは、足し算を実現するためのクラスサンプルです。
1つのpublicメソッドのメンバーがあります。
2つの整数の引数から計算し、結果を戻り値として返します。

public class CalcAdd
{
    public int AddInt(int a, int b)
    {
        return a + b;
    }
}

テストの自動化をしない場合

このように感じでテストするのではないでしょうか?

シーンイメージ

Test.csスクリプト

CalcAddクラスのインスタンスを作って、メソッドを呼び出します。
引数として、1と2を渡して戻り値3が返ってくると正常動作と表示させます。

using UnityEngine;

public class CalcAdd
{
    public int AddInt(int a, int b)
    {
        return a + b;
    }
}

public class Test : MonoBehaviour
{
    void Start()
    {
        CalcAdd calcAdd = new CalcAdd();
        int actual = calcAdd.AddInt(1, 2);

        if (actual == 3)
        {
            Debug.Log("正常動作");
        }
    }
}

メリット、デメリット

この方法でもテストが実行でき、メソッドが正確に実装されていることが確認できるのでテストとして機能していることがメリットとして挙げられますが、反面、このままでは実行コードに組み込まれ不要なコードが残ることになります。

テストの自動化をする場合

デメリットを解消するため、このテスト用のコードを実行コードから完全に切り離す方法をとることにします。
これは、プロジェクトからも独立させたコードとする必要がありますが、Unityにはその機能が備わっています。

Unity Test Framework

Unityのパッケージとして提供されます。
これがインポートされていることを確認しましょう(バージョンが古い場合は、アップデートしましょう)
(サンプルは、この時点のバージョンです)

準備

アセンプリ定義ファイルとは

まず、実際に実行されるコードのアセンプリを作るための定義ファイルを作成します。
アセンプリの定義の説明は、リンクを確認してください。
アセンプリは、実行形式にコンパイルされたファイルになります。

Unityの説明

マイクロソフトの説明

アセンプリ定義ファイルの作成

自動テストを実行するためにテストしたいスクリプトのアセンプリ定義ファイルを作成します

実際に動作させるコードのフォルダー内(今回は、Assets/Scriptsフォルダ)をクリックします
右クロック → Create → Assembly Definitionを選択します
ScriptAssamという名前で保存します

Scriptフォルダ内にScriptAssam定義ファイルが作成されますので、選択して、インスペクターを確認します。
次のようにパラメータを追加、変更してApplyします

テストアセンプリ定義ファイルの作成

自動テストを実行するためにアセンプリ定義技ファイルを作成します

Assetsフォルダを選択し、右クロック → Create → Testing → Tests Assemblyを選択します
Testsという名前でフォルダを保存します

Testsフォルダ内にTests定義ファイルが作成されますので、選択して、インスペクターを確認します。
次のようにパラメータを追加、変更してApplyします

テストファイルの作成

Testsフォルダで右クロック → Create → Testing → C# Test Scriptを選択します
TestScriptという名前で保存します

このようにファイルが作成されます

テスト用のコードを作ります

作ったコードを開くと次のようになっています

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

public class TestScript
{
    // A Test behaves as an ordinary method
    [Test]
    public void TestScriptSimplePasses()
    {
        // Use the Assert class to test conditions
    }

    // 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 TestScriptWithEnumeratorPasses()
    {
        // Use the Assert class to test conditions.
        // Use yield to skip a frame.
        yield return null;
    }
}

コメントを日本語にすると・・・

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

public class TestScript
{
    // テストは通常のメソッドと同じように動作する
    [Test]
    public void TestScriptSimplePasses()
    {
        // Assert クラスを使用して条件をテストする
    }

    // UnityTestは、プレイモードではコルーチンのように動作します。編集モードでは
    // `yield return null;` でフレームをスキップします。
    [UnityTest]
    public IEnumerator TestScriptWithEnumeratorPasses()
    {
        // Assert クラスを使用して、条件をテストします。
        // yield を使ってフレームをスキップする。
        yield return null;
    }
}

これは雛形なので、テストフォーマットに沿ったコードに書き直します

using NUnit.Framework;

public class TestScript
{
    [Test]
    public void 足し算ルーチンのテスト()
    {
        CalcAdd calcAdd = new CalcAdd();
        int actual = calcAdd.AddInt(1, 2);

        // 実際の戻り値 actual と 期待する結果 3 とを比較し正しいか検証
        Assert.That(actual, Is.EqualTo(3));
    }
}

テスト用のウィンドウを表示

テストの実行や結果を表示するためのウィンドウを表示します

Windowメニュー → General → Test Runnerを選択します

テストの実行

テスト用のウィンドウは次のようになります
▶︎を開いてみましょう
Run Allボタンをクリックすると・・・

テスト結果

緑のチェックがついていれば、テスト成功です

正常値の確認以外のテスト

テストの種類

旧バージョンでも動作はします

正しくないことをテスト

// 旧
Assert.AreNotEqual(期待値, 実際の値);
// 新
Assert.That(実際の値, Is.Not.EqualTo(期待値));

より大きいことをテスト

// 旧
Assert.Greater(期待値, 実際の値);
// 新
Assert.That(実際の値, Is.GreaterThan(期待値));

以上であることをテスト

// 旧
Assert.GreaterOrEqual(期待値, 実際の値);
// 新
Assert.That(実際の値, Is.GreaterThanOrEqualTo(期待値));

範囲内であることをテスト

// 新
Assert.That(実際の値, Is.InRange(上限値、下限値));

許容範囲内であることをテスト

// 旧
Assert.AreEqual(期待値, 実際の値, 許容誤差);
// 新
var  = new FloatEqualityComparer(誤差);
Assert.That(実際の値, Is.EqualTo(期待値).Using(comparer));

Null、空文字関連のテスト

// 検証する値がNull(*) である場合、テスト成功
// 旧
Assert.IsNull(検証する値)
// 新
Assert.That(検証する値, Is.Null)

// 検証する値がNull(*) ではない場合、テスト成功
// 旧
Assert.IsNotNull(検証する値)
// 新
Assert.That(検証する値, Is.Not.Null)

// 検証する値が空文字 である場合、テスト成功
// 旧
Assert.IsEmpty(検証する値)
// 新
Assert.That(検証する値, Is.Empty)

// 検証する値が空文字 ではない場合、テスト成功
// 旧
Assert.IsNotEmpty(検証する値)
// 新
Assert.That(検証する値, Is.Not.Empty)

// 検証する値がNull(*) または空文字である場合、テスト成功
// 旧
Assert.IsNullOrEmpty(検証する値)
// 新
Assert.That(検証する値, Is.Null.Or.Empty)

// 検証する値がNull(*) 以外または空文字以外の場合、テスト成功
// 旧
Assert.IsNotNullOrEmpty(検証する値)
// 新
Assert.That(検証する値, Is.Not.Null.Or.Empty)

Bool値(真偽値)のテスト

// 検証する値が True である場合、テスト成功
// 旧
Assert.IsTrue(検証する値)
Assert.True(検証する値)
// 新
Assert.That(検証する値, Is.True)

// 検証する値が False である場合、テスト成功
// 旧
Assert.IsFalse(検証する値)
Assert.False(検証する値)
// 新
Assert.That(検証する値, Is.False)

文字列をテストする

// 実際の値に含まれるべき期待値が含まれている場合、テスト成功
// 旧
StringAssert.Contains(含まれるべき文字列、実際の値)
// 新
Assert.That(実際の値, Does.Contain(含まれるべき文字列));

// 実際の値が始まりの文字列で始まっている場合、テスト成功
// 旧
StringAssert.StartsWidth(始まりの文字列、実際の値)
// 新
Assert.That(実際の値, Does.StartWith(始まりの文字列));

// 実際の値が終わりの文字列で終わっている場合、テスト成功
// 旧
StringAssert.EndWith(終わりの文字列, 実際の値)
// 新
Assert.That(実際の値, Does.EndWith(終わりの文字列));

// 実際の値と期待値で大文字小文字を区別なく比較し、等しい場合はテスト成功
// 旧
StringAssert.AreEqualIgnoringCase(期待値、実際の値)
// 新
Assert.That(実際の値, Is.EqualTo(期待値).IgnoreCase);

// 実際の値と期待値で大文字小文字を区別なく比較し、等しくない場合はテスト成功
// 旧
StringAssert.AreNotEqualIgnoringCase(期待値、実際の値)
// 新
Assert.That(実際の値, Is.Not.EqualTo(期待値).IgnoreCase);

ファイル内容が一致するかテストする

// 旧
//  ファイル内容が一致している場合テスト成功
FileAsert.AreEqual(ファイル1,ファイル2)

//  ファイル内容が不一致の場合テスト成功
FileAsert.AreNotEqual(ファイル1,ファイル2)

// 「期待する内容を持つファイルのパス文字列」が指すファイルの内容と、「テスト対象のファイルパス文字列」が指すファイルの内容が一致する場合、テスト成功
FileAssert.AreEqual(期待する内容を持つファイルのパス文字列、テスト対象のファイルパス文字列)	

//「期待する内容を持つファイルのFileInfoクラスのインスタンス」のファイルの内容と、「テスト対象のFileInfoクラスのインスタンス」が指すファイルの内容が一致する場合、テスト成功
FileAssert.AreEqual(期待する内容を持つファイルのFileInfoクラスのインスタンス、テスト対象のFileInfoクラスのインスタンス)

//「期待する内容を持つファイルのパス文字列」が指すファイルの内容と、「テスト対象のファイルパス文字列」が指すファイルの内容が不一致の場合、テスト成功
FileAssert.AreNotEqual(期待する内容を持つファイルのパス文字列、テスト対象のファイルパス文字列)

//「期待する内容を持つファイルのFileInfoクラスのインスタンス」のファイルの内容と、「テスト対象のFileInfoクラスのインスタンス」が指すファイルの内容が不一致の場合、テスト成功
FileAssert.AreNotEqual(期待する内容を持つファイルのFileInfoクラスのインスタンス、テスト対象のFileInfoクラスのインスタンス)

コレクション(配列やList)の内容が一致するかをテストする

// 旧
// 期待するコレクションと、テストするコレクションの内容および順番が一致している場合、テスト成功CollectionAssert.AreEqual(期待するコレクション, テストするコレクション)

// 期待するコレクションと、テストするコレクションの内容および順番が一致していない場合、テスト成功
CollectionAssert.AreNotEqual(期待するコレクション, テストするコレクション)

コレクションに指定された内容が含まれるかをテストする

// 旧
// 実際の値に含まれているべき値が存在する場合、テスト成功
CollectionAssert.Contains(実際の値, 含まれているべき値)

コレクションが空かどうかをテストする

// 旧
// テストするコレクションの内容が空(Empty)の場合にテスト成功
CollectionAssert.IsEmpty(テストするコレクション)

// テストするコレクションの内容が空(Empty)ではない場合にテスト成功
CollectionAssert.IsNotEmpty(テストするコレクション)

ドキュメント

属性(アトリビュート)による、複数のテストパターンを実行

テスト用の引数として組み合わせを与える

using NUnit.Framework;

public class TestScript
{
    // それぞれの引数値は、value1, value2, answerに代入され、全てのパターンで実行されます
    [TestCase(1, 1, 2)]
    [TestCase(1, 2, 3)]
    [TestCase(1, 3, 0)]
    // このように引数として渡します
    public void 足し算ルーチンのテスト(int value1, int value2, int answer)
    {
        CalcAdd calcAdd = new CalcAdd();
        // value1とvalue2を引数としてメソッドを実行
        int actual = calcAdd.AddInt(value1, value2);

        // 実際の戻り値 actual と 期待する結果 answer とを比較し正しいか検証
        Assert.That(actual, Is.EqualTo(answer));
    }
}

指定された値を組み合わせとしてパターン実行

次のテストは、xの値ごとに15回、3回実行され、それぞれが-1.0から+1.0までの5つのランダムダブルスと組み合わされます

using NUnit.Framework;

public class TestScript
{
    [Test]
    // 3 x 3 x 3パターンの組み合わせでテストが実行されます
    public void 足し算ルーチンのテスト([Values(1, 2, 3)] int value1, [Values(1, 2, 3)] int value2, [Values(1, 2, 3)] int answer)
    {
        CalcAdd calcAdd = new CalcAdd();
        // value1とvalue2を引数としてメソッドを実行
        int actual = calcAdd.AddInt(value1, value2);

        // 実際の戻り値 actual と 期待する結果 answer とを比較し正しいか検証
        Assert.That(actual, Is.EqualTo(answer));
    }
}

乱数の組み合わせとしてパターン実行

次のテストは、xの値ごとに15回、3回実行され、それぞれが-1.0から+1.0までの5つのランダムダブルスと組み合わされます

using NUnit.Framework;

public class TestScript
{
    [Test]
    // 乱数を引数として渡します Random(最小値, 最大値, 繰り返し回数)
    public void 足し算ルーチンのテスト([Random(-3, 3, 3)] int value1, [Random(-3, 3, 3)] int value2, [Random(-3, 3, 3)] int answer)
    {
        CalcAdd calcAdd = new CalcAdd();
        // value1とvalue2を引数としてメソッドを実行
        int actual = calcAdd.AddInt(value1, value2);

        // 実際の戻り値 actual と 期待する結果 answer とを比較し正しいか検証
        Assert.That(actual, Is.EqualTo(answer));
    }
}

ドキュメント

参考

Test,Unity

Posted by hidepon