fresh digitable

セミコロンたちが躍動する おいらのコードを 皆さんに 見せたいね

UIテストでDBに入れるデータをテストアプリのassetsとして管理する

モチベーション

  • サーバーとは独立した環境でテストを行えるようにしたかった
  • 必要なデータの種類が結構多いのでテストコードの中でデータを作ってDAOからDBにInsertしまくるというのが面倒くさそうだった
    • OkHttpのMockサーバーから返すレスポンスをテストコードの中にゴリゴリ書くのも同じ理由で面倒くさいと思われる
  • 必要なデータが入っているDBをテスト時にコピーすればいいという話もあるが、どんなデータが入っているか簡単にわかって、かつ簡単にいじったりできたらいいかなと思ってCSVでデータを管理することにした
    • TSVとかYamlとかでもいいかもしれないが、SQLiteにはテーブルの中身を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のSupportSQLiteOpenHelperSupportSQLiteDatabaseクラスを使って行う。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:これのほかにもテーブル名を列挙したりする必要がありますがお好みの方法でどうぞ。