やらなイカ?

たぶん、iOS/Androidアプリの開発・テスト関係。

Unite Tokyo 2019『Unity Test Runnerを活用して内部品質を向上しよう』のフォローアップ #UniteTokyo

9/25, 26に開催されたUnite Tokyo 2019 Day 2に『Unity Test Runnerを活用して内部品質を向上しよう』というタイトルで登壇させていただきました。

スライドはすでにUnity Learning Materialsで公開中。動画は後日公開される予定です。

learning.unity3d.jp togetter.com

以下、説明が足りなかったところ、質疑応答やTwitterでコメントいただいた点など補足していきます。

CEDEC 2019『組織にテストを書く文化を根付かせる戦略と戦術』 (p.6)

いきなり、TDD(テスト駆動開発)の大家、@t_wadaさんによるCEDEC 2019でのセッションを紹介しました。 私はUnity製ゲームに特化した話をしましたが、そのベースとなる、より一般的な視点での開発者テストについて語られています。 ぜひ併せて読んでいただくことをおすすめします。

cedil.cesa.or.jp

当日「すでに見た」という方に挙手いただきましたが、2割いないくらいで驚きました。こちらを見てテストに興味を持って来る方が大多数だと思っていたので、その前段階なしでテストのセッションに興味を持ってたくさん来ていただいたのは良い意味で予想外でした。

Unity Test Framework (p.33)

Unity 2019.2からUnity Test Runnerがpackage化されたことに触れましたが、それにまつわる変更についてコメントいただきましたので確認して別記事にまとめました。

www.nowsprinting.com

Player(実機)でのテスト実行 (p.34)

実機でのテスト実行についてセッションで触れなかったので、別記事にまとめました【10.2追記】

www.nowsprinting.com

インテグレーションテスト (p.52)

スライドで紹介した、Sceneベースのテストフレームワークはこちら。

github.com gitlab.com

AltUnityTesterについては、以前Gotanda.unityでお話したスライドも参考にしてください。

www.slideshare.net

インライン化 (p.65)

これはきちんと検証できていないので情報のみですが、インライン化してほしいメソッドに [MethodImpl(MethodImplOptions.AggressiveInlining)] アノテーションをつけることで積極的なインライン化を指示できるはずです。

ただし、Unityエディタ上では無効、MonoおよびIL2CPPでは有効なようです。

このあたり情報源はForumのこのスレッドを読んだだけなので、追って検証していくつもりです。

AllocatingGCMemory (p.68)

Unity 2018.3で追加されたAPIです。ドキュメントはこちら。

Class AllocatingGCMemoryConstraint | Unity Documentation

使用例はこんな感じ。下はsut.UsePrimitive()の中でGCされるメモリ(ヒープ)を確保していたら(Is.Notなので)failするテストです。

[Test]
public void UsePrimitive_isNotAllocatingGCMemory()
{
    Assert.That(() => sut.UsePrimitive(), Is.Not.AllocatingGCMemory());
}

AllocatingGCMemoryについては、お伝え忘れていた重要なトピックがあります。対象のメソッド内(上例ではUsePrimitive())でDebug.Log()を使用していると、その中でメモリ確保が行われるためfailしてしまいます。

性能を要求されるメソッド内でDebug.Log()を使うことは無いとは思うのですが、ご注意ください。

依存オブジェクト (p.70)

依存オブジェクトを持つコンポーネントのテストを行なうためのパターンとして、「Dependency Injectionパターン」や「Test Doubleパターン」があります。

そのパターンを踏まえて、少ないコードで実現させるフレームワークが存在します。確かに便利なのですが、ツール・フレームワークに振り回されることがないように、基本を知っておくことは大事です。

スライドでもご紹介した『xUnit Test Patterns』、Kindle版がセール中のようです。この機会に買って積んでみてはいかがでしょうか。

xUnit Test Patterns: Refactoring Test Code (Addison-Wesley Signature Series (Fowler))

xUnit Test Patterns: Refactoring Test Code (Addison-Wesley Signature Series (Fowler))

テストダブル (p.71)

Test Doubleパターンについては、@goyokiさんのこちらの記事が参考になります。

goyoki.hatenablog.com

もしくは拙著『iOSアプリテスト自動化入門』(絶版)がお手元にあれば、「2.3.5 テストダブル」を参照してください。

YAGNI (p.77)

Wikipediaさんにも載っているYAGNI*1。"You ain't gonna need it"の略で、今必要ないものは考えない・実装しない、という意味で使います。技術者なら言いたいセリフ「こんなこともあろうかと」とは対極にある言葉です。

ja.wikipedia.org

シンプルに「今は必要ないから考慮しない」という意味でも使えますが、テストコードがあることによって後々の仕様変更のコストとリスクが減るために取れる選択肢でもあります。

また、これはセッションでは取り上げなかったのですが、XPやアジャイル開発の文脈で言われる「ドキュメントは書かない」も、本来はテストコードがドキュメントの役割をなす "Test as Documentation" が前提だったりします。

境界値を攻めすぎない (p.80)

前提となる「境界値テスト」の話を端折ってしまったので補足します。

例えば、HPゲージが通常はグリーン、HP 50%以下はイエロー、HP 20%以下はレッド、という色を返すメソッドを例にします。

public static Color GaugeColor(int hp, int maxHp)
{
    var percent = hp / maxHp;
    if (percent <= 20)
    {
        return Color.red;
    }
    if (percent <= 50)
    {
        return Color.yellow;
    }
    return Color.green;
}

このとき、結果が分岐する条件となる入力値である「0〜20%」「21〜50%」「51〜100%」はテスト用語で「同値クラス」と呼ばれます。 そして同値クラス同士の隣接した値、ここでは 20, 21, 50, 51 が「境界値」です。

境界値にはバグが発生しやすいので*2、通常のテストではよく境界値のテストを実施します。しかしゲームの世界では、「やっぱりHP 30%で赤にしよう」といった変更もあるのではないでしょうか。

同じ境界値でも、「HPが0になったら死ぬ」といった境界値は厳密にテストしていいのですが、HPゲージの見た目であれば「10%」「40%」「80%」くらいのゆるい値でテストしておけば、最低限色が変わることは保証できますし、しきい値が調整されてもテストコードを修正する手間もありません。

つまり、境界値を厳密に評価する必要があるテストなのかどうか、という「出力」の見極めが大事ということです。

なお、上例では、引数がhp > maxHpmaxHp=0で呼ばれる可能性を考慮していません。テストを書く過程でこうしたことに気付けるケースもよくあります。

テストコードの構造化 (p.82)

@goyokiさんのスライドが参考になります。

www.slideshare.net

ただし、過度の構造化によってテストコードの可読性が損なわれたり、プロダクトコード側にテストのためのコードが紛れ込んでしまうアンチパターンもありますので注意が必要です。

あくまでも目的は、テストコードが壊れにくくなる、堅牢性 (robustness) のための構造化です。

質疑応答

講演後の質疑応答には10名ほど来てくださり、30分ほどQ&Aさせていただきました。以下、覚えている範囲で*3

Unity Test Runner以外のテストツール・フレームワークは使っていないのか?(Miyamasuとか)

  • ユニットテストとしてはEdit mode testsに絞っているので、Unity Test Runnerのみ
  • もう少し上層の(結合度の)テストは、ツールから分けている

MonoBehaviourのテストをどうしているか(Play mode testsでしかできないのか)

  • MonoBehaviourのテストであっても、Edit mode testsでのテストは可能
  • ただし、責務が分離されていないとテストしづらいのはお話した通り。コンポーネントごとの分離も必要だし、MonoBehaviourと、個々の処理を分離することも必要。例えば、Update()にすべて書くのではなくメソッドを分けていく
  • テスタビリティを上げるためのデザインパターンとしてHumble Object Patternが知られています。これも『xUnit Test Patterns』に書かれています

Humble Object Patternについては、@adarapataさんのスライドも参考になります。

www.slideshare.net

静的解析ツールはあるのか

  • JetBrainsさんが "ReSharper Command Line Tools" を提供しています。インスペクション、重複コードの検出、コードクリーンナップをコマンドラインから実行できます
  • 実行にUnityエディタライセンスもReSharperライセンスも不要ですが、WIndows版のみ

www.jetbrains.com

所感

テストのセッションということでどのくらい来ていただけるか不安でしたが、400人部屋の8割くらい埋まっていたように見えました。ありがとうございました。

セッションのほうは、後半急ぎ足になってしまい反省しています。これでも直前になっていくつかトピックを削ったりTipsにまわしたりしたのですが…。

なお、壇上から客席まで距離があり、かつ照明の関係で暗く、客席の反応がほとんどわからなかったので戸惑いました。冒頭で笑いを取ってペースを掴むのが常套手段なのですが、ウケているのかどうかわからないのはつらかったです。

Unite Tokyo 2019全体について。皆さんTwitter等で書かれていますが、とても素晴らしい会場とホスピタリティで、たいへん満足度の高いカンファレンスでした。スピーカー特典で利用できたVIPルームおよびVIP席もものすごく快適で、来年は登壇しなくてもVIPチケットは確保しようと心に決めました。

最後に。今回のUniteのセッション公募、気づいたのは締め切り4.5h前でした。@nkjzmさんに感謝。

参考文献

xUnit Test Patterns: Refactoring Test Code (Addison-Wesley Signature Series (Fowler))

xUnit Test Patterns: Refactoring Test Code (Addison-Wesley Signature Series (Fowler))

テスト駆動開発

テスト駆動開発

Game Programming Patterns ソフトウェア開発の問題解決メニュー (impress top gear)

Game Programming Patterns ソフトウェア開発の問題解決メニュー (impress top gear)

*1:読みは「ヤグニ」でいいはず。みんなそう言ってるので…

*2:実装以前に、仕様書の段階で「以下」と「未満」があいまいであったり矛盾があったりもありがちなバグです

*3:こんな質問もしたよ!というのがあればTwitter等で教えて下さい