FakerやGimeiのseedをrspecのseedと一致させる

Ruby on Rails

概要

  • rspecにおいて、単体テストの順番や使うデータをランダム化してテストパターンを増やすために config.order = :randomを有効化するのは普通かと思います。
  • テストが失敗したときに、rspecコマンドの–seedオプションでランダム生成器のseedを固定してデバッグするときがあると思います。今回は、seedを固定してもテストが失敗したり成功したりして不安定でした。
  • そしたら、乱数生成のseedはFakerやGimeiは各ライブラリ内で乱数発生器のインスタンスを作っているのでランダムに生成された値に依存したテストがユニークになりませんでした。

問題が起きたテスト

FactoryBotで以下のようにエンティティを生成したうえで、このエンティティのページングをテストするコードでした。

ページングする際には、codeカラムでsortされているので生成される値がランダムになるとテストのときに検証する値が予期しないものとなってしまいます。

FactoryBot.define do
  factory :product do
    code { Faker::Alphanumeric.unique.alpha(number: 20)  }
    name { Gimei.prefecture.kanji  }
  end
end

rspecコマンドに–seedを渡してもFakerやGimeiで生成した値が固定されないことが問題でした。

実際にはこの問題を見つけるのに2時間ぐらいかかりました。結果だけ書くのは簡単ですが、見つけるのは結構大変です。

実装が正しいのか、テストが正しいのかわからない状況で検証を進めるのは結構大変なものです。しかも、seedオプションを渡しても検証したいコードの結果が変わったりするので辛かったです。

(そもそも、そんなカラムでソートしている実装なんて知らなかったし。natural orderで帰ってきていると思っていたという思い込みもあり。)

解決策

差分

GimeiやFakerにrspecで使っている乱数生成器を渡してあげれば良いです。GimeiやFakerを使っている場合は必ず入れるべき設定です。

--- a/spec/spec_helper.rb
+++ b/spec/spec_helper.rb
@@ -91,5 +91,11 @@ RSpec.configure do |config|
   #   # Setting this allows you to use `--seed` to deterministically reproduce
   #   # test failures related to randomization by passing the same `--seed` value
   #   # as the one that triggered the failure.
+  Kernel.srand config.seed
+
+  require 'faker'
+  Faker::Config.random = Random.new(Random.seed)
+
+  require 'gimei'
+  Gimei.config.rng = Random.new(Random.seed)
 end

結果

spec/spec_helper.rbはこんな感じになります。

  #   # Run specs in random order to surface order dependencies. If you find an
  #   # order dependency and want to debug it, you can fix the order by providing
  #   # the seed, which is printed after each run.
  #   #     --seed 1234
  config.order = :random
  #
  #   # Seed global randomization in this process using the `--seed` CLI option.
  #   # Setting this allows you to use `--seed` to deterministically reproduce
  #   # test failures related to randomization by passing the same `--seed` value
  #   # as the one that triggered the failure.
  Kernel.srand config.seed

  require 'faker'
  Faker::Config.random = Random.new(Random.seed)

  require 'gimei'
  Gimei.config.rng = Random.new(Random.seed)
end

テストコードの書き換え

値に依存しないようなテストコードに書き換えます。

今回は、返り値となるデータの中身の検証が必要だったので、FactoryBotのcreate時に定数を渡すことで対処しました。

コメント