3
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 1 year has passed since last update.

HoloLensAdvent Calendar 2021

Day 18

Unity Test Framework (UTF) と MRTK Test Utilities を使ったテスト駆動開発 (TDD) 入門

Posted at

Overview

XR Kaigi 2021 | Tokyo HoloLens ミートアップ枠で登壇させて頂いた内容を Qiita でご紹介させて頂きたいと思います。

イベント名 開催日時
XR Kaigi 2021 11/15 (月) ~ 11/17(水)
本セッションは 11/17(水) 18:00~18:40 の一部になります。
ー 登壇タイトル MRKT Test Utilites を使ったテスト駆動開発 (TDD) 入門

登壇概要

Unity Test Framework (UTF) を使ってテストを行うための基礎知識や、Mixed Reality Toolkit for Unity (MRTK) のテストユーティリティである MRTK Test Utilities パッケージの内部構成、Utilities パッケージを利用したテストコードの記述方法などを、実際の MRTK テストコードを紐解きながら解説いたします。xR アプリケーション開発におけるテスト、また、Unity Test Framework を使ったテストを試したことはないけれど、具体的にどんなことができるのか?興味をお持ちの方向けの入門セッションです。

XR Kaigi 2021 登壇資料

当日発表した資料は、SpeakerDeck にアップロードしています。

アジェンダ

  • MRTK について
  • MRTK Test Utilitites 概要
    • MRTK Test Utilities 内部構造
  • テスト駆動開発に関する基礎知識
    • テストコード と テスト駆動開発 ( Test-Driven Development ) の違い
    • 一般的な Unit Test (単体テスト) の役割
    • Unit Test ( 単体テスト ) を書くべき場面
  • Unity Test Runner ( Unity Test Framework ) の使い方
    • 検証環境
    • Unity Test Runner とは
    • Unity Test Framework ( UTF ) の内部構造
    • Test Assembly : アセンブリの仕組み
    • dll, asmdef ( Unity Assembly Definition )
    • asmdef プロパティ
    • テストの種類 : Play mode tests / Edit mode tests
    • Four Phase Test ( Setup / Excercise/ Verify / Teardown )
  • MRTK Test Utilities を使ったテストコードの書き方
    • MRTK 公式ドキュメント にスケルトン (雛形) が用意されている
    • NUnit, Unity Test Framework の属性
      -すぐに使えるメソッド ( Assert, TestHand, TestUtilities )
    • 実際の挙動 ( Play mode test )
  • テスト駆動開発 ( Test-Driven Development ) に入門してみて

MRTK とは

Microsoft が開発を主導しているオープンソースプロジェクト。クロスプラットフォーム向けの Mixed Reality アプリケーション開発を加速させる便利な機能や UX Component ( Script & Prefab ) 等が提供されています。現在の最新バージョンは v2.7.2 です。

image.png (412.8 kB)
  • クロスプラットフォームの入力システム、3次元空間のインタラクションやUI を提供
  • Unity Editor のシミュレーションですぐに変更を確認できるので、ラピッドプロトタイプが可能
  • 幅広いプラットフォームをサポート ( eg. OpenXR, WinMR, Oculus, OpenVR, Mobile )
  • Unity, Unreal 各ゲームエンジン向けのプロジェクトがある ( ※ 今回は Unity を使用 )
  • MIT ライセンス での提供 ( 商用利用可能 )
image.png (31.7 kB)

MRTK's Unit Test (単体テスト)

MRTK の信頼性を担保するため、MRTK には コードの変更によって既存の挙動が後退しないことを保証するための テストコードセットが用意されています。MRTK のような大きな Codebase ( ソースコードの集まり ) で適切なテストカバレッジを有することは安定性に不可欠であり、変更を加える際の自信にも繋がります。

MRTK は NUnit の Unity インテグレーションを使用した Unity Test Runner を使っています。

MRTK Test Utilities 概要

  • 実際の MRTK コンポーネント開発にも使われているテストユーティリティ

  • Mixed Reality Toolkit for Unity ( MRTK-Unity ) v2.5.0 から追加された

  • ( Microsoft.MixedRealityToolkit.TestUtilities パッケージ) 開発者が簡単に Play mode test を作成できるヘルパースクリプトのコレクション

  • このユーティリティは、MRTK コンポーネントの開発者に特に役立ちます。

  • Test utilities package | Microsoft Docs

MRTK Test utilities 内部構造

MRTK Test Utilities の中には、以下のソースコードが含まれています。

ファイル名 概要
PlayModeTestUtilities.cs テスト実行時に入力ソースを操作するためのクラス
TestController.cs シミュレートされたコントローラーを操作するための抽象クラス
TestHand.cs TestController の派生クラス
( テスト用の手をシミュレーションすることが可能 )
TestMotionController.cs TestController の派生クラス
( テスト用のモーションコントローラーをシミュレーションすることが可能 )
TestUtilities.cs テスト実行時に Scene, MixedRealityToolkit, Playspace などを操作するためのクラス
MRTK.Tests.Utilities.asmdef Test Utilities のアセンブリ定義ファイル

MRTK Test Utilities で出来るコト

MRTK Test Utilities を使用すると、Unity play mode tests (再生モード) 内において、
コードベースで入力ソースのシミュレーションが可能になります。

eg シミュレーションした手を180°回転させるデモ

実際のソースコード

image.png (64.5 kB)

テスト駆動開発に関する基礎知識

テストコード と テスト駆動開発 ( TDD : Test-Driven Development ) の違い

image.png (189.7 kB)

テストコード は健康診断、テスト駆動開発 (Test-Driven Development : TDD) は健康診断 ~ 診断結果 (数値) の維持・改善ループ

テストコード

  • テストのためのソースコード。
  • 実行時に レッドグリーン のステータスを返す。

テスト駆動開発 (Test-Driven Development : TDD)

  • ユニットテストをプロダクションコードよりも先に記述することを原則とした開発手法
    • この原則は テストファースト ( test first ) と呼ばれる。

「テスト駆動開発の開発サイクル」

  1. 設計する
  2. テストコードを書く ( レッド : テスト失敗 )
  3. プロダクションコードを書く ( グリーン : テスト成功 )
  4. リファクタリングを行う ( グリーン : テスト成功 )

一般的な Unit Test (単体テスト) の役割

  • クラスやメソッドを対象としたプログラムを検証するためのテストであり、ソフトウェアテストの中で最も粒度の小さいテスト。
  • 対象クラスやメソッドが「期待された振る舞い」をするか検証し、テスト成功時にそれを保証する
  • 「期待された振る舞い」 == 対象のクラスやメソッドの仕様

Unit Test ( 単体テスト ) を書くべき場面

「完璧なテスト」を書くことが目的化してはいけない。

ユニットテストの価値が高い場面

  • 回帰テストが必要な操作
  • 課金が発生する操作 ( eg Azure Mixed Reality サービス利用時 )

ユニットテストを書くメリット
コード修正時の動作確認の自動化 & プログラマーの自信に繋がる。
※ 但し、テストコードを書くにはエンジニアの学習コストも掛かる。
そして、繰り返しテストを実施しない場合は、メリット < コスト のデメリットの方が大きくなる。

Unity で Unit Test (単体テスト) を実施する

検証環境

  • 開発用PC ( Windows 10 Pro / ビルド番号 : 19042.1288 )
  • Unity 2020.3.8f1
    • Unity Test Framework 1.1.30
    • MRTK v2.7.2
      • Mixed Reality Toolkit Foundation 2.7.2
      • Mixed Reality Toolkit Standard Assets 2.7.2
      • Mixed Reality Toolkit Test Utilities 2.7.2
      • Mixed Reality OpenXR Plugin 1.0.3

Unity Test Runner

Unity 標準の ユニットテスト実行環境。Unity バージョン 2019.2 から、テスト用のパッケージ Unity Test Framework ( UTF ) として提供されている。Unity Test Framework (UTF) は NUnit の Unity インテグレーションパッケージです。NUnit は .NET 言語向けのユニットテスト用オープンソースライブラリで、UTF では NUnit バージョン 3.5 が使用されています。最新バージョンは、1.1.30 (2021/10/23 現在)

image.png (389.1 kB)
Unity バージョン Unity Test Framework (UTF) 有無
Unity 2019.1 以前 Test Runner が Unity Editor に組み込まれているので、Unity Package Manger (UPM) からインポートする必要はない。
Unity 2019.2 以降 Test Runner がパッケージ化されたため、Unity Package Manager から Test Framework パッケージを各自インポートする必要がある。
Unity 2020.3.8f1 ( LTS ) プロジェクト生成時の manifest.json に Test Framework 1.1.29 が含まれていました。詳しくは Packages/manifest.json または、Unity Package Manager (UPM) を参照してください。
image.png (46.7 kB)

テストの種類

image.png (170.2 kB)

Edit mode tests (編集モードテスト)

  • ゲーム再生 ( Play ) をしていない Edit mode ( 編集モード ) の状態で実行されるテスト
  • ゲーム再生 ( Play ) 無しで実行できるのでオーバーヘッドが少ない

Play mode tests | Microsoft Docs

Play mode tests (再生モードテスト)

  • ゲーム再生 ( Play ) をしている状態で実行されるテスト
  • 手や目などの異なる入力ソースに対して、どのようなレスポンスを返すのか確認可能
  • ゲーム再生 ( Play ) 遷移時のオーバーヘッドが大きい

※ 実行時に毎回テスト用のシーンが生成・ロードされる。こちらのシーンを使いまわして、テストが実施される。
テストケース (テスト毎) に適切な TearDown (後処理) を行わないと、後続のテストに影響が及ぶ。
特定のシーンでテストを実行する必要がある場合は、対象のシーンを読み込む処理を書く必要がある。

Edit mode tests | Microsoft Docs

Unity Test Runner の使い方

  • Unity Test Framework がインストールされているか確認する ( Unity Package Manager )
image.png (93.5 kB)
  • Unity Test Runner ウィンドウを開く
Unity
  • Unity Test Runner が開いたら [ PlayMode ] or [ EditMode ] を選択します。
image.png (46.3 kB)
  • [ Create PlayMode Test Assembly Folder ] ボタンを押し、テストアセンブリフォルダを作成します。
image.png (106.3 kB)

※ Project ウィンドウで選択しているフォルダ配下に、新しいテストフォルダが生成されます。

image.png (74.7 kB)
  • 生成されたテストアセンブリフォルダの中身を確認します。
image.png (25.4 kB)

フォルダ名と同じ名前のアセンブリ定義ファイル ( Assembly Definition | 拡張子 .asmdef ) が生成されていることが確認できます。

image.png (125.4 kB)

Test Assembly とは

Unity Test Runner では、Assembly References で nunit.framework.dll を参照しているアセンブリを Test Assembly (テストアセンブリ) として検索し、Test Runner で表示します。

Test Assembly (テストアセンブリ) では、テスト対象のアセンブリを手動で設定 (Assembly Definition References) する必要があります。

image.png (295.9 kB)

.asmdef - Assembly の仕組み

Assembly Definition (アセンプリ定義) は、アセンブリを分割できる仕組みです。アセンブリの分割を行うメリットは、ソースコード修正時にコンパイル時間を最小限に抑えること。Unity バージョン 2017.2 以前、プロジェクト内のすべてのスクリプトが AllCSharp-Assembly.dll にまとめられていたため、ソースコードを一箇所修正するたびにプロジェクト内すべてのスクリプトのコンパイルが必要となり、開発イテレーション (反復) の遅延の原因となっていた。

テストコード作成時も、asmdef でどの dll を参照するのか自分で設定を行う必要がある。

Assembly Define Properties - アセンブリ定義プロパティ

プロパティ 説明
Name アセンブリの名前 (ファイル拡張子なし) アセンブリの名前は、プロジェクト全体を通してユニーク (一意) である必要があります。
Allow 'unsafe' code アセンブリ内のスクリプトに C# unsafe キーワードを使用している場合は、**['unsafe' Code を許可する] ** を有効にします。このオプションが有効な場合、Unity はアセンブリをコンパイルする際に、C#コンパイラに 'unsafe' オプションを渡します。
Auto Referenced 事前定義されたアセンブリがこのプロジェクトのアセンブリを参照するかどうか指定します。Auto Reference (自動参照) オプションを無効にした場合、Unity はコンパイル中にアセンブリの自動参照を行いません。これは Unity がビルドにアセンブリを含むかどうかに影響を及ぼしません。
No Engine References このオプションを有効にした場合、アセンブリをコンパイルする際、Unity は UnityEditor もしくは UnityEngine への参照を追加しません。
Override References Override References (参照の上書き) を有効にすると、このアセンブリに依存関係のある事前コンパイル済みアセンブリを手動で指定することができます。オプションを有効にすると、Inspector に [Assembly References] セクションが表示され、参照を指定することができます。プリコンパイル済みのアセンブリは、Unity プロジェクトの外部でコンパイルされたライブラリです。
Root Namespaces このアセンブリ定義内のスクリプトのデフォルトの名前空間。Rider もしくは Visual Studio いずれかのコードエディターを使用する場合、このアセンブリ定義で新規作成したスクリプトにデフォルトの名前空間を自動で追加します。

テストコードを書く前に

設計 : Four Phase Test

ユニットテストに限らず、ソフトウェアテストは次の4フェーズで実行されます。

テストコードの4フェーズパターン

フェーズ 概要
事前準備 ( setup ) テスト対象オブジェクトの初期化、必要な入力値、期待される結果等の準備を行うフェーズ
テストの実行 ( exercise ) テスト対象オブジェクトに対して、テストの操作を行うフェーズ
実行結果の検証 ( verify ) テストの結果が期待通りであるか比較検証を行うフェーズ
後処理 ( teardown ) 次のテストに影響が及ばないよう後処理を行うフェーズ
  • テストコードでどんなテストを実施しているのか分かりづらいと、結局テストコードを導入する意味が薄れるので、テストコードも 可読性 を意識する必要がある。
  • 但し、「完璧なテスト」を書くことは不可能なので、ユーザーの要求 & 予算に応じて 「十分に良いテスト」を目指す。

MRTK Test Utilites を使ったテストコードの書き方

MRTK 公式ドキュメントに スケルトン (雛形) が用意されています。

Play Mode テストコードの雛形

ExamplePlayModeTest.cs
using Microsoft.MixedReality.Toolkit.Input;
using Microsoft.MixedReality.Toolkit.Utilities;
using NUnit.Framework;
using System;
using System.Collections;
using System.Linq;
using UnityEngine;
using UnityEngine.TestTools;

namespace Microsoft.MixedReality.Toolkit.Tests
{
    class ExamplePlayModeTests
    {
        // This method is called once before we enter play mode and execute any of the tests
        // do any kind of setup here that can't be done in playmode
        // このメソッドは Play Mode (再生モード) に遷移前、テストが実行される前に一度だけ呼び出されます。
        // Play Mode (再生モード) で実行することができないセットアップを記述します。
        public void Setup()
        {
            // eg installing unity packages is only possible in edit mode
            // so if a test requires TextMeshPro we will need to check for the package before entering play mode
            // 例 : Unity Package のインストールは、edit mode (編集モード) でのみ可能です。
            // もしテストが TextMeshPro を要求するのであれば、パッケージのチェックを
            PlayModeTestUtilities.InstallTextMeshProEssentials();
        }

        // Do common setup for each of your tests here - this will be called for each individual test after entering playmode
        // Note that this uses UnitySetUp instead of [SetUp] because the init function needs to await a frame passing
        // to ensure that the MRTK system has had the chance to fully set up before the test runs.
        // ここで各テストに共通のセットアップを行います - これはプレイモードに入った後に各テストケース毎に呼び出されます
    // init関数はフレームが通過するのを待つ必要があるため、これは [SetUp] の代わりに [UnitySetUp] を使用することに注意してください
        [UnitySetUp]
        public IEnumerator Init()
        {
            // in most play mode test cases you would want to at least create an MRTK GameObject using the default profile
            // ほとんどのプレイモードのテストケースでは、少なくともデフォルトのプロファイルを使用して MRTKGameObject を作成する必要があります
            TestUtilities.InitializeMixedRealityToolkit(true);
            yield return null;
        }

        // Destroy the scene - this method is called after each test listed below has completed
        // Note that this uses UnityTearDown instead of [TearDown] because the init function needs to await a frame passing
        // to ensure that the MRTK system has fully torn down before the next test setup->run cycle starts.
        // シーンを破棄する - このメソッドは、以下にリストされている各テストケースが完了した後に呼び出されます
        // init関数はフレームが通過するのを待つ必要があるため、これは [TearDown] の代わりに [UnityTearDown] を使用することに注意してください
        [UnityTearDown]
        public IEnumerator TearDown()
        {
            PlayModeTestUtilities.TearDown();
            yield return null;
        }

        /// <summary>
        /// 新しい MRTK Play Mode Test のスケルトン (骨組み)
        /// </summary>
        [UnityTest]
        public IEnumerator TestMyFeature()
        {
            // ----------------------------------------------------------
            // 例 : Play Mode Test メソッド
            // ----------------------------------------------------------
            // 入力システム (Input Sytem) の取得
            var inputSystem = PlayModeTestUtilities.GetInputSystem();

            // 入力用のテストハンド (test hand) を生成
            var rightHand = new TestHand(Handedness.Right);
            yield return rightHand.Show(new Vector3(0, 0, 0.5f));

            // テストハンド (test hand) の移動
            // テストハンド (test hand) を新しい位置に移動するため、yield return を実行します。
            // このアクションを完了するためには、複数のフレームが必要です。
            yield return rightHand.MoveTo(new Vector3(0, 0, 2.0f));

            // テストハンド (test hand) から、固有のポインターを取得します。
            var linePointer = PointerUtils.GetPointer<LinePointer>(Handedness.Right);
            // テスト結果の検証
            // ポインターが取得できているかどうか
            Assert.IsNotNull(linePointer);
            // ---------------------------------------------------------
            yield return null;
        }
    }
}

Edit Mode テストコードの雛形

EditModeExampleTest.cs
using NUnit.Framework;

namespace Microsoft.MixedReality.Toolkit.Tests
{
    class EditModeExampleTest
    {
        [Test]
        // このメソッドの名前は Unity Test Runner のテスト名として使用されます。
        public void TestEditModeExampleFeature()
        {

        }
    }
}

属性 ( Attribute )

属性 役割
UnitySetUp Setup (事前準備) 関数用の属性
TearDown TearDown (後処理) 関数用の属性
UnityTest テストケース関数用の属性

Assert

アサーション ( 値の比較検証 ) は、NUnit の Assert クラスを使用します。

NUnit 2.x 系以前 : Classic Model

メソッド 概要
Assert.IsFalse() 対象 Boolean が false かどうか検証を行う
Assert.IsTrue() 対象 Boolean が true かどうか検証を行う
Assert.IsNotNull() 対象変数が null でないか検証を行う

NUnit 3.x 系以降 : Constraint Model

メソッド 概要
Assert.That(actual, constraint) actual (実際の値) が constraint (期待した状態) であるか検証を行う

現在も Classic Model を使用可能です。

TestHand Class メソッド ( 継承 : TestController )

メソッド 概要
Click() TestHand でクリックを行うメソッド
GrabAndThrowAt() TestHand で掴んで移動させるメソッド
Hide() TestHand を非表示にするメソッド
Move() TestHand を移動するメソッド
MoveTo() TestHand をステップごとに移動するメソッド
SetGesture() TestHand のジェスチャーをセットするメソッド
SetRotation() TestHand の回転をセットするメソッド
Show(Vector3 position, bool waitForFixedUpdate = true) TestHand を表示するメソッド

TestUtilities Class メソッド

メソッド 概要
InitializeMixedRealityToolkit() MixedRealityToolkit ゲームオブジェクトの生成、標準プロファイルの設定を行うメソッド
InitializeMixedRealityToolkitAndCreateScenes() MixedRealityToolkit ゲームオブジェクトを追加する前に、空のシーンを生成するメソッド
InitializePlayspace() 初期の Playspace の Transform とカメラ位置 (Position) をセットするメソッド
ShutdownMixedRealityToolkit() 以前に作成された MixedRealityToolkit ゲームオブジェクトと Playspace を Destroy (破棄) するメソッド

テスト駆動開発 ( Test-Driven Development ) に入門してみて

実際に Test-Driven Development (テスト駆動開発) を試してみて、感じたこと。

image.png (195.9 kB)

メリット

回帰テスト ( Regression Test : リグレッションテスト )

  • ソースコード変更後、繰り返し自動テストを行えるのソースコードに修正を加えやすい
  • 手動で繰り返しテストを行う煩わしさ (工数) を省略できる
  • ソースコードの不具合 (バグ) を見つけるまでの時間が短縮される (バグの早期発見)

デメリット

  • テストコードの知識がいないと運用がしんどい
  • 最初はどのソースコードでテストが必要かの判断が難しい
  • チーム開発の場合、全員が同じ粒度でテストを書く運用をしないと厳しい
  • 実装の前にテストを書く必要があるので初期コスト ( 学習コスト & テストコード記述 ) が大きい

参考文献

Unity Test Framework (UTF) や テスト駆動開発 (Test-Driven Development) を調査するにあたり、以下Webページ、書籍を参考にさせていただきました。ありがとうございました。

書籍

  • JUnit 実践入門  (技術評論社)
  • Unity ゲーム プログラミング・バイブル 2nd (株式会社ボーンデジタル)
  • レガシーコードからの脱却 (O’REILLY)

Web サイト

3
1
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
3
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?