注意: 詳細な話は
RSpecのletを使うのはどんなときか?(翻訳)
と
「RSpec で example の外で定義したローカル変数を使うのはアリか?」に対する僕の見解と解決策
あたりを見てもらう方が早いです
別に目新しい何かを語るわけではないので結論から話すとletでいいぞっていう話です。
概要
レビュー時に「この変数をletで定義する意味がないと思う」というレビューをもらった。
describe 'test' do
let :user_name do 'test' end
let :password do 'password' end
it 'username' do
expect(User.new(user_name, password).user_name).to eq('test')
end
it 'password' do
expect(User.new(user_name, password).password).to eq('password')
end
end
とりあえず共通化できる変数はまとめてちゃえの精神の元,宣言していたのでいまいちletを使う
理由というを即答えることができなかった。
この規模のテストならこうもかけるし
describe 'test' do
user_name='test'
password='password'
it 'username' do
expect(User.new(user_name, password).user_name).to eq('test')
end
it 'password' do
expect(User.new(user_name, password).password).to eq('password')
end
end
こうもかける
describe 'test' do
# eachでもいいしallでも良い
before(:each)do
@user_name='test'
@password='password'
end
it 'username' do
expect(User.new(@user_name, @password).user_name).to eq('test')
end
it 'password' do
expect(User.new(@user_name, @password).password).to eq('password')
end
end
正直動くしどれでもいい、脳死でletでええやんけ
さぁどれが良いのだろうか。
letの場合の挙動
letのドキュメントはこれかな?
ざっくりいうと「同じexample内は最初に呼ばれた値をキャッシュして使い回すよ。でも違うexampleの場合また呼び直すよ。(で同じexmaple内ではそれを使い回す)」
すなわちbefore(:each)的な動きをする。
当然it内で色々letの値をこねくり回して放置しても次のit内では新しくletないのblockで初期されるって寸法なわけだ。話の分かるやつだ。
インスタンス変数(before block)
beforeブロックで定義できる。
describe 'test' do
@test = true
it 'test' do
expect(@test).to eq(true)
end
end
こういうのはあかんみたい。 いまいちインスタンス変数のスコープがわからん
before構文は色々種類があるのでどれで初期化するのかで挙動が変わるけど
describe 'test' do
before do
test = true
end
it 'test' do
expect(@test).to eq(true)
end
end
こういう感じでローカル変数を定義するのはできないようなのでbefore blockで変数を定義して、it内で利用するならインスタンス変数しかないのかな。
これでもいいという人は多そうだけど、なんかなんでもかんでも@つけるのダサくないですか?
後、@をつけて回るのめんどくさいです。
ローカル変数
まぁ動くけど下の例のような場合ちょっと怖い
describe 'test' do
test = true
it 'test' do
expect(test).to eq(true)
test = false
end
context 'true' do
it 'test' do
expect(test).to eq(true)
end
end
end
要は意図しない形で変数が書き換えられた時対応が難しいという問題がある。
じゃぁ定数?
describe 'test' do
test = true.freeze
it 'test' do
expect(test).to eq(true)
test = false
end
...
warningは出るけど再代入はできるのでちょっと怖いね。
つまりローカル変数を使用するようなケースは複数のexampleやitブロック間で共有する変数を使用したくてなおかつインスタンス変数やグローバル変数を使用したくない場合?
クソテスtあまり考えたくないケースですね(棒)
letを使用する理由
基本的にletでやっとけば変数は安全に初期化されるし、定数(freeze)よりも安全性が高いのではないかという気がしてきたが他にletを利用した方が良い理由としては以下の点が挙げられる。
- typoにすぐ気づける (代入文でない限りtypoした場合「undefined local variable or method」のエラーがでる。インスタンス変数の場合nilと評価される)
- ローカル変数からすぐ変更できる。(インスタンス変数ではないので@とかつける必要ない)
- 無駄な初期化が防げる(遅延評価なので使われない場合初期化処理は実施されない)
- かっこいい!(ブロックはかっこいい!)
上記理由は
RSpecのletを使うのはどんなときか?(翻訳)
にありますので詳しくはそちらを参考にしてください。
定数だろうがなんだろうがletで定義しときゃよしなにしてくれるって!
当然遅延評価なので、letで初期化した結果が他の場所で影響してくる場合注意する必要が出てくるのかな?
遅延評価が原因でテストが失敗する例
describe 'test' do
let :test do @test = 'test'; 'test' end
it 'test' do
expect(@test).to eq('test')
end
end
上の例が遅延評価が原因でテストが失敗する例
実用性?犬にでも食わせておけ。
Failures:
1) test test
Failure/Error: expect(@test).to eq('test')
expected: "test"
got: nil
@test
がexpect時に宣言されていないのでnilになってしまう。
すぐに評価してほしい場合は
describe 'test' do
let! :test do @test = 'test' end
...
上のようにlet!を使用しよう。その場合どうも挙動を見る限りit,exampleが実行される直前にlet block内が評価されているように見える。(つまり無駄な初期化が発生する)
ざっと調べた感じがこんな感じ。
楽しいtest lifeを