RSpec
FactoryGirl

Factory Girl の sequence でハマった

More than 3 years have passed since last update.

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.rbconfig.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)するのとは分けておくのがいいんですかね。
この辺、常識なのかもしれないですけど、ハマったのでメモしておきました。