【Rails】DatabaseCleaner導入でテストが遅くなった話

現在仕事のrailsプロジェクトで、feature specの導入のところでdatabase_cleanerの設定でいろいろ時間を費やしたため、時間を費やした箇所についてまとめておこうと思います。 その時のRailsバージョン/Rubyバージョン:3.2.11/1.9.3。古い!

DatabaseCleanerとは

github.com databaseのお掃除をしてくれるgem。主にrspecやminitestの際に、「テストが終わったらテストデータを全部消したい」みたいなモチベーションで使われる。

使い方

簡潔にまとまっている記事がたくさんあるので省略します。

【Ruby on Rails】Database Cleanerによるテストデータの消去 | Developers.IO
RSpec + database_cleaner で永続的なマスターデータを扱う - 彼女からは、おいちゃんと呼ばれています

困ったところ(今回のメイン)

DatabaseCleanerを使いはじめると、急にテストが遅くなる

rspecである程度テストが書いてあるプロジェクトにDatabaseCleanerを入れてみたのですがテストが急に遅くなりました。 今回実施した導入方法は以下になります。
* Gemfile

gem 'database_cleaner'
  • spec/spec_helper.rb
RSpec.configure do |config|

  # これより前の設定は省略...
 
  # config.use_transactional_fixturesはコメントアウト
  # config.use_transactional_fixtures = true
  
  config.before(:suite) do
    DatabaseCleaner.strategy = :truncation
  end

  config.before(:each) do
    DatabaseCleaner.start
  end

  config.after(:each) do
    DatabaseCleaner.clean
  end

log/test.logをみていると、同じクエリログが何回も出ていました。よくよく調べてみるとspec/fixturesディレクトリで定義しているfixtureをテストのたびにDBにinsertしてtruncateしているようでした。DatabaseCleanerを入れるまでは以下の設定が入っていたのでfixtureは一番最初にinsertして、テスト毎に入れたり消したりされることはありません。DBのクリーンはrspecに入っている機能で行う模様です。

config.use_transactional_fixtures = true
config.global_fixtures = :all

解決方法

spec/spec_helper.rbのDatabaseCleanerのstrategyをtransactionにしました。

  # これより前の設定は省略

  # config.use_transactional_fixtures = true
  # config.global_fixtures = :all
  
  config.before(:suite) do
    DatabaseCleaner.clean_with(:truncation)
    dir = "#{Rails.root}/spec/fixtures"
    Dir["#{dir}/*.yml"].map { |f| f[(dir.size + 1)..-5] }.each do |fixture_file|
      ActiveRecord::Fixtures.create_fixtures(dir, fixture_file)
    end
  end

  config.before(:each) do
    DatabaseCleaner.strategy = :transaction
  end

  config.before(:each) do
    DatabaseCleaner.start
  end

  config.after(:each) do
    DatabaseCleaner.clean
  end

要約すると、①fixtureは自力で入れる。②DatabaseCleanerのstrategyをtransactionにする。
です。rspecのtransactionを使うとfeature specは上手く動かない場合があるのですが、DatabaseCleanerの場合はひとまず大丈夫そうでした。
テストの速度も以前通りになりました。

その他

他のプロジェクト(Rails4系)でtruncate→transactionの設定を試したところ、残念ながらテストは早くならず逆に遅くなってしまいました。
テーブル数、データ量等の違いによって結果が異なりそうなので、ベストな設定はこれ!というものはなさそう。実は、パーフェクトRuby on Railsにはfeature spec用の設定としてはまた若干違った設定が書かれています。
他にもいろいろ設定があるようで、DatabaseCleaner.strategy = :truncation:exceptというオプションを与えてtruncateするテーブルを制限する方法もあるようです。

悩んでるポイントはみんな同じ!?「Rubyistのためのテストコード相談会」の質疑応答まとめ - give IT a try

database_cleaner transactionの設定 - Qiita

まとめ

rspecやDatabaseCleanerの設定にはいろいろあるので、テストの量やDBの状態に応じてベストな設定を模索していきましょう。
もっと良い設定があればぜひ教えてほしいです!