PIYO - Tech & Life -

seed_dumpを使ってRailsのテストデータの読み込みを高速化した

Rails RSpec

そこそこ複雑なデータを持たないとフィーチャテストを動かせないようなプロジェクトがありまして、きちんとしたデータを作るためにはCSVファイルからデータを取り込んでリレーション作るという必要がありました。

テストケースによってはこれを使わないこともありますが、全体の40%程度はこのデータがないとうまくいかないので、それぞれのテストの前に取り込み処理が入るります。これにより全体を流すのにめちゃくちゃ時間がかかっていました。

といっても数十分程度なので多少目をつぶってこれまではやり過ごしてきました。

このたび、重い腰をあげて対応策を考えましたので記しておきます。といっても↓の記事を大いに参考にして実施したので、こちらの記事に感謝します。

実際の DB データをテストで使用する - milk1000cc
Rails で開発していると、実際に使っている DB のデータを、テストで使用したい場合があります。 そのような場合、seed_dump と activerecord-import を使ってうまく実現することができました。 Gemfile group :development do gem 'seed_dump' end group :test do gem 'activerecord-import' end 使わない環境では、gem を読み込まないようにします。 script/dump_models.rb DUMPED_MODELS = [Site, Category, SubCategor…

こちらの記事で紹介されているのはseed_dump gemを使ってDBのデータをactiverecord-importの形式で書き出し、テストの必要な箇所で書き出したRubyのコードを実行するというとやり方です。大筋はこのやり方をとります。

細かいところで工夫した箇所があるのでその点について紹介します。

必要なデータだけのDBをつくる

元々CSVインポート処理で取り込まれていたデータはごく一部なので、開発用に使っているデータが全て必要なわけではありません。 そこで新しいデータベースを用意してテスト用のデータのみを取り込んだあとで、そのデータベースに対してseed_dumpにより書き出せばよいと考えました。

ちなみに、テストコードでのインポート処理は、

MyTestData.import!

みたいに、ユーティリティクラスのメソッドを呼ぶだけとなっています。これを踏まえた上で、一時的なデータベースを作るために以下のようなことをしました。

  1. database.ymlを開き、developmentのdatabase名をseed_tmp_devみたいな適当な名前に変更する
  2. rails db:createなどでデータベースを作成
  3. rails db:migrateでマイグレーション
  4. rails consoleでコンソールに入る
  5. MyTestData.import!を実行

これで必要なデータだけが入った一時的なデータベースが完成しました。

seed_dump時の工夫

先程紹介した記事ではRubyスクリプトによりデータを出力していました。seed_dumpにはrakeタスクがありますので、今回はそちらを使いました。データベースにあるデータを全て書き出してくれればいいので、モデルの絞り込みは特にしませんでした。

出力用のコマンドは次のようになりました。

% rake  db:seed:dump EXCLUDE=created_at,updated_at IMPORT=true FILE=spec/fixtures/seed.rb

記事と同様に以下のオプションを指定しています。

  • EXCLUDE 除外カラム。関連のためidは出力対象です。
  • IMPORT activerecord-import対応の形式で書き出します。
  • FILE 出力先のファイルパスを指定しています。

さて、このファイルを元記事のように:seedタグ有りのときに読み込むようにして実施したところ、関連の外部キーでエラーになりました。

出力されたファイルを見てみると、アルファベット順に処理が書かれています。

# 例
Child.import(...)
Parent.import(...)

ここでChildが外部キーparent_idあたりでParentを参照していたりするとChildをimportするときにエラーとなります。本来は以下のような順番で取り込まれるべきです。

# こうなっているべき
Parent.import(...)
Child.import(...)

今回のプロジェクトの場合、関連するモデルが15程度あったのでそれぞれ親子関係をたどって出力されたファイルのコードを並び替える必要がありそうでした。

ここでseed_dumpでは対象のモデルを指定するやり方があることを思い出しました。rakeタスクの場合はMODELS="Parent, Child"のように指定可能です。

% rake  db:seed:dump MODELS="Parent, Child" EXCLUDE=created_at,updated_at IMPORT=true FILE=spec/fixtures/seed.rb

これを実行したところMODELSに指定した順番でコードが出力されており、望み通りにインポートできそうなところまでいきました。

バリデーションの回避

一部のモデルではデータベースのカラムの情報だけでは保存できないようなバリデーションがかかっているモデルがありました。

そのため、出力されたファイルのParent.import(\[...\])のところにvalidate: falseオプションをつける必要がありました。

# before
Parent.import([...])

# after
Parent.import([...], validate: false)

この変更はエディタのマルチカーソル機能でがんばって変更しましたw

ここまででようやく取り込めるようになりました。

結果

対象のテストを流してみたらこんな結果となりました。

変更前

Finished in 41 minutes 15 seconds (files took 11.15 seconds to load)

変更後

Finished in 17 minutes 25 seconds (files took 11.13 seconds to load)

41分→17分ということで、40%程度に短縮できました!