UIテストでDBに入れるデータをテストアプリのassetsとして管理する
モチベーション
- サーバーとは独立した環境でテストを行えるようにしたかった
- 必要なデータの種類が結構多いのでテストコードの中でデータを作ってDAOからDBにInsertしまくるというのが面倒くさそうだった
- OkHttpのMockサーバーから返すレスポンスをテストコードの中にゴリゴリ書くのも同じ理由で面倒くさいと思われる
- 必要なデータが入っているDBをテスト時にコピーすればいいという話もあるが、どんなデータが入っているか簡単にわかって、かつ簡単にいじったりできたらいいかなと思ってCSVでデータを管理することにした
前提
- サーバーからとってきたデータをDBにキャッシュし、DBにあるデータを信頼できる唯一のソースとする。
- DB: Room 2.1.0
- DI: Koin 2.0
- UIテスト: Espresso 3.1.0
どうやって?
まずはDBからデータを吸い出す。sqlite3はDBファイルからテーブルのデータをCSVでダンプさせることができる。INSERT INTO...
の形式でも吐き出せるので読みにくくならなさそうならこれでもいいかもしれない。*1
$ sqlite3 db_file -cmd ".headers on" \ # カラム名を最初の行に追加する ".mode csv" \ ".output table.csv" \ "SELECT * FROM table"
src/androidTest
の直下にassets
ディレクトリを作って、ここに上のコマンドで出力したCSVファイルを置くと、それがそのままテストアプリのAPKファイルの中に入る。次のような感じでテストアプリのContext
からアクセスできるようになる。これを使ってテストケースの最初にデータが読み込まれるようにする。
val testAssets = InstrumentationRegistry.getInstrumentation().context.assets
モジュール分割のやり方によっては、UIテストを実装するモジュールが、Roomやそれを使って実装しているDaoやEntityのクラスとかを知らなかったりする。依存させてもいいかもしれないが、なんとなくテスト対象へのクラスに依存しないようにしておきたかったので、INSERT INTO table_name VALUES(...)
を使ってCSVの行をSQLに直接流し込むことにした。
SQLiteの操作はandroidxのSupportSQLiteOpenHelper
やSupportSQLiteDatabase
クラスを使って行う。RoomDatabase
からSupportSQLiteOpenHelper
を取得できるので、DIか何かを使ってテストクラスに渡せるようにしておく。defer_foreign_key
PRAGMAをtrueにしてから流し込むとはかどる。
openHelper.writableDatabase.run { execSQL("PRAGMA defer_foreign_keys = true;") beginTransaction() try { tableFiles.forEach { file -> val tableName = file.nameWithoutExtension testAssets.open(file.path).reader().useLines { lines -> lines.filter { /* スキップしたい行を取り除いたりとか */ } .map { /* データのサニタイズとか */ } .forEach { line -> execSQL("INSERT INTO `$tableName` VALUES($line);") } } } setTransactionSuccessful() // 忘れずに呼ぼうね(1敗) } finally { endTransaction() execSQL("PRAGMA defer_foreign_keys = false;") } }
DBのデータだけでなく、端末で行うテストで使うリソースファイルなら大体のことに応用できるんじゃないかと思うのでうまくいったら教えてください。
*1:これのほかにもテーブル名を列挙したりする必要がありますがお好みの方法でどうぞ。