
自分で遅くしたRSpecを自分で改善した話
反省兼、備忘録兼、社内メンバーへの共有資料です
TL;DR
実装した機能と一緒に作成したデータの抽出を確認するRSpecが激遅だったので、before(:all) を使ってRSpecの実行時間を元々の時間まで戻しました。(どう見てもマッチポンプです。本当にありがとうございました。)
環境
・ruby (2.6.5)
・rails (5.0.7.2)
・rspec-rails (3.8.2)
・factory_bot (5.0.2)
実際どれぐらい遅くなったのか
実装前(rspec 23m 24s)
実装後(rspec 1h 16m 16s)
はい、実行時間が3倍以上になりました。
遅くなった原因
大量のデータをテスト毎に作成してたのが原因。
設定でテスト毎にトランザクションとロールバックを行って、他のテストに影響が出ないようにしていたため、テスト毎に同一データを作成していました。
一番多いテストで1,152件ものデータを作成、条件を微妙に変えて12回、その中で3回テストを行っていたので計41,472件のデータを作成していました。
対策の検討
同一のテストデータを使用するのにテスト毎に作成するのがそもそも微妙。
特定のブロック内で初回にデータ作成してあとはデータの抽出確認するだけで良い。(ブロック外には影響が出ないような形であること)
なんかいい方法ないかなーと有識者に相談したところ、before(:all) あるよと教えてもらいました。
before(:all) とは
こちらのドキュメントによれば
before(:all)で作成されたデータはロールバックされません。
before(:all) フックは、トランザクションがオープンされる前に呼び出されます。
ズバリやりたい事そのものでした。
before(:all)に差し替える
※名称を変えたり、一部データを省略して記載してます
修正前
let!(:fiscal_years) { create_list(:fiscal_year, 12) }
let!(:departments) { create_list(:department, 4) }
before(:each) do
fiscal_years.each do |fiscal_year|
departments.each do |department|
create_list(:employee, 12, department: department) do |employee|
create(:medical_checkup, employee: employee, fiscal_year: fiscal_year)
end
end
end
end
修正後
let!(:fiscal_years) { FiscalYear.order(:id) }
let!(:departments) { Department.order(:id) }
before(:all) do
create_list(:fiscal_year, 12) do |fiscal_year|
create_list(:department, 4) do |department|
create_list(:employee, 12, department: department) do |employee|
create(:medical_checkup, employee: employee, fiscal_year: fiscal_year)
end
end
end
end
after(:all) do
DatabaseCleaner.clean_with(:truncation)
end
上記のように修正を行いました。
ですが2点の注意点があります。
1つは、after(:all)で必ず下記のようにデータを削除する事。
after(:all) do
DatabaseCleaner.clean_with(:truncation)
end
この処理を書いておかないと、ブロック内のテストが終わって、次のテストに進んでもテストDBにデータが残ったままになるので、他のテストにも影響が及んでしまいます。
2つめは、元々 let で作成されていたデータも before(:all) 内で作成するようにしました。
これには理由があって、before(:all) で動作する時点では let にはアクセス出来ないので、同じく before(:all) 内で作成するようにしました。
でもexpectで fiscal_years との比較を行いたいので、
let!(:fiscal_years) { FiscalYear.order(:id) }
let!(:departments) { Department.order(:id) }
という感じで let 内ではデータ作成ではなく参照を行うようにしました。
結果どの程度改善されたのか
修正前(rspec 1h 16m 16s)
修正後(rspec 18m 56s)
早くなりました!!!!
まとめ
お気づきの方がおられると思いますが、実装前の23m 24sよりも早くなってます。これは before(:all) に修正する以外にもテストの見直し等を行ってデータの作成件数を減らした等の要素もあって以前よりも早くなりました。
テストでのデータ作成部分が占める時間の割合を改めて実感しつつ、これから作成していくテストも気をつけて実装していきたいと思います。