RSpecのlet
は遅延評価でlet!
は事前評価というのがわかったとしても、具体的な挙動は試してみないとわかりにくい。
気をつけたいところとしては、let!
が事前評価をするとはいっても、変数の依存関係の解決はlet
同様に動的に行えるということ。
つまり、let!
を使ったとしても、let!
変数の評価中にまだ評価・宣言されていないlet/let!
変数が登場してもエラーにはならない。
その際には、let
と同様のプロセスで、深さ優先探索のようにして処理の途中に出会ったlet/let!
変数の評価をその都度行っていくだけである。
別の言い方をすると、let!
を使おうとも、let!
変数の依存関係がプログラムオーダー順に解決されるように宣言位置を調節する必要はない。
RSpecでは個々のテストを実行する前に、プリプロセス(後述)を行なった後、RSpec.describe
句の中身全体をプログラムオーダー順に上から見ていくフェーズが入る。
このフェーズでは、before
句もしくはlet!
句に出くわした場合には評価が開始される一方で、let
句は無視される。
このテストの実行前の評価機会の有無がlet
とlet!
の違いの本質であり、評価開始後の処理のされ方は両者ともに全く同じである。
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
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のコードを読んだわけではないので確証はない)
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
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