imog

主にUnityとかの事を書いています

UnityでTDDハンズオンしたお話

Unityゲーム開発者ギルド2 Advent Calendar 2020 24日目のエントリです。2時間オーバーしちゃったごめんなさい

adventar.org

今月の頭くらいにUnityゲーム開発者ギルドの人たちを対象にTDDハンズオンをやってみました。

何故やったのか

TDDというのはなんとなくわかったのだけど、Unityでのゲーム開発を行うときにどう始めたらいいのかわからないということが最近あったのでどうしようかと悩んでいて。 FizzBuzzをTDDでやってみようなどは既にいっぱいあるけどやはりUnityというものと直接紐づいた事例がないので、もっと現実にありそうなテーマでTDDハンズオンをやってみようと考えたのでした。 何度も繰り返してブラッシュアップしていきたいなと思ったのでどこかで話を聞いてくれる人がいないかなあとギルドで募ったら意外とみなさん手を挙げてくださったので、感謝の正拳突きをしながらzoomでTDDハンズオンをしたのでした。

abe

その時の画像。参加者の方にはモザイクをかけています。僕はアバターで参加しました。

二日連続でやったのですが二回参加された方もいて圧倒的感謝。 2時間超えの長丁場にお付き合いいただきありがとうございました。

UnityでのTDDは特殊なのか

特に何も変わらないと思っています。MonobehaviourがUnityレイヤにべったりで書きにくいというのはその通りですがそれはAndroidのActivityも大体そんな感じだしWebにおけるViewレイヤもそうであるのでUnityが特殊ということは無いでしょう。

どんなことをしたのか

名前変更機能を作ろう

ユーザーが自分の名前を変更できるようにしたい、というストーリー

  • 開いたときに画面に現在の名前を表示させておきたい
  • ユーザが入力フォームから入力できるようにしたい
  • 名前はアルファベットのみで1~8文字以内にしたい
  • 変更に成功したら変更成功したことを表示したい
  • 変更に失敗したら変更成功したことを表示したい
  • 現在の名前が変更後の名前に変わってほしい
  • 名前の変更が保存されててほしい

画面には、自作コンポーネントの貼られていない、それっぽく作ったUIだけを添えています。

image

一切スクリプトがない状態か、これを実装していこう!みたいなストーリーです。

入力と加工と出力

要件は一杯あるので、まずはざっくりと入力と加工と出力に分類してみる。 基本的には出力はテストが難しい。出力はプラットフォーム固有の機能を利用することが多いのでテストに組み込みにくい。ログが吐かれていることをテストするのは若干骨が折れる。また、正解を定義しにくいのもある。「ゲームクリア!」と表示されていればいいのか?「GameClear!」になるとテストとして間違っているのか?みたいなところ。

加工部分は一番簡単である。何らかのインプットをすることで何らかのアウトプットが返ってくることを期待するのはプログラミングの基本だから定義しやすい。

入力

  • ユーザが入力フォームから入力できるようにしたい

加工

  • 名前はアルファベットのみで1~8文字以内にしたい

出力

  • 開いたときに画面に現在の名前を表示させておきたい
  • 変更に成功したら変更成功したことを表示したい
  • 変更に失敗したら変更成功したことを表示したい
  • 現在の名前が変更後の名前に変わってほしい
  • 名前の変更が保存されててほしい

と思ったら出力ばかりでつらいですね。みたいなところから始まるTDDハンズオンです。

伝えたかったこと

大体この2点です

  • 思考を書き出す
  • 不安になったら書きましょう

大きな問題に大きいまま挑むとだいたい返り討ちに合います。メインのゲームを実行できるようにするぞと意気込むと、GameManagerでGodなClassが生まれて無限に連なるゲームを開始するメソッドが生えるでしょう。ゲームを実行するという言葉の解像度を少し上げてみると実はいろんな意味が含まれていたりします。

  • 利用するアセットをロードする
  • 敵キャラクターの生成
  • プレイヤーの生成
  • ゲームタイマーの設定 etc...

コードを書くその前に、対象を観察して問題を細かく分割していくことは大事です。これらは特別なスキルではなく、皆さん割と脳内で無意識に考えているはずです。その結果なんらかのクラスが生まれたりメソッドが生えたりするわけなので。その無意識に行っている分割と思考の整理をテストケースとして書き出すといいのではないかなと考えてます。テストを書くために特別な思考をというよりも、普段の脳内を垂れ流したらテストできてたみたいなイメージ。これを繰り返していくと手癖で書けるようになるのかなあとは思ってます。

不安になったら書きましょうはかなり抽象的な話ですが、自分自身こうしているので・・。すくなくとも全てのコードにテストを書く気はないしテストファーストじゃないといけないことはないです。なんかちょっと怖いな~と思ったら書きます。 怖いなー不安だなーをもっと具体的にするなら、コードの循環的複雑度が高そうなら書くといいのかなと思います。一切のif文のないまっすぐなコードを書くときに不安を感じる人はあまりいないでしょう。

また、不安の元をきちんと整理することも大事です。不安でテストを書きたいけどUnityが提供しているAPIが挟まってて書きにくいという状況もままあるでしょう。しかし一旦整理すると、その不安要素にUnity関係ある?みたいなこともあるかもしれません。僕はTransform.SetParentが正しく動くか不安だと感じたことはあまりありません。そこは想定したインプットをすれば想定したアウトプットをしてくれると期待しているからです。(もし期待通りに来なかったらUnity ForumにPostしましょう)

でも、Transform.SetParentの呼び出しに至るまでに自分で書いたロジックが大量に挟まっているなら不安になるかもしれません。その際は分離をしてみるか、みたいなアプローチを考えて書き直すのもアリかもしれません。

余談ですが、1メソッド単位でテスト書くのは僕は推奨しません。テストを書くためにカプセル化された機能が表に出るのは勿体ないし、メソッド名の変更だけでも修正の恐れがあるのはちょっと辛いからです。メソッドのテストを書くのではなく期待している振る舞いに対してテストを書く方が好きです。

反省

前半は簡単なテストから始めていったけど、後半は出力に近い領域のテストを書くためにinterfaceを挟んでモックを作るなどして一気に加速したのでそこからついていけないということが起きてました。この辺は今後改善していきたいところ・・・。

あとは、題材とした名前変更機能は昨今のスマホゲームなら確かにあり得るテーマだけど、リアルタイムなレスポンスがあったり、オブジェクト同士がぶつかり合ってメッセージングし合うとかいわゆる「ゲーム」みたいな場面とは少し離れているなぁと思った。なのでそのあたりをケアできるといいなあと考えている。

おまけ:欲しいのはテストかイテレーション

ゲーム開発でテストコードを書くときに度々話題になることとして、Viewのテストが書きたいというモチベーションとの向き合い方があると思う。ここでいうViewは実際に画面に情報を出力する役割を持つクラスと定義する。 前述したが、出力に近い部分のテストは大抵書きにくい。

  • 正常系の定義を定めにくい
  • 変更頻度が多いのでメンテナンスコストが高い
  • 出力部分の機能はテスト上で動かないことが多い

こういう理由があって割に合わないことが多いから自分はあまり出力部分をテストコードでカバーしたいと考えておらず、エディタとか実機で見ようぜ!みたいな考えがあるんだけどやっぱり声は聞いたりする。果たしてその気持ちはどこから溢れてくるのかというのを聞くと、こういうものがあった。

  • 毎回実行して任意の画面まで遷移するコストが高い

コードを書いてPlayMode再生して、ログインしてホーム画面を通って目的の画面へえんやこら。これがアプリケーションの成長につれて時間がかかるようになる。なのでPlayせずに見たいし調整したい。テストコードでなんとかしたい!

凄く気持ちは伝わったけど、多分これはトライアンドエラーイテレーション速度を上げたい欲求の話で、そこに対してテストコードで正常系を担保というのは目的と合ってなくて難易度が高そうだなと感じた。そもそもトライアンドエラーでいい感じに画面調整するために毎度毎度テストコード書き直すの辛いし。 これの実際の問題は直接画面を開くことができない設計になっていることで、何故直接開けないかというとその画面に必要な情報だけじゃなくてログインしたユーザデータとかその他諸々がシングルトンに鎮座していることが多いから。キャラクタのフレーバーテキストを見る画面でもユーザのインスタンスがないといけないとか稀に良くある。

そのあたりを改善するには、外から最低限のパラメータ流し込むだけで画面が単体で立ち上がるみたいなモジューラビリティの高い設計にしないといけなくて、それはコードを書き直していくしかなさそう。難易度が高いのはよくわかる・・。

なので、Viewのテストが書きたいとなったときは一旦立ち止まって見るのがいいなと思いました。

おまけのおまけ

人間の温かさに包まれました