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

Humble Object Patternな話

いも
February 21, 2019

Humble Object Patternな話

Roppongi.unity #01の資料です

いも

February 21, 2019
Tweet

More Decks by いも

Other Decks in Programming

Transcript

  1. Humble Object Pattern
    な話
    Roppongi.unity #1 2019/02/21
    Roppongi.unity #1 2019/02/21

    View Slide

  2. いもです
    いも(@adarapata)
    ゲー
    ムクライアントエンジニア
    adarapata.com
    Roppongi.unity #1 2019/02/21

    View Slide

  3. ご注意
    2019/1/23
    にGotanda.unity #10
    で発表した「
    どこから始めるUnity
    Test」
    の時間切れで話せなかった後半部分にフォー
    カスした内容です。
    https://speakerdeck.com/adarapata/dokokarashi-meruunity-test
    なので前回と若干被る話が多いのでご了承ください。
    Roppongi.unity #1 2019/02/21

    View Slide

  4. 今日の話
    Humble Object Pattern
    について
    Roppongi.unity #1 2019/02/21

    View Slide

  5. 前提
    みんなテストが書きたくて手が震えている
    Roppongi.unity #1 2019/02/21

    View Slide

  6. テストしにくさの元
    密結合
    static
    なインスタンス
    入力イベントが絡む処理
    UI
    が絡む処理
    ファイル、DB
    などの外部リソー
    スが絡む処理
    etc..
    実際問題、
    どう綺麗に書いてもテストしにくい部分は出てくる
    Roppongi.unity #1 2019/02/21

    View Slide

  7. テストピラミッド。
    上位に行くほど難易度が高い
    Roppongi.unity #1 2019/02/21

    View Slide

  8. コスト高いのでテスト避けたい
    わかる
    時にはそういう判断も必要かもしれない
    書かない ≠
    書けない
    書けない理由は明らかにすべき。
    ピラミッドの境界を跨ぐような処理を分割していく
    Roppongi.unity #1 2019/02/21

    View Slide

  9. Humble Object Pattern
    とは
    テストしやすいものとしにくいものを住み分ける実装パター

    テストしにくいものの実装をHumble(
    控え目)
    にする
    多分初出は xUnit Patterns
    http://xunitpatterns.com/Humble Object.html
    クリー
    ンアー
    キテクチャ本にも載ってたので有名かも
    Roppongi.unity #1 2019/02/21

    View Slide

  10. public class PlayerUnit : MonoBehaviour {
    [SerializeField]
    private float _speed = 1F;
    void Update()
    {
    var horizontal = Input.GetAxis("Horizontal");
    var vertical = Input.GetAxis("Vertical");
    Move(new Vector3(horizontal, vertical));
    }
    private void Move(Vector3 direction)
    {
    transform.position += direction * _speed;
    }
    }
    きちんと任意の方向に動くかテストを実装したい
    Roppongi.unity #1 2019/02/21

    View Slide

  11. 書きにくさポイント
    入力が絡むので書きにくい
    MonoBehavior
    の機能に依存しているので書きにくい
    SerializeField
    も同様
    でも移動する部分は書きたい
    書きやすいものと書きにくいものを切り分ける。
    Unity
    における書きにくいものは大体MonoBehaviour
    絡みなのでそこか
    ら切り出す。
    Roppongi.unity #1 2019/02/21

    View Slide

  12. public interface IPlayerUnit {
    float Speed { get; }
    Vector3 Position { get; set; }
    }
    // MonoBehavior
    成分を0にしたけど、
    移動ロジックを持つクラス
    public class PlayerUnitController {
    private readonly IPlayerUnit _unit;
    public PlayerUnitController(IPlayerUnit unit)
    {
    _unit = unit;
    }
    public void Move(Vector3 direction)
    {
    _unit.Position += direction * _unit.Speed;
    }
    }
    テストを書きにくいMonobehavior
    からロジックを抽出する
    Roppongi.unity #1 2019/02/21

    View Slide

  13. public class PlayerUnitHumble : MonoBehaviour, IPlayerUnit {
    [SerializeField]
    private float _speed = 1F;
    private PlayerUnitController _controller;
    public float Speed => _speed;
    public Vector3 Position {
    get => transform.position;
    set => transform.position = value;
    }
    void Start() => _controller = new PlayerUnitController(this);
    void Update() {
    var horizontal = Input.GetAxis("Horizontal");
    var vertical = Input.GetAxis("Vertical");
    _controller.Move(new Vector3(horizontal, vertical));
    }
    }
    Roppongi.unity #1 2019/02/21

    View Slide

  14. 変更点
    移動処理の詳細と、Monobehavior
    が離れた
    具体的な処理は PlayerUnitController
    に任せるようになった
    テストしやすいものとしにくいものに分かれた
    ピラミッドの境界が明確になった
    切り分けられたので、
    移動処理のテストも書ける。
    入力のテストはやらない!
    移動処理を行う側はインタフェー
    スだけ見ているので、
    差し替えが楽。
    Roppongi.unity #1 2019/02/21

    View Slide

  15. IPlayerUnit
    は状況に応じて差し替えられる
    Roppongi.unity #1 2019/02/21

    View Slide

  16. IPlayerUnit
    は状況に応じて差し替えられる
    Roppongi.unity #1 2019/02/21

    View Slide

  17. テストコー

    public class PlayerUnitTest {
    //
    テスト用のいい感じモック
    public class MockPlayerUnit : IPlayerUnit {
    public float Speed { get; set; }
    public Vector3 Position { get; set; }
    }
    [Test]
    public void PlayerUnitMove() {
    var unit = new MockPlayerUnit { Speed = 5, Position = Vector3.zero };
    var controller = new PlayerUnitController(unit);
    controller.Move(Vector3.up);
    Assert.AreEqual(new Vector3(0,5F,0), unit.Position);
    }
    }
    Roppongi.unity #1 2019/02/21

    View Slide

  18. まとめ
    テストしにくい部分は発生する
    テストしにくいところとしやすいところを分けて、
    書ける領域を増
    やす
    Humble Object
    はそれらを切り分ける
    Roppongi.unity #1 2019/02/21

    View Slide