Railsのrspecが遅いので対応した結果wwwwwwwwwwwwww
rspecで書いているテストが遅い。CPUもめちゃ回る。めちゃ回ってあのスピードか。
rspec + guard + spork(spring試したい)で自動テストを回しているので、あまりテストのパフォーマンスが開発に影響することはなかったんだけど、けっこう大きいmodelの修正とかになると、1modelのテスト結果がでるまでも数秒かかってしまっていて、いよいよ耐えられなくなってきた。
ので、調査対応したときのメモ。
遅いテストを調べる
rspec2系なら--profileというオプションをつければ遅いテストワースト10がテスト終了後に表示される。毎回表示されてもいいと思うので.rspecファイルに書いておく。
.rspec
--colour --drb --profile ←追加
ちなみに--profile 15とか数字を引数につけるとワースト10がワーストNになる。
遅いテストの内訳を調べる
Top 10 slowest examples (22.01 seconds, 13.3% of total time): Hoge scope:recent return 10 rows 4.99 seconds ./spec/models/hoge_spec.rb:152 Api::HogesController GET recent data should respond with 10 rows 4.17 seconds ./spec/controllers/api/hoges_controller_spec.rb:56 Hoge fetch_hoge_image_url should fetch the first image out of hoge points 3.18 seconds ./spec/models/hoge_fuga_spec.rb:109 ~snip~ Api::PiyoController PUT by invalid user should return status 404 1.28 seconds ./spec/controllers/api/piyos_controller_spec.rb:36
一番遅いテストのコードは以下の様な感じ。
describe "scope:recent" do context "return 10 rows" do before do 11.times do FactoryGirl.create(:hoge, status: HogeStatus::PUBLIC) end end specify { Hoge.recent(0).count.should be(10) } end ~snip~
まあどう考えてもFactoryGirl.createを11回ループしているところが重いわけだ。
バルク(バッチ)インサートに修正する
こういう場合の常套手段であるbulk insertに修正してみる。毎回createすることによって発生するDBとのコネクションなどのコストを下げることによりパフォーマンスアップを図る。
Railsでbulk insertをする場合activerecord-importというgemを利用するのがよさそうだ。
Gemfile
~snip~ gem "activerecord-import", "~> 0.3.1" ~snip~
これに合わせてテストコードも修正した。
describe "scope:recent" do context "return 10 rows" do before do hoges = [] 11.times do hoges << FactoryGirl.build(:hoge, status: HogeStatus::PUBLIC) end Hoge.import hoges end specify { Hoge.recent(0).count.should be(10) } end ~snip~
FactoryGirl.createのかわりにbuildにして一回のDBコネクションかつ1回のSQLで11件のデータを入れるように変えた。
期待して結果を待つ。
Top 2 slowest examples (5.52 seconds, 100.0% of total time): Hoge scope:recent return 10 rows 5.13 seconds ./spec/models/hoge_spec.rb:162 Hoge scope:recent return only public hoges 0.39086 seconds ./spec/models/hoge_spec.rb:194
ぜんぜん改善しねー。カッコワリー。
FactoryGirlのテストデータを見直す
hogeモデルのテストデータ定義を見なおしてみた。
FactoryGirl.define do factory :hoge do sequence(:title) { |n| "hoge title#{n}" } sequence(:description) { |n| "hoge description#{n}" } status HogeStatus::PUBLIC user end
hogeモデルはuserモデルとhas_many関係にあるのでuserが定義してある。これか。buildするとhogeはDBにいれらんないけどuserは毎回作られてDBに入れられてるわけだ。
もう1回テストを書きなおす
ぜんぶ同じuserに紐づくhogeデータでもテストしたい内容に変わりはないので1userしか作られないように修正する。
describe "scope:recent" do let(:user) { FactoryGirl.create(:user) } context "return 10 rows" do before do hoges = [] 11.times do hoges << user.hoges.new( title: "title", description: "desc", status: HogeStatus::PUBLIC) end Hoge.import hoges end specify { Hoge.recent(0).count.should be(10) } end ~snip~
結果を見てみる
Top 2 slowest examples (1.08 seconds, 100.0% of total time): Hoge scope:recent return only public hoges 0.60324 seconds ./spec/models/hogee_spec.rb:193 Hoge scope:recent return 10 rows 0.47999 seconds ./spec/models/hogee_spec.rb:161
超絶改善wwwwwwwwwwwww
追記)FactoryGirl.buildを使ったほうがいいと気づいた
describe "scope:recent" do let(:user) { FactoryGirl.create(:user) } context "return 10 rows" do before do hoges = [] 11.times do hoges << FactoryGirl.build(:hoge, user: user, status: HogeStatus::PUBLIC) end Hoge.import hoges end specify { Hoge.recent(0).count.should be(10) } end ~snip~