Railsプロジェクトで、RSpec+Factory Girlを使ってテストを書いていて、
ローカルでテストを走らせると問題なく通るのだけど、Travis CI上ではFが出るという不思議な現象が起きて、解決するまでに少し時間がかかったので、メモを残しておく。
例えばUserとHistoryというモデルがあって、1対Nで対応している。
テストの中でUser.joins(:histories)
のようにINNER JOINしたものを使う必要があったので、Factory Girlで以下のように書いた。
factory :user, class: User do
sequence(:id) { |n| n+1000 }
…
end
factory :history, class: History do
sequence(:id) { |n| n }
sequence(:user_id) { |n| n+1000 } #ここをuserのidと揃える
…
end
RSpecにはこう書く。以下、Aの場所と呼ぶ。
before(:each) do
100.times { create(:user) }
100.times { create(:history) }
end
(今回は1対N (N>1)の状態でテストする必要はなくて、1対1でも良かったのでこういう書き方をしている。)
このAのあとでUser.joins(:histories)
をすればUser.joins(:histories).size
は100になるはずなんだけど、0になってしまう問題が起きてFが出ていた。
原因は別の場所(以下、Bの場所と呼ぶ) で
1000.times { create(:history) }
をしていて、historyのuser_idがuser.idよりも1000進んでいて、INNER JOINの時に一致したidがなかったということ。
spec_helper.rb
でconfig.order = "random"
にして、順番がランダムに行われるようにしているのだけど、ローカルでrspecを走らせた時には、BよりもAの方が先に実行されたため、historyのuser_idとuser.idが一致していて、JOINが問題なくできたためテストをパスできたのだけど、
Travis CIでテストを走らせた時には(7回中7回とも)、AよりもBの方が先に実行されたため、JOINが上手くできなくてFが出ていた。
ハマった理由は3つで、
- before(:each) を使えば毎回テストの前に状態がリセットされて、今回であれば毎回idが1001から作られると思っていた。
- Rails 4.1.5から4.1.7にあげた途端に7回連続でFailしたため、(ありえない気がするけど)Railsのアップデートと何か関係があるのかと思って、そっちの方向でしばらく考えてしまった。
- 4.1.5ではTravisで成功(2回試行)、4.1.7ではローカルではテストが成功する(3回試行)のに、Travisのみで失敗する(7回試行)という脅威の運の悪さ(1/(2**12) = 0.024%)によって、Travisの環境になにか問題があるのかと考えていた。
ということで、解決方法としては
factory :user4join, class: User do
sequence(:id) { |n| n+1000 }
…
end
factory :history4join, class: History do
sequence(:id) { |n| n }
sequence(:user_id) { |n| n+1000 } #ここをuserのidと揃える
…
end
みたいにJOINする専用のデータを作って、単にcreate(:hitstoy)
するのとは分けておくのがいいんですかね。
この辺、常識なのかもしれないですけど、ハマったのでメモしておきました。