Upgrade to Pro — share decks privately, control downloads, hide ads and more …

Zenject.SceneTestFixture and DI

Yosuke Nakano
October 23, 2019

Zenject.SceneTestFixture and DI

How to use Zenject.SceneTestFixture and example clean architecture on unity.

Yosuke Nakano

October 23, 2019
Tweet

More Decks by Yosuke Nakano

Other Decks in Technology

Transcript

  1. 中野 洋輔
    Moff Inc.
    Zenject.SceneTestFixture
    と向き合った
    naninuneno_y

    View Slide

  2. Zenject.SceneTestFixture
    • Dependency Injection(依存性注入)のためのライブラリ
    • 過去の勉強会でも取り上げられている
    • Unity Zenject完全に理解した
    依存性注入?
    疎結合というキーワードが出始めた時、C#ならばinterfaceを使うのが常套手段
    InterfaceBに依存したClassAにClassBのインスタンスを渡してやるのが依存性注入

    View Slide

  3. Zenject.SceneTestFixture
    • ZenjectではTest-Runnerでの実行もサポートされており、
    • テストコード上で専用のBind()を行うことができる
    • その内、シーンテストを行うためのもの
    Zenject/WritingAutomatedTests.md
    詳しくは
    自分でGoogle翻訳かけた版

    View Slide

  4. そもそも何がしたいんだっけ?
    • 画面毎(≒シーン毎)の挙動をテストしたい
    • このボタンを押すとxxxが消えるとか
    • 名前の入力とか
    • アプリケーションロジックをテストしたい
    • ログイン画面のテストでサーバにアクセスしない
    • ゲームコントローラが無くてもテストできる

    View Slide

  5. そもそもinterfaceって?
    • オブジェクト指向をより理解するために実際に書いて解説する
    (Qiita)
    • クラスの継承がコーディングの上で何が便利か
    • Interfaceの抽象化のメリット/デメリット
    • Unity開発で使える設計の話+Zenjectの紹介(SlideShare)
    • SOLID原則について説明
    • Zenjectの導入もある
    • 書籍
    • Adaptive Code ~ C#実践開発手法 第2版 (マイクロソフト関連書)
    • Clean Architecture 達人に学ぶソフトウェアの構造と設計

    View Slide

  6. Clean Architecture
    • 手掛かり無く抽象化を行うよりは、
    アーキテクチャに則るのが良い
    • アプリケーションのロジックを
    UseCasesより内側に閉じ込める
    • テストに場合に青の層の実装を入れ替
    える
    CleanArchitectureでひとつ 『上』 のコードを目指す:概念編(Qiita)

    View Slide

  7. LoginUseCase LoginPresenter LoginView
    ILoginPresenter ILoginView
    AuthController FirebaseAuth
    IAuthController
    DummyAuth
    IAuth
    ログイン画面を作る場合…
    • 普通に使うときはFirebaseAuthを使い、テストで実
    行する場合はDummyAuthを使うよう切り替えたい
    • 依存先をinterfaceにしているので、DI次第で可能!

    View Slide

  8. シーン上では…
    public class LoginMain : MonoBehaviour {
    IUseCase authUseCase;
    [Inject]
    void ConstructUseCases(IAuthController authController,
    ILoginPresenter loginPresenter) {
    authUseCase = new AuthUseCase(
    loginPresenter,
    authController,
    this
    );
    }
    void Awake() {
    authUseCase.Begin();
    }
    }
    • InjectメソッドでUseCaseの材料を受け取り、
    UseCaseを生成
    • MonoBehaviour.Awakeで実行
    CleanArchitectureでひとつ 『上』 のコードを目指す:実装編(Qiita)

    View Slide

  9. シーン上では…
    public class LoginSceneInstaller : MonoInstaller {
    [SerializeField] LoginView loginView = default;
    public override void InstallBindings() {
    Container
    .Bind()
    .FromInstance(new AuthController(FirebaseAuth.DefaultInstance))
    .AsCached();
    Container
    .Bind()
    .FromInstance(new LoginPresenter(loginView))
    .AsCached();
    }
    }
    • [SerializeField]など実装の詳細はDIを行うこいつ集約

    View Slide

  10. public class LoginSceneTest : SceneTestFixture {
    LoginPresenter loginPresenter = new LoginPresenter();
    AlertView alertView;
    LoginView loginView;
    AuthController auth = new AuthController(new DummyAuth());
    [UnityTest]
    public IEnumerator ログイン失敗でアラートが表示されるか() {
    // StaticContextにBind
    StaticContext.Container.Bind().FromInstance(loginPresenter).AsTransient();
    StaticContext.Container.Bind().FromInstance(auth).AsTransient();
    // シーン読み込み
    yield return LoadScene("Login");
    // シーンから欲しい要素をfind
    var canvas = GameObject.Find("Canvas").transform;
    alertView = canvas.Find("AlertView").GetComponent();
    loginView = canvas.Find("LoginView").GetComponent();
    loginPresenter.View = loginView;
    // 適当なID/パスワード入力
    loginView.IdInputField.onEndEdit.Invoke("hoge");
    loginView.PasswordInputField.onEndEdit.Invoke("fuga");
    // 実行
    loginView.LoginButton.onClick.Invoke();
    yield return null;
    // アラートが表示されているか
    Assert.IsTrue(alertView.gameObject.activeSelf);
    // 閉じるボタン
    alertView.CloseButton.onClick.Invoke();
    yield return null;
    // アラートが消えたか
    Assert.IsFalse(alertView.gameObject.activeSelf);
    }
    }
    • なんかいけそうやん!

    View Slide

  11. • StaticContextよりもSceneContextが優先されることを知り、
    InstallerのBindすべてに.IfNotBound()をつけた
    • LoadScene時にAwakeが実行されないようにMainをアタッチ
    したGameObjectをテストのときには一時非アクティブにする
    ようにした
    • GitHub
    • 労力とリターンがあってない気がする(他にいい方法あるだろ)
    しかしこの後…
    naninunenoy/UnityViewPatterns/ViewPattern/BMIApp

    View Slide