1
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 3 years have passed since last update.

RSpecのletとlet!の違い

Posted at

RSpecのletは遅延評価でlet!は事前評価というのがわかったとしても、具体的な挙動は試してみないとわかりにくい。

気をつけたいところとしては、let! が事前評価をするとはいっても、変数の依存関係の解決はlet同様に動的に行えるということ。
つまり、let!を使ったとしても、let!変数の評価中にまだ評価・宣言されていないlet/let!変数が登場してもエラーにはならない。
その際には、letと同様のプロセスで、深さ優先探索のようにして処理の途中に出会ったlet/let!変数の評価をその都度行っていくだけである。
別の言い方をすると、let!を使おうとも、let!変数の依存関係がプログラムオーダー順に解決されるように宣言位置を調節する必要はない。

RSpecでは個々のテストを実行する前に、プリプロセス(後述)を行なった後、RSpec.describe句の中身全体をプログラムオーダー順に上から見ていくフェーズが入る。
このフェーズでは、before句もしくはlet!句に出くわした場合には評価が開始される一方で、let句は無視される。
このテストの実行前の評価機会の有無がletlet!の違いの本質であり、評価開始後の処理のされ方は両者ともに全く同じである。

spec/let_test1.rb
RSpec.describe do
  let(:var1) { # スルーされる
    puts "evaluating var1"
    var3 + 2
  }
  before :each do
    puts "invoke before block"
  end
  let(:var2) { # スルーされる
    puts "evaluating var2"
    var3 + 1
  }
  let(:var3) { # スルーされる
    puts "evaluating var3"
    var4 + 1
  }
  let(:var4) { # スルーされる
    puts "evaluating var4"
    100
  }
  it do
    puts "======"
    puts "#{var1}" # var1 -> var3 -> var4
    puts "#{var2}" # var2 -> var3(初期化済み)
    puts "#{var3}" # var3(初期化済み)-> var4(初期化済み)
    puts "#{var4}" # var4(初期化済み)
  end
end
$ bundle exec rspec spec/let_test1.rb
invoke before block
======
evaluating var1
evaluating var3
evaluating var4
103
evaluating var2
102
101
100
spec/let_test2.rb
RSpec.describe do
  describe do
    let!(:var1) { # var1 -> var3 -> var4
      puts "evaluating var1"
      var3 + 2
    }
    before do
      puts "invoke before block"
    end
    let!(:var2) { # var2-> var3(初期化済み)
      puts "evaluating var2"
      var3 + 1
    }
    let!(:var3) { # var3(初期化済み)-> var4(初期化済み)
      puts "evaluating var3"
      var4 + 1
    }
    let!(:var4) { # var4(初期化済み)
      puts "evaluating var4"
      100
    }
    it do
      puts "======" # これの実行時には全て初期化済み
      puts "#{var1}"
      puts "#{var2}"
      puts "#{var3}"
      puts "#{var4}"
    end
  end
end
$ bundle exec rspec spec/let_test2.rb
evaluating var1
evaluating var3
evaluating var4
invoke before block
evaluating var2
======
103
102
101
100
.

Finished in 0.00471 seconds (files took 0.12489 seconds to load)
1 example, 0 failures

補足:ここでいうプリプロセスとは何か

主にdescribe, contextなどのネストの解消と、同一名称の変数の統合処理である。
let_test3.rbは、itの実行前にlet_test4.rbのように変形されると考えられる。
(ただし、RSpecのコードを読んだわけではないので確証はない)

let_test3.rb
RSpec.describe do
  let!(:var2) {
    puts "evaluating var2 (100)"
    100
  }
  describe do
    let!(:var2) {
      puts "evaluating var2 (0)"
      0
    }
    let!(:var1) {
      puts "evaluating var1"
      var2
    }
    before {
      puts "before"
    }
    let!(:var2) {
      puts "evaluating var2 (1)"
      1
    }
    let!(:var2) {
      puts "evaluating var2 (2)"
      2
    }
    it do
      puts "#{var1}"
    end
  end
end
let_test3_preprocessed.rb
RSpec.describe do
  let!(:var2) {
    puts "evaluating var2 (2)"
    2
  }
  let!(:var1) {
    puts "evaluating var1"
    var2
  }
  before {
    puts "before"
  }
  it do
    puts "#{var1}"
  end
end
$ bundle exec rspec spec/let_test3.rb
evaluating var2 (2)
evaluating var1
before
2
.

Finished in 0.00399 seconds (files took 0.12003 seconds to load)
1 example, 0 failures
1
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
1
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?