先月くらいまで現在Lemonade Labで開発中のAndroidアプリ単体テスト環境の構築を行った結果、Robolectricを導入することにしたのでその経緯を残しておきます。
どなたかの検討の参考になれば幸いです。
#テスト環境構築初期
最初は王道であるMockitoでテスト環境を導入し、Utilクラスなどテスト作成が簡単なクラスから導入していきました。
この辺りは色々な情報がネットにあるのでそれらを参考にしながらすんなり環境構築できました。
助けてもらったエントリ
http://blog.yohei.org/android-lets-use-mockito/
http://y-anz-m.blogspot.jp/2013/06/android-mockito.html
その他多数
余談:Mockitoをずっと"もっくあいてぃーおー"と読んでいましたが"もひーと"と読むのをこの頃知りました。。。
#PowerMock期
次にテストしようとしたのはHTTP通信周りで、この時Mockitoだけで対応できなかったのがコンストラクタとstaticメソッドの差し替えでした。
具体的にはコンストラクタの中でとあるライブラリのstaticメソッド呼び出しており、このメソッドがUnittestの環境上では例外を発生させるという状況でした。
早速「mockito constructor」とかで検索すると大体「PowerMock使えばいいじゃん」というのが出てきます。
という事でPowerMockを導入し、見事コンストラクタの無視(suppress constructor)やstaticメソッドのモック(mockStatic)を利用してテストを通すことができました。
この時はかなり感動しました。「PowerMock様すげぇよ!」と会社の人に意気揚々と話していたような気がします。
#Coverage Zero問題
ここで幾つかのテストを通すことができるようになったのでコードカバレッジを取得したくなります。(そんなもんテスト環境作るなら最初から取るだろう普通よう、というご指摘はその通りです。すみません。)
Android Studioではカバレッジ計測しながらTest実行できる操作があるようですが、LemonadeではTravisCIでのテスト実施が必要なため、Android studioだけに頼ることはできません。Gradleコマンドでカバレッジレポートを吐き出させる必要がありました。
そこでほぼAndroid開発では標準となっているJacocoによるカバレッジ取得の設定を入れました。
するとどうでしょう。何度テストしてもカバレッジが0としか出ません。
ログやデバッグすると確実にテストクラスは動いています。Gradleのtestタスクでもテストケース数は正しくカウントされており全てパスしたログが出ています。
(なんだJacocoって馬鹿なの?やれやれ)←失礼
と思いながらGoogle先生に色々聞いていると
PowerMock disables EclEmma code coverage
犯人はPowerMock様じゃないですかぁーーーー!
どうやらPowerMockはClassLoaderのあたりでゴニャゴニャやっているらしく、それが原因でJacocoがカバレッジ計測をしようとした時にそれぞれのクラスのハッシュが合わなくてどれがどのクラスかわからなくなってしまう、らしい(と理解した)。
ここからひたすら調べたもの、最終的な結論は
「PowerMock使うとJacocoでカバレッジ取れない。解決策はない。」
でした。
ではこの後何ができるか。
テストケース自体は必要なものなので、カバレッジを取るためにテストケースを減らしては本末転倒です。今あるテストケースはキープする前提で、この結論から取れるアクションは今PowerMockを使っているテストロジックを徐々に"代わりの何か"にリファクタするしかありません。
そこで現れたのがRobolectricでした。
#Robolectric+Powermock期
Robolectricは社内のテストナレッジ共有会議?でN田さんが教えてくれたライブラリでした。
このライブラリもネットにそれなりな情報があるので導入は比較的楽でした。これから導入する場合の注意点にもなりますが、最近ver3.0が出たようで、3.0の情報はあまり多くありません。引っかかる情報はほとんどver2.4だと思うので、その情報をアップグレードガイドと合わせてテスト環境を構築していきました。
http://robolectric.org
https://github.com/robolectric/robolectric/wiki/2.4-to-3.0-Upgrade-Guide
一部のPowerMockを残してRobolectricにテストクラスをリファクタしていきます。
そして、ついにRobolectric+Jacocoでカバレッジを取ることができました!!!!そして今まで無かったエラーも発生しました!くそう!
一部のテストケースでClassLoaderが「そんなクラスロードできねぇ」みたいなことを言っています。
ClassLoader、、、、PowerMock様がここでも影響していました。RoborectricはPowerMockとの同時利用について情報提供しています。これに従うと確かに同時利用できるのですが、完全ではないようです。
今回の場合は、RobolectricはShadowと呼ぶ仕組みによって全てのクラスのダミークラスを作ることができ、これが条件によってはPowerMockの影響を受けたようでした。
(エラーログを残してなかったから詳しく思い出せない、、、)
このとき、ShadowがあるおかげでPowerMockを利用したロジックをリファクタできたしJacocoでカバレッジを取れたという実績ができていたので、この問題をきっかけに完全にPowerMockを利用しないことを決めました。
#Robolectric期 現在
その後PowerMockを使っていたテストロジックをリファクタしていき、現在のAndroidアプリのテスト環境は以下の通りになりました。
- Junit 4.12
- Mockito 1.10.19
- Robolectric 3.0
- Jacoco // plugin経由で利用
この環境で今の所以下のテストは問題なく実行&カバレッジ取得できています。
- Utilクラステスト
- HTTP通信のテスト(HTTP通信はmockで横取りして返してる)
- Activity単体のテスト
今後もServiceやDB周りのテストを追加していく必要がありますが、基本はRobolectricを使い続けるでしょう。
ずいぶん長文になってしまった。
また時間があれば得たネタを書きたいと思います。(Robolectricの実際の使い方とかiOS側とか)