XFLAG Tech Noteに学ぶ、Unity テスト駆動開発

テスト駆動開発 イメージ IT・プログラミング

※ 本記事にはプロモーションが含まれています。

こんにちは! ねこです。

今回は、XFLAG Tech Noteに学ぶ、Unity テスト駆動開発。

ということで、XFLAG Tech Note の MVP アーキテクチャにのっとって、テスト駆動開発を行ってみましたので、その有用性を書いていきます。

Unity で MVP 設計を初めてやった人が、ついでにテストをやってみたら、どうなったのか?

それでは、ご覧ください。

ぜひ、前の記事もお読みください

まずは、こちらの記事を先に読んでいただくと、ご理解いただきやすいです。

[sitecard subtitle=関連記事 url=/unity-mvp-with-xflag-tech-note/ target=]

現在、「学ぶは、まねぶ」の精神のもと、XFLAG Tech Note に書かれてある内容を、可能な限り再現しています。

今回の記事は、その続きとして、テスト駆動開発について書いていきたいと思います。

XFLAG Tech Note とは?

モンストの開発・運用元で有名な XFLAG さんが、技術書だけの同人イベント「技術辞典5」で出展していたものです。

現在は、無料で公開されており、誰でも読むことができるようになっています。

https://career.xflag.com/report/engineer/xflag-tech-note/

今回も、その中の、第 3 章「とある Unity 開発事例」を参考に書かせていただきます。

テスト駆動開発を行おう

テスト駆動開発をするにあたって、やはり、内容をある程度理解しておく必要があります。

テスト駆動開発とは

テスト駆動開発をざっくりと説明しますと、プログラム開発手法の一種です。

進め方を簡単に説明します。

動くプログラムを書く前に、先に要件をテスト&チェックするコードを書きます。

もちろん、そのままだと、テスト結果は、失敗(レッド)になります。

これを、いったんテストがとおるコード(グリーン)を書き、エラーをなくします。

そして、後日ちゃんと作り込み(リファクタリング)を行うのです。

もし、テスト駆動開発の詳細を知りたい方は、こちらの記事が参考になります。

記事みた中で一番、平文で読みやすく、芯をくっているような気がします。

テスト駆動開発って何だろう | DevelopersIO
はじめに モバイルアプリサービス部の中安です。 このたび、テスト駆動開発(Test Driven Development = TDD)の社内読書会を数ヶ月に渡って参加させていただきました。 原題: Test-Driven …

ところで、なぜ、テスト駆動開発をする必要があるんでしょうか?

テスト駆動開発のメリット

テスト駆動開発のメリットを表すと、こんな感じだと思います。

  • ソフトウエアの品質を一定に担保できる
  • 開発後期の開発スピードの低下の軽減
  • 開発者の心理的負担の軽減

私が一番いいと思うのが、開発者の心理的な負担の軽減です。

少なくとも、レビュアーは、テストに書かれている要件については、全て通っているという前提で、レビューできます。

また、開発後期となると、不安になる修正の影響範囲とエンバグ問題。

動くけど、実装がめちゃくちゃなコードは、場合によっては、たった 1 行を変えるだけでも、緊張が走ります。

場合によっては、それだけで結構デバック工数も発生しますよね。

しかし、テストを書いていくことで、そこに書かれた機能要件は満たしていることになり、余計なチェックや心理的な負担も減ります。

また、テストがし辛いクラスや、テスト工数が多すぎるクラスは、設計上、何かしらの問題があります。

そこも、見える化されそうなのも、副次的なメリットかもしれません。

これは、コードを書く側も、見る側も幸せになれそうです。

Unity 開発でテストはいらない?

はい。私もそう思ってました。実際にテストを書くまでは。

私自身、実際に運用にはのせているわけではないので、今後進めるのであれば、様々な問題に対処する必要は出てくると思います。

しかし、それを差し置いても、テスト駆動開発の有用性は、ひしひしと感じました。

ちゃんと稼働すれば、チーム開発を行う上での、エンジニアの心理的な負担が、だいぶん減りそうです。

ビヘイビア(振る舞い)駆動開発と言うべき?

今まで、テスト駆動開発やったことなかった私です。

ですので、「テスト駆動開発」という言葉に、ぴんと来なかったんですよね。

でも、テスト駆動開発 ≒ ビヘイビア(振る舞い)駆動開発と聞いて、納得しました。

簡単に言うと、テストコードに書く内容に、要件にのっとった振る舞いを書く。ということです。

それなら、先に書いておくメリットがわかりやすく伝わります。

例えば、「先にテストを書く」、というより、「先に振る舞いを書く」。と、言った方がしっくり来ますよね。

実際、要件(振る舞い)を先に決めてしまう手法ですので、ビヘイビア(振る舞い)駆動開発というべきかもしれません。

依存性の注入(Dependency injection)

テスト稼働開発をするにあたって、疎結合を維持するために必要とされるテクニックである、依存性の注入。

こちらの説明をしていこうと思います。

ちなみに、XFLAG Tech Note で、Zenject の記載がありましたが、今回は説明しません。

Zenject とは依存性の注入を行いやすくするためのフレームワークです。

依存性の注入 = Zenject ではありませんので、ご注意下さい。

内容は、下で説明したものを、より簡単に行えるものだと思って頂ければと思います。

ずっと疑問だった、この書き方

XFLAG Tech Note の MVP アーキテクチャを、マネするにあたって、メリットのわからなかったこの書き方。

public class SampleRepository : ISampleRepository
{
    private SampleEntity entity;

    public SampleRepository (ILoader loader) // ← ここの部分
    {
        entity = loader.Load<SampleEntity>();
    }
}

public class SamplePresenter : MonoBehaviour
{
    public void Initialize (IApiClient client, ISampleRepository repository) // ← ここの部分
    {

    }
}

なぜ、わざわざ、外側から必要データを渡す必要があるのか?

元の記事では、なぜ、そのようにしているのかが、はっきりと書かれて無かったため、意図を汲み取れてませんでした。

しかし、これこそが、テスト駆動開発に重要な役割をしめしているのです。

依存性の注入(Dependency injection : DI)とは

上記にあるような、クラスに必要なパーツ(クラス)を外部から取得するテクニック。

それを、依存性の注入(Dependency injection、略して DI )といいます。

依存性を注入するという、非合法の香りただようネームですが、れっきとしたソフトウエアの設計(デザイン)パターンです。

ちなみに、Dependency = 依存性 という名前ですが、もとの意味としては、「オブジェクト」に近いです。

ですので、じっさいには「依存オブジェクトの注入」というほうが、より内容を表していると思います。

意味としては、そのクラスの成り立ちに必要で、依存しているオブジェクトを、外部から注入するソフトウエアパターンのことです。

依存性の注入(DI)を行うと、以下のメリットがあります。

  • 結合度の低下によるコンポーネント化の促進
  • 単体テストの効率化
  • 特定のフレームワークへの依存度低下

要するに、オブジェクト同士のつながりが薄く(疎結合に)なるから、パーツ単位で取り出しやすいし、変化にも対応しやすくなるよね。ということです。

テストを書いたら、書きやすい

なぜ、依存性の注入(DI)を行うのか?

それは、テストコードを書いて分かりました。

すごく、テストが書きやすいのです。

例えば、このクラスの GetName ()をテストしたいと思います。

// SampleRepository.cs ※テストしたいクラス

public class SampleRepository
{
    private SampleEntity entity;

    public SampleRepository (ILoader loader)
    {
        entity = loader.Load<SampleEntity>();
    }

    public string GetName ()
    {
        return "名前は、" + entity.name + "です。";
    }
}

上記のコードを、テストするサンプルコードがこちらです。

// TestLoader.cs ※デバッグ用 Loader クラス

public class TestLoader : LoaderBase, ILoader
{
    public TestLoader ()
    {
        SampleEntity sampleEntity = new SampleEntity();
        sampleEntity.name = "サンプル名";

        // オブジェクトを追加
        base.AddObject(sampleEntity);
    }
}

// SampleRepositoryTest.cs ※SampleRepository を、テストするクラス

public class SampleRepositoryTest
{
    SampleRepository repository;

    [SetUp]
    public void InitializeAllTest ()
    {
        var loader = new TestLoader ();
        repository = new SampleRepository (loader);
    }

    [Test]
    public void 名前チェック ()
    {
        Assert.AreEqual (repository.GetName (), "名前は、サンプル名です。");
    }
}

初期化も簡単、テストも簡単なのが、理解できると思います。

しかし、例えば、このクラスをテストしようと思ったらどうでしょうか?

// SampleBadRepository.cs ※テストしたくても出来ないクラス

public class SampleBadRepository
{
    private SampleBadEntity entity;

    public SampleBadRepository ()
    {
        entity = new SampleBadEntity ();
        entity.name = "サンプル名" + Random.Range (0, 100).ToString ();
    }

    public string GetName ()
    {
        return "名前は、" + entity.name + "です。";
    }
}

この、GetName () をテストする場合、受け取るのは、中で隠された文字列になってしまいますので、特定の文字で比較ができません。

Assert.AreEqual (repository.GetName (), /*ここに比較する文字を入れられない*/);

ここから、XFLAG Tech Note に示された書き方だと、なぜテストが書きやすくなったのか、説明していきたいと思います。

なぜテストが書きやすかったのか

なぜ、テストが書きやすかったのか?

それは、当たり前ですが、そのクラスが必要なパーツを、外部からカスタマイズできるようにしたからです。

SampleEntity を外部から変更できるため、
名前の変更ができた。

そのため、データの変更を外部から行うことが可能になっています。

もうひとつは、参照元を上位クラス(インターフェース)から行っていることです。

こちらのテストの部分に絞ってクラスの参照を書くと、このようになります。

共通の ILoader インターフェースで、
やりとりしている。

見ると、共通のインターフェース(ベースクラス)を経由してやりとりしているのが分かりますね。

このように抽象クラス参照しあうのには、メリットがあります。

それは、実体クラスを簡単に変更できることです。

今回の場合は、注入するデータをテスト用データに置き換えました。

だから、テストコードを書くときに、簡単に記述できたのです。

テストの場合は、テスト用の
Loader クラスからデータ受け取り。

テストが書きやすい = 設計がしっかりしている

テストコードを書いてみて思ったのは、テストが書きやすいコードは、設計がしっかりしているとも取れるということ。

もし、仮にテストが書きづらいものが出てきた場合、設計そのものを見直すべきという、注意サインになるかも知れません。

例えば、テストを書いていて、このようなことが起こった場合。

  • 初期化がしにくい … 依存している外部クラスが多い可能性
  • テスト項目が多い … クラスに複数の機能を詰め込み過ぎている可能性
  • テストが書けない … 依存性のあるクラスが隠蔽されている可能性

これらのケースが出てきたら、メンバーと相談するのが良さそうですね。

テスト駆動開発の準備

では、実際にテスト駆動開発をやりたい場合、どうすればいいか。

実際のツールを確認しつつ、見てみましょう。

Test Runner を使おう

Test Runner は Unity 標準のテスト実行環境です。

2019.2 から、Package 化されています。

実際にテストしてみよう

実際にテスト駆動開発を始めてみようとした場合ですが、色々な準備が必要です。

前提として、テストを行う環境を作るのに、Assembly Definition Files の設定をする必要があります。

特に、リリース済みのプロジェクトに関しての Assembly Definition Files 設定の追加は、ほとんどの場合、アセットバンドルの再構築が必要になると思いますので、ご注意。

こちらのサイトは、初期設定のやり方から、詳しく記載されていますので、かなり参考になります。

Unityでテストを書くのが当然になる時代に今から備えよう - Qiita
# 実は既にもうそんな時代なのかも...レガシーコードとはテストのないコードのことであるテスト駆動開発 や 継続的インテグレーション/デリバリー といった言葉が注目される昨今ですが、その中心に…

最後に

もし、XFLAG の資料で設計を行うのであれば、やはり、テスト駆動開発は導入するべきだと思います。

やはりテストも含めての事例であります。

今後同じようにマネされる方がいらっしゃいましたら、これが少しでも参加になれば幸いです。