Islands in the byte stream

Technical notes by a software engineer

Ormaのユニットテストをどう書くか、あるいはMockitoは諸刃の剣という話

短い答え

OrmaはRobolectric上でほぼ完全に動作するので、 OrmaDatabase.builder(context).name(null).build() でオンメモリDBを作ってそれを使うDAOをテストすればよい。

長い答え

Orma を使ったユニットテストは、Ormaを使うコードが正しいかどうかをテストするものであるべきです。たとえば、konifar/droidkaigi2016ではSessionDaoというクラスをつくり、Session / Speaker / Category / PlaceなどのデータをSessionというクラスを中心に操作するためのDAO: Data Access Object としています。複雑なモデルの関係があるときは、このようにDAOを作って操作するというのはいいパターンです。

このとき、DAOはロジックを含んでおり、そのロジックはテストが必要です。しかしバックエンドはOrmaなため、Ormaが動く環境を用意するか、pure JVMであればMockito*1などのモックライブラリでAndroid Frameworkをモックする必要があります。

Ormaの場合は、Robolectricの提供するSQLiteDatabaseでほぼ完全に*2動作します。実際、OrmaのすべてのユニットテストはRobolectricでもパスします。そこでDAOのロジックのテストを書くためには、単にオンメモリのOrmaDatabaseを用意してそのうえでDAOを通じた操作を行うことにすればいいでしょう。

かくして、konifar/droidkaigi2016に次のようなテストを追加しました。この場合は、アプリ内で使用している現実のデータ(session_ja.json)をロードしてなんとなくデータが構築されているかどうかをみる、というだけのテストです。開発時にこのテストがあれば、「お気に入り」まわりのロジックはTDDしたほうが開発効率はよかったと思います。

さて、このテストではMockitoを使っていません。たしかにユニットテストではMockitoを使ったほうが書きやすい事があり、とくにプラットフォーム*3が例外を投げるケースはMockitoを使わないとテストが困難なこともあります。DAOのテストも、たとえばディスクフルなどの状態をテストしたいのであればMockitoが必要でしょう。

しかし、Mockitoを使わずに書けるテストをMockitoを使って書くのは避けるべきです。Mockitoはコードの一部しか実行しないため、本当はパスしないような誤ったコードを書くことが簡単にできてしまうからです。また、Mockitoはその性質上ホワイトボックステストになり、メソッドの入出力ではなくメソッドの実装の詳細を固定してしまうことになりがちです。なので、そもそもなるべくMockitoを使わなくてすむように設計し、Mockitoを使わないようにテストを書くほうがいいと思っています。

*1:Java用のモックライブラリ

*2:ほぼというのは、一部のSQL実行時の戻り値がAndroid Frameworkのものと違うことがあるからです。しかし、普通に使う分には問題ありません。

*3:AndroidにせよJVMにせよ