helen's blog

ずっとおもしろいことしてたいな。

mockの前にletな話

mockの使い方を調べる人柱になってたら
let使いましょうと言われたのでお勉強めも

letとは

  • memoized helper method
  • メモ化:呼び出しの際の結果を引数を保存し、後で同じ引数で呼び出されたら計算せずにその格納されている結果を返す

特徴

  • 遅延評価
    • 変数が参照されたときに初めて初期化される
  • 一度呼ばれたら二回目以降はそのオブジェクトを返す
  • インスタンス変数、ローカル変数の置き換えに
  • スレッドセーフ
    • 複数のスレッドが同時並行に実行しても問題発生しないこと
    • letの処理がちょっと早くなる

let!

  • 先行評価
  • 変数の値が得られた時点で評価される
  • 即座に変数が初期化される

書き方

let(:変数名) { 入れたい変数 }

# 例
let(:items) { items.find_by_maker_id }

{ items.find_by_maker_id }を items で呼び出すことができます
hashも入れられます

試してみた

$count = 0

let(:count) { $count += 1 }

it "memoizes the value" do
  expect(count).to eq(1) # count = 1
  expect($count += 1).to eq(2) # count = 2
end

it "is not cached across examples" do
  # ↑ の count = 2 が呼ばれ let のとこで+1され3になる
  expect(count).to eq(3)
end

it "is not cached across examples" do
  expect(count).to eq(4) # 同様に4
end

it の前に毎回処理するbeforeより処理が少なくて良さそうだけど
it や let でその値に処理を加えると厄介そうな気が
テストでそんなことするもんじゃないと思うけど

追記

rspec書いてて
そこはletじゃないんだよな〜って言われたのが気になってたので聞いてみた

beforeとletの使い分け
  • before
    • 副作用的な処理を含むもの
  • let
    • 値の代入
before do
  @hoge = 1
  @hoge += 1 # なんらかの処理
end

let!(:hoge){ Item.find(1) } # 代入だけ

用意した値に処理をするならbefore、
値を代入するだけならlet
というのが最近のはやりらしいです

おまけ

インスタンス変数の遅延初期化
  • インスタンス変数に初めてアクセスする時に初期化すること
  • 初回のアクセスでインスタンス変数に値が代入され、以降のアクセスでは既に代入された値が返される
変数名 ||= 変数

# 例
@hoge ||= 300
インスタンス変数が定義されてない場合のみ初期化
# インスタンス変数 var が定義されていたら真を返します
instance_variable_defined?

# 例
unless instance_variable_defined?(:@hoge)
   @hoge = Fuga.find_by(fuga_id: self.id)
end

参考
let and let! - Helper methods - RSpec Core - RSpec - Relish
Better Specs { rspec guidelines with ruby }
ruby on rails - When to use rspec let()? - Stack Overflow
すごいぞRSpec(letとlet!編) - ぷろぐらまねが
instance method Object#instance_variable_defined? (Ruby 2.1.0)