よりぬき「Androidテスト全書」さん
TL;DR
かねてより執筆中であった「Androidテスト全書」をついに出版しました。
我が国のAndroid史に残るほど良い本に仕上がったと思います。Androidのテストのみにフォーカスした本は日本では類を見ないと思いますが、2018年時点でのUIテストとCI/CDの実践的な知見まで含めると、世界でもここまでまとまった本はないんじゃないでしょうか1。 まだテストがなくこれから増やして行きたい現場や、新人教育に時間を割けない会社など、ぜひこの本を買ってAndroidのテストにチャレンジしてみてください!
それから、これはとても重要なことなのですが、出資者のみなさまで内容にご満足いただけた方はぜひ紹介コードを使ってお知り合いに薦めてあげてください。キャッシュバックがあります2。 これはまだプロジェクトが成立するかどうかも分からない段階から我々を支援してくださったみなさまだけの権利です。みなさまのご支援なしに我々の執筆はありえませんでした。心から感謝します。
さて、このエントリでは自分の担当した章からいくつか見どころを紹介したり、ボツネタを供養したり、思い出話を焚き上げたりしようと思います。お付き合いください。
Overview
購入を検討してくださっている方のためにざっと全体を俯瞰します。 すべてを紹介したいのですが、エントリが長くなりすぎるため自分の担当章のみに留めます。その他の章はそれぞれの著者が解説エントリをかいてくれるでしょう。
第1章「テスト入門」
本書はLocal Unit Test3からUIテストを含むInstrumented Test4まで、単に「テスト」と言っても広大な範囲をカバーしています。 したがって1章ではまず本書における用語の定義と整理をしています。
それからテストを書くことが品質を上げるのみならず、結果的に開発スピードまで上がってしまう話をしています。 スタートアップなんかでありがちなんですが、「スピード優先で」テストを書かない現場もあると思うんですけど、これは個人的には疑問に感じています。 なのでボスが上記のようなことを言い出したら「Androidテスト全書にはテストを書いたほうが早くできるって書いてましたけど」とか言ってやってください。
第2章「ユニットテスト実践入門」
生まれて始めてAndroidのユニットテストを書く人のために、JUnit 4とHamcrestを使ってテストを書く方法をごくごく簡潔に解説しました。 それから、AssertJを使ったアサーションも簡単に解説しています。なぜTruthやAssertK、Expektじゃないんだ?というような話にも触れています。
2章の目玉は「テストダブル」です。次のようなテスト対象クラスを用意して、「スタブ」「モック」「スパイ」を自作しながらそれぞれの使い分けを解説しています。
class WeatherForecast { val satellite = Satellite() fun shouldBringUmbrella(): Boolean { val weather = satellite.getWeather() return when (weather) { Weather.SUNNY, Weather.CLOUDY -> false Weather.RAINY -> true } } }
自作したテストダブルは結局Mockitoで置き換えるのですが、どうしてこれをわざわざ自作してまでみっちり解説したかというと、テストダブルの正しい定義と使い方を身につけていれば仮にMockitoが廃れた世界線でもこの知識は活かせると信じたからです。
本章の草案ではテストダブルの説明そのものも意図的にMockitoの実装に寄せていたのですが、アーリーアクセスのKazuCocoa氏のご指摘を受けてxUnit Patterns.comのTest Doubleに準拠するように書き直し、サンプルコードも全面的に改めました。とても良い章になったと思います。
第3章「ユニットテスト応用編」
前章が意図的にAndroidフレームワークに依存しないモジュールのユニットテストだったのに対し、本章ではまずAndroidフレームワークのコードを利用したモジュールのテスト方法を解説しました。それから、これまでの内容を総合して「現場で使えるテクニック」をいくつか厳選して収録しました。 普段開発していて自分の中に溜め込んだAndroidのテストの知見というのはそれこそ大小さまざまに数えきれないほどあり、すべてを解説するのは無理というものです。したがって自分でうんうんうなったり、Twitterでアンケートを実施したりしてテーマを絞りました。そのうち2つを紹介します。
ひとつは非同期処理のテストです。ExecutorService
を使った次のようなモジュールのユニットテストの方法を解説しました。
class StringFetcher { fun fetch(): String { Thread.sleep(1000L) return "foo" } } class AsyncStringFetcher(val fetcher: StringFetcher) { val executor: ExecutorService = Executors.newCachedThreadPool() fun fetchAsync(onSuccess: (value: String) -> Unit, onFailure: (error: Throwable) -> Unit) { executor.submit { try { val value = fetcher.fetch() onSuccess(value) } catch (error: Throwable) { onFailure(error) } } } }
ここでは CountDownLatch
を使った方法に留まらず、もう一歩先に進めたアプローチも紹介しています。
非同期処理ライブラリも栄枯盛衰が激しく、何かひとつに絞って解説しても廃れたらどうしようもないというのは明白です。従ってここでも「非同期処理はどうしてテストがしづらくて、どういう考え方を身につけていればこの先新しい非同期処理ライブラリが出てきても工夫してテストが書けるのか?」ということを意識しながら書きました。詳しくは本編で!
もうひとつは「テストのないプロジェクトにテストを導入する」と題して、テストコードがない現場でいかにも目にするような悲しみに包まれたJavaのコードを題材にして、少しずつ改良しながらテストを書いていく節です。次のようなクラスを少しずつ変えながらテストを書いていきます。
public class LegacyCode { private LocalDataFetcher localDataFetcher = new LocalDataFetcher(); private RemoteDataFetcher remoteDataFetcher = new RemoteDataFetcher(); void loadData(String param, Context context, Callback<OldData> callback) { OldData result; if (NetworkUtils.isOnline(context)) { result = remoteDataFetcher.fetch(param); } else { result = localDataFetcher.fetch(param); } callback.onSuccess(result); } }
どうでしょうか、ワクワク(げっそり)しませんか?こちらもぜひ本編を読んでいただきたいです。
その他の章は他の著者の方に譲るとして、本書は特に日本語でまとまった知見の少ないUIテストのノウハウががっつり3章に渡って収録されているかなり貴重な書籍です。 ぜひお求めいただけると嬉しいです。
この本に書かなかったこと
先程も書きましたが、この本を書くにあたって収録しきれなかったネタは山程あります。 アサーションにしてもモックにしても応用編にしても、構想段階や執筆段階で倍ぐらいあったものを削りに削っていまの形になりました。著者としてはコンパクトな文量ですべてを収められなかった悔しさというのはどうしてもあります。
例えば、 AsyncTask
や AsyncTaskLoader
のテスト方法は敢えて収録しませんでした。理由は本当にいくつもあるのですが、これらのモジュールがかつて程は用いられてないことや、 Activity
と密結合していてどの章のどの節で解説すべきなのか、それも Robolectric
を使って Local Unit Test
で無理やり解説すべきなのか、 IdlingResource
を駆使して Instrumented Teset
として解説すべきなのか…ね、悩ましいでしょう。それよりかは、非同期処理の本質に迫るような解説にするに留めました。
それから非同期処理でもちゃんとエラーを通知できるJUnit Rulesなんていうのも最初書いてたんですが、これもカットしました。ここで供養しましょうか。
class MultiThreadFail : TestRule { val errorRef = AtomicReference<AssertionError>() fun fail(message: String) { errorRef.set(AssertionError(message)) } override fun apply(base: Statement?, description: Description?): Statement { return object : Statement() { @Throws(Throwable::class) override fun evaluate() { errorRef.set(null) base?.evaluate() errorRef.get()?.let { throw it } } } } } class TestClass { val onSuccess: (value: String) -> Unit = { _ -> rule.fail("ERROR") } val onFailue: (error: Throwable) -> Unit = { _ -> rule.fail("ERROR") } @get:Rule val rule = MultiThreadFail() /* ... */ }
こんな風にしておけば非同期処理のコールバックの中からでもアサーションを失敗させられます。 ただ、テストケースからルールにアクセスするのはキモイとか色々査読コメントをいただき、収録しませんでした。
他にも、実際に解説したライブラリひとつ取っても、便利ではあるんだけどマニアックすぎるメソッドは解説を断念したものも多数あります。
Parcelable
のテストも ThreeTenABP
のテストも SharedPreferences
のテストもみんなみーんな書きたかった。本を書くというのは本当に難しい。
思い出話
さて、この本は元々僕が書きたい!と言って書くことになりました。 なので他に僕よりも優れた著者陣が多数参加してくださったにも関わらず、筆頭著者として本当に色々好き勝手やらせていただきました。 たとえば、ある著者とのやり取りでは「めっちゃ内容は良いんですが前戯が長いのでヌける部分を手っ取り早くください!」みたいなクソ失礼な注文を付けたりしたのですが、最終的に素晴らしい玉稿5をあげてくださいました。
優秀な著者のみなさま、編集長のひつじさんとPEAKSの永野さんには感謝してもしきれません。 特にひつじさんの編集は本当にびっくりしました。空間認識能力が違いすぎるとでも言うのでしょうか、他人の書いた膨大な長さの原稿を一瞬で全体像を把握して適切な長さに圧縮する力は脱帽としか言いようがありません。凄いものを見ました。本当にありがとうございます。何もかも、とても良い思い出になりました。
そして何よりも、プロジェクトをサポートしてくださったみなさんに厚く御礼申し上げます! このエントリを読んで興味を持ってくださった方、きっと損はさせません!買ってください!宜しくお願いいたします🙏
最後に、やっぱり妻と子供にはお礼を言わないわけにはいきません。どれだけの休日を犠牲にしたかわかりません。 異国の地に引っ越してきたばかりで孤独と不安だったことでしょう。とても申し訳ない。
4歳の長女とはいつも一緒に寝ていたのだが、僕が執筆のために毎夜書斎にこもるようになってからは、書斎にあるゲストベッドに毎晩長女も泊まりに来るようになりました。毎晩しばらく一緒にベッドに入ってやって、おしゃべりして、背中をポリポリして、足の指をポリポリもみもみ6してやって、おでこにキスをしてプーさんを抱っこさせ、そのままパパは原稿に向かうというスタイルが確立されました。太陽のように美しい娘。しばらく余裕ができるので一緒に寝てやろうと思います。
なお、家族会議の結果「バンドマンの彼女が歌詞にされるみたいで嫌だ」ということで本のあとがきに「妻の○○○に感謝します」みたいな文言は入れないことが閣議決定されました。代わりにここに記すものであります。ありがとう。
結びに
DroidKaigi 2019にはプロポーザルが採択されてもしなくても日本に一時帰国して参加予定です! 「著者です」Tシャツを着て参加しているので声を掛けてください!
suzuri.jpじゃあの!