コード日進月歩

しんくうの技術的な小話、メモ、つれづれ、など

RailsでのRSpec実行時乱数の状態を実行毎に変わらないように固定する

RSpecでsampleするとコケるんだけど特定しづらい…みたいな奴を解消する。

環境

$ ruby -v
ruby 2.5.0p0 (2017-12-25 revision 61468) [x86_64-darwin18]
$ bin/rails --version
Rails 5.2.2
$ bundle exec rspec --version
RSpec 3.8
  - rspec-core 3.8.0
  - rspec-expectations 3.8.2
  - rspec-mocks 3.8.0
  - rspec-rails 3.8.1
  - rspec-support 3.8.0

やり方

spec_helperでsrandのseed値を固定する

bin/rails g rspec:install

で生成される rspec_helperだと以下のようにbeginでまるまるコメントアウトされているので直す(昔はコメントアウトされてなかったみたいなの場合によって齟齬がある)

# The settings below are suggested to provide a good initial experience
# with RSpec, but feel free to customize to your heart's content.
=begin
  # This allows you to limit a spec run to individual examples or groups
  # you care about by tagging them with `:focus` metadata. When nothing
  # is tagged with `:focus`, all examples get run. RSpec also provides
  # aliases for `it`, `describe`, and `context` that include `:focus`
  # metadata: `fit`, `fdescribe` and `fcontext`, respectively.
  config.filter_run_when_matching :focus

#### 中略 ####

  # 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
=end
Kernel.srand config.seed

これを指定すると rand にまつわるものはすべてseed値が rspec--seed {{seed数値}} もしくは --order rand:{{seed数値}} のオプションのseed数値を使うようになる。

rspec実行時に指定する

前述通りseedの値を指定する

bundle exec rspec --seed {{seed数値}}

もしくは

bundle exec rspec --order rand:{{seed数値}}

※もともとのRSpecのオプションとしては order とあるとおりテスト順番を変えるためのものなので注意

対象とするコード

1から10000の値の数値が入った配列からsampleで1個取り出す

spec/sample/demo_spec.rb

require 'rails_helper'

RSpec.describe "sample" do
  let(:demo_array) { (1..10000).map{|x| x} }
  subject { demo_array.sample}
  context "sample実行した場合" do

    it "期待した結果が返ってくる" do
      expect(subject).to eq(1548)
    end
  end
end

spec_helplerの値をコメントアウトした状態の場合

▼実行1回目

$ bundle exec rspec spec/sample/demo_spec.rb
F

Failures:

  1) sample sample実行した場合 期待した結果が返ってくる
     Failure/Error: expect(subject).to eq(1548)
     
       expected: 1548
            got: 6736
     
       (compared using ==)
     # ./spec/sample/demo_spec.rb:9:in `block (3 levels) in <top (required)>'

Finished in 0.02443 seconds (files took 7.75 seconds to load)
1 example, 1 failure

Failed examples:

rspec ./spec/sample/demo_spec.rb:8 # sample sample実行した場合 期待した結果が返ってくる

▼実行2回目

$ bundle exec rspec spec/sample/demo_spec.rb
F

Failures:

  1) sample sample実行した場合 期待した結果が返ってくる
     Failure/Error: expect(subject).to eq(1548)
     
       expected: 1548
            got: 9457
     
       (compared using ==)
     # ./spec/sample/demo_spec.rb:9:in `block (3 levels) in <top (required)>'

Finished in 0.02021 seconds (files took 5.08 seconds to load)
1 example, 1 failure

Failed examples:

rspec ./spec/sample/demo_spec.rb:8 # sample sample実行した場合 期待した結果が返ってくる

実行毎に数値が違う。 ではそのまま --seed をつけるとどうなるか

▼seedつけて1回目

$ bundle exec rspec spec/sample/demo_spec.rb --seed 100

Randomized with seed 100
F

Failures:

  1) sample sample実行した場合 期待した結果が返ってくる
     Failure/Error: expect(subject).to eq(1548)
     
       expected: 1548
            got: 6560
     
       (compared using ==)
     # ./spec/sample/demo_spec.rb:9:in `block (3 levels) in <top (required)>'

Finished in 0.01806 seconds (files took 2.08 seconds to load)
1 example, 1 failure

Failed examples:

rspec ./spec/sample/demo_spec.rb:8 # sample sample実行した場合 期待した結果が返ってくる

Randomized with seed 100

▼seedつけて2回目

$ bundle exec rspec spec/sample/demo_spec.rb --seed 100

Randomized with seed 100
F

Failures:

  1) sample sample実行した場合 期待した結果が返ってくる
     Failure/Error: expect(subject).to eq(1548)
     
       expected: 1548
            got: 2008
     
       (compared using ==)
     # ./spec/sample/demo_spec.rb:9:in `block (3 levels) in <top (required)>'

Finished in 0.01302 seconds (files took 2.04 seconds to load)
1 example, 1 failure

Failed examples:

rspec ./spec/sample/demo_spec.rb:8 # sample sample実行した場合 期待した結果が返ってくる

Randomized with seed 100

特に固定はされない

spec_helperで Kernel.srand config.seed を指定した場合

ちゃんとspec_helperに記述して、--seed をつけてやってみる

▼seedつけて1回目

$ bundle exec rspec spec/sample/demo_spec.rb --seed 100

Randomized with seed 100
F

Failures:

  1) sample sample実行した場合 期待した結果が返ってくる
     Failure/Error: expect(subject).to eq(1548)
     
       expected: 1548
            got: 5641
     
       (compared using ==)
     # ./spec/sample/demo_spec.rb:9:in `block (3 levels) in <top (required)>'

Finished in 0.02062 seconds (files took 4.1 seconds to load)
1 example, 1 failure

Failed examples:

rspec ./spec/sample/demo_spec.rb:8 # sample sample実行した場合 期待した結果が返ってくる

Randomized with seed 100

▼seedつけて2回目

$ bundle exec rspec spec/sample/demo_spec.rb --seed 100

Randomized with seed 100
F

Failures:

  1) sample sample実行した場合 期待した結果が返ってくる
     Failure/Error: expect(subject).to eq(1548)
     
       expected: 1548
            got: 5641
     
       (compared using ==)
     # ./spec/sample/demo_spec.rb:9:in `block (3 levels) in <top (required)>'

Finished in 0.02198 seconds (files took 4.91 seconds to load)
1 example, 1 failure

Failed examples:

rspec ./spec/sample/demo_spec.rb:8 # sample sample実行した場合 期待した結果が返ってくる

Randomized with seed 100

5641で固定された。

参考