Androidエンジニアの村田です。チームメンバーが増えてきてますますテストコードの必要性を感じています。 ということでAndroidアプリの開発でユニットテストを導入しました。 挫折しないでユニットテストを始めるための3つのポイントを紹介していきたいと思います。
なぜ始めたか?
まずはじめにそもそもなぜ始めたのかの理由を3つ紹介します。
プロダクトの品質を仕組みとして保ちたかった
コードレビューはしてるが、あくまで人の目の確認となり抜け漏れが生じてしまいます。クラスやメソッド単位での品質は仕組みとして担保したいと思いました。
チーム内でクラス、メソッドの仕様についての共通認識を高めたかった
そのコードは何を受け取ったらエラーとするのか?テストコードを書けばわかりやすくなると思います。自分のためにもチームメイトのためにもなると思います。
仕様変更によるバグ発生の回避
たくさんの機能をハイスピードで開発、改善、廃止などしていくうちにちょっとしたコードの変更が思わぬバグを生むことがあります。 予めテストが書かれていればコードを微妙に変えても異変にすぐ気がつけると思います。 このように始める理由は結構ありきたりな理由でした。 それでは本題に入っていきます。挫折しないようにユニットテストを始めるための3つのポイントを紹介していきます。 1.現状分析&シンプルな導入 2.自分のコードは完璧じゃないと疑う 3.メリットを明確に納得する
現状分析&シンプルな導入
始めるにあたってアプリケーションのどの部分から始めるかを決めるのは大事です。 どこから始めるかをしっかり絞って優先付けをしていくことで迷わずにユニットテストを導入することができます。 iQONではクラス単位でざっくりどういう役割があるのかを把握することから始めました。
iQONのAndroidアプリのクラス数
Activityクラスの数:73 Fragmentクラスの数:32 Adapterクラスの数:15 View関連の共通クラス数:30 モデルなどのデータ処理系のクラス数:45 iQONの機能のほとんどはバックエンドのAPIサーバ+画像サーバとhttp通信をすることで実現しています。 この処理を担当するのが1番下のデータ処理系のクラス(45クラス)になります。
ユーザから見えにくい処理はユニットテストにうってつけ
ViewとかActivityとか表面に見える部分のバグは実機テストするときに人の目でも検知できますが、データ処理系は目視では検出しにくく、本当にその挙動が正しいのか正確に判断できないことがあります。こういった処理はユニットテストでバグを検知するにはうってつけだと思います。
処理の重要度で優先付け
データ処理関連はアニメーションなどの表現に関する処理に比べるとシビアなのでまずはテストすべきだと思います。どんなにすごいアニメーションで表現しても肝心の処理がうまく行ってなければ本末転倒です。
導入は簡単に、シンプルなものから始めてまずは文化づくりから
ユニットテストを始めるときに、あんまりいろんなことを最初からやってはいけません。気持ちの問題ですが、最初からあれこれやり過ぎて導入、開始までで疲れてしまって挫折してしまうこともあるかと思います。まずは簡単な仕組みから始めてテストをかく習慣、文化を作ります。 実際に導入するテストツールはいろいろ検討しましたが、Javaで書かれたコードをユニットテストするのにもっともメジャーでシンプルなJUnitを採用しました。弊社ではAndroidStudioを使用しているので、実際かなり簡単に始めることが出来ました。 具体的な導入については次回以降のエントリーで紹介していきたいと思います。 という3つの理由からJUnitによるユニットテストを開始しました。
自分のコードは完璧じゃないと疑う
ちゃんと動いていると信じていたメソッドも意外とまともに動いてない
テストコードを書くとなると容赦ないパラメータをメソッドに投げるわけですが、変な値を投げると意外とうまく動いていないことがあります。
設計を見直す良いチャンス
テストコードを書き始めると、「あれ?テスト書きにくいクラスがあるな」と思うことが出てきます。これはテストが書きにくいクラスなのではなく、設計があんまりよくないクラスの場合が多いと思います。ちょっとiQONであった実例を紹介します。
public void setJSON(JSONObject json) { try { if (!json.isNull("category")) { String category = json.getString("category"); if (2 < category.length()) { // 省略(categoryを適当に変更する処理) this.category = category; } else { // 省略(categoryを適当に変更する処理) this.category = category; } } else { // 省略 } // 省略(他のインスタンス変数に対する処理がこのあとも多数続く) } catch (JSONException e) { e.printStackTrace(); } }
バックエンドのAPIから返ってきたJSONのレスポンスを上のようなメソッドに渡して、 インスタンス変数の値を設定する処理がありました。 このメソッドは300行近くに達し、とても見通しの良いコードではなかったです。本来ならsetterを実装してそこで値の検証をして、インスタンス変数に値を設定するのがいいと思います。
public void setJSON(JSONObject json) { try { if (!json.isNull("category")) { String category = json.getString("category"); setCategory(category) } else { // 省略 } // 省略(他のインスタンス変数に対する処理がこのあとも多数続く) } catch (JSONException e) { e.printStackTrace(); } } public void setCategory(String category) { try { if (2 < category.length()) { // 省略(categoryを適当に変更する処理) this.category = category; } else { // 省略(categoryを適当に変更する処理) this.category = category; } } catch (JSONException e) { e.printStackTrace(); } }
このようすればsetCategoryメソッドでいろんな値を設定してインスタンス変数の変更に関するテストがかけるようになります。 ちょっと設計を改善すれば、テストが書けるようになります。逆にテストを書こうと思ったので設計の見直しができたと思います。(今回の例だともともと処理を切り分けたいと思っていた気持ちがより強くなりました)
メリットを明確に
なぜテストをやるのか?そのメリットを明確にしてやるべきだと思います。 私自身もテストを書くのに挫折したことがありましたが、挫折する理由の一つとしてメリットを納得できてなかったからだと感じています。 自分なりに納得したポイントをざっくり上げたいと思います。
1. コードの品質があがる
言わずもがな、テストをかけばその分コードの品質は上がります。これは保守するコストを下げるメリットがありますね。
2. テストを意識した、きれいな設計を保てる
きれいな設計を保てるということは保守するコストもそれなりに下がっていきます。これもメリットですね。
3. テストが用意されているので思い切った開発ができるようになる
テストが用意されているので、ちょっと処理の内容を変えてもテストを流せばおかしいところはすぐに検出できます。 この処理を変えたらあちこちに影響ないだろうか?と開発時に迷う気持ちを抑えられると思います。不安ならテストを流せばいいわけです。 あれこれ影響範囲を調べたりする開発時のコストを下げることもできそうです。これもメリットと言えるでしょう。
4. テストコードが仕様書になる
どんな値を受け取って、どんな場合にエラーを返すか。みたいな仕様はテストコードを見れば一目瞭然なので、仕様書の代わりになります。ドキュメントとして細かい仕様をまとめるのも大事ですが、テストコードを代わりにしてみるといろいろ無駄が省けるかもしれません。
まとめ
現状分析&導入はシンプルに
現状のアプリの中でテストを書き始められる箇所がどこか見極める。大掛かりなしくみを最初からやろうとしないで小さなことから始めてみる。最初から飛ばし過ぎると導入の時点で疲弊して続きません。
自分のコードを疑う
自分が書いたコードは完璧じゃないと疑う。テストコードを書いてみて初めて気がつくことはよくあります。
メリットを明確に
メリットを明確にして、きちんと納得して始める。 次回は実際の導入の紹介やTravisCIとの連携などを紹介していきたいと思います。 しばらくテスト関連のエントリーが続きますが、楽しみにして頂けたらと思います。
さいごに
iQONではエンジニアを募集しています。一緒に世界を目指すAndroidアプリの開発をしませんか? 【Android】世界で勝ちたいAndroidアプリエンジニア募集 情報交換なども随時していきたいと思いますので勉強会などの開催、参加についても気軽に相談頂ければと思います。