yotiky Tech Blog

とあるエンジニアの備忘録

Unity Unit Test(単体テスト)入門

目次

はじめに

  • この一連の記事は
    • UnityのUnity Test Frameworkを使ったテストに関して調べたメモ書きに補足を足したもの
    • Unityのテスト、およびDIコンテナ、モックライブラリの基本的な使い方やそれぞれの役割など入門レベルの解説
    • ライブラリのリファレンス的な使い方などについては公式や他の記事参照
    • 実機テストやCIは含まない
    • TDDに関しては考慮しない

シリーズの目次

環境

単体テスト入門

基本方針

  • 何をテストするのか
    • テストコードは保守が大変なので、実装と同時に足かせにもなる
    • コスパを考慮して、優先度をつけてテスト対象を絞る
      • 重要度が高い
      • 状態のパターンが多かったりロジックが複雑
      • 結合するとパターンの再現が難しくなる(動作が確認できない)
    • テストしづらく、手間がかかって、その上変更頻度も高い所は優先度低
      • 例えばUI周り、MonoBehaviourなどUnity由来の部分は大体コスパが悪い
      • WebやAndroidのように実行環境の種類が多く、人海戦術では逆にコストが増大にかかるようなのは検討の余地はある
    • テスト対象(ロジック)は、できる限りPure C# なクラスに切り出して小さく薄く作る

Unity Test Framework

手順

  • Test Framework 入ってなければ追加する
    • 1.3から非同期使えるのでバージョンを更新する
    • Package Managerから
      • by url : com.unity.test-framework@1.3
      • by name : com.unity.test-framework/1.3.9
  • Test Runner ウィンドウを開く
  • テストのアセンブリを定義する
    • PlayMode、EditModeそれぞれAssebly Folderを作成
  • アプリのアセンブリを定義する
    • アセンブリ定義を作成する
    • テストのアセンブリ定義にアプリのアセンブリ定義の参照を追加する
    • Assembly-CSharp.dllを直接参照しない
      • Workflow: How to create a Play Mode test | Test Framework | 1.3.9
      • Enable playmode tests for all assembliesはエディタから直接触れなくなっている
        • 一応、ProjectSettings.asset を直接開いて、playModeTestRunnerEnabledを1にすれば使える
        • ビルドする前に0に戻す必要がある
        • Test Runnerのタブを右クリックすると、無効化するために再起動を求められる、再起動後0に戻ってる
  • テストコードを実装する
    • 2つ目以降は
  • Test Runner ウィンドウからテストを実行する

参考

DIコンテナの導入

DIコンテナは

  • 主な利用目的
    • レイヤー境界での実装の分離、の解決
    • テスタビリティの向上(今回はこっち)
  • 実装の分離

    • インターフェースと実装を分離すると依存関係を抽象化することができる
    • 分離したとは言え、誰かは実装が何かを知ってなければならないし、インスタンス化しなければならない
    • これを関心事の中心(ドメイン)から分離して外へ追いやるために、DIやサービスロケーターが登場する
    • DIコンテナは参照関係を遅延して(後から)、関心事の外から解決(注入)する
    • ServiceLocatorなど
    • DIコンテナ
  • テストを目的とする場合、アプリケーションのプログラムに"テストのため"のコード、都合を混入せずに済み、テストを実行するまで依存関係の解決を遅らせることができる

  • DIコンテナは、"それ"だけで複雑な依存関係を解消したり、設計を容易にするための銀の弾丸ではないので使い所は慎重にすべき
    • 次の記事は今から18年近く前、DIが発表された翌年2005年の記事
    • 長い時間、何度となく同じような悩みを抱えてることが分かる
    • www.ulsystems.co.jp

インタフェースは

  • 主な目的
    • コントラクト(契約)
      • 使う側、使われる側がどういうインターフェースでやり取りするかを決める行為
      • クラスが何であれ、インターフェースが同じであれば、中身を知らなくても同じコントラクトでインターフェースの機能を利用できる
    • 依存性の逆転
      • 使う側、使われる側が直接参照関係を結ぶのではなく、間にインターフェースを挟むことで双方がインターフェースに依存するようになる
      • このインターフェースを使う側が持つことで依存関係が逆転する
      • インタフェースを使われる側が持つとまったく意味がない
        1. A->B
        2. A->interface<-B
        3. 境界はレイヤー境界(Namespaceなど)だったり、アセンブリの境界だったり
          • o : A->interface<-|-B
          • x : A-|->interface<-B
  • 単体テストで持つ役割
    • 依存性逆転を利用して、テストに都合の良い動きをするモックを参照するように仕向ける
    • こうすることでテストしたい機能(クラス、メソッド)に対して、呼び出し側、または機能から呼ばれる側の条件を自由に設定して様々な条件下のテストが容易にできるようになる

Extenject(Zenject)

  • AssetStoreからExtenjectを追加する
  • アプリケーションのasmdefに、zenjectの参照を追加する
  • 基本的な使い方
    • Inject対象を実装する
      • インジェクト方法は4つ
        • フィールドインジェクション(メンバにInject属性をつける)
        • プロパティインジェクション
        • メソッドインジェクション
        • コンストラクタインジェクション(属性なし、引数にバインドされる)
      • MonoBehaviour
        • コンストラクタ使えないので初期化メソッドを定義してメソッドインジェクション
    • MonoInstallerを継承したInstallerを作成して、依存関係を定義する
    • HierarchyでContextを追加してInstallerをアサインする

VContainer

  • VContainerを追加する
    • https://github.com/hadashiA/VContainer.git?path=VContainer/Assets/VContainer#1.13.2
  • アプリケーションのasmdefに、VContainerの参照を追加する
  • 基本的な使い方
    • Inject対象を実装する
      • インジェクト方法は4つ
        • フィールドインジェクション(メンバにInject属性をつける)
        • プロパティインジェクション
        • メソッドインジェクション
        • コンストラクタインジェクション(属性なし、引数にバインドされる)
          • コードストリッピング対策でInject属性をつける場合がある
      • MonoBehaviour
        • コンストラクタ使えないので初期化メソッドを定義してメソッドインジェクション
        • MonoBehaviourへのインジェクトは自動ではおこなれないので以下のどれかが必要
          • InspectorからLifetimeScopeに対象のGameObjectを登録する
          • コードからRegisterComponent*で登録する
          • Instantiateする場面では、IObjectResolver.Instantiateを代用する
    • LifetimeScopeを継承したコンポーネントで、依存関係を定義する
      • C#スクリプトを作成する時に「~LifetimeScope」にするとテンプレが適用される
    • HierarchyでLifetimeScopeを追加する

比較の参考

モックライブラリ導入

  • テスト用のモッククラスをテストケースの数だけ定義するのは無理がある
  • モックライブラリは動的にモックを生成できるため、テストケース(メソッド)の中で生成、動作を定義してその場で使えるようにしてくれる

Moq

yotiky.hatenablog.com

NSubstitute

yotiky.hatenablog.com

Pureな.NETのプロジェクトの方ではMicrosoft Fakesが未だに健在らしい。
これは、Visual Studio Enterprise以上でしか使えない「高級」かつ「重厚」なモックライブラリで、`DateTime.Now` すら書き換えられる強力なやつ。

Microsoft Fakes を使用したテストでのコードの分離 - Visual Studio (Windows) | Microsoft Learn

サンプルプロジェクト

github.com