Rspecのbeforeとletの挙動を検証

rspecの挙動が分かりづらかったので簡単にまとめてみました。

# Gemfile.lock
```
    rspec (3.6.0)
      rspec-core (~> 3.6.0)
      rspec-expectations (~> 3.6.0)
      rspec-mocks (~> 3.6.0)
    rspec-core (3.6.0)
      rspec-support (~> 3.6.0)
    rspec-expectations (3.6.0)
      diff-lcs (>= 1.2.0, < 2.0)
      rspec-support (~> 3.6.0)
    rspec-mocks (3.6.0)
      diff-lcs (>= 1.2.0, < 2.0)
      rspec-support (~> 3.6.0)
    rspec-rails (3.6.0)
      actionpack (>= 3.0)
      activesupport (>= 3.0)
      railties (>= 3.0)
      rspec-core (~> 3.6.0)
      rspec-expectations (~> 3.6.0)
      rspec-mocks (~> 3.6.0)
      rspec-support (~> 3.6.0)
    rspec-support (3.6.0)
```

# 検証コード

`create(:model)`は`factorygirl`で何かしらの`ApplicationRecord`のオブジェクトを作る想定

```rb
require 'rails_helper'

RSpec.describe do
  let(:model) do
    m = create(:model)
    p 'outer let:' + m.id.to_s
    m
  end
  before do
    m = create(:model)
    p 'outer before:' + m.id.to_s
  end
  before(:each) do
    m = create(:model)
    p 'outer before(each):' + m.id.to_s
  end
  before(:all) do
    m = create(:model)
    p 'outer before(all):' + m.id.to_s
  end

  context do
    let(:model) do
      m = create(:model)
      p 'inner let:' + m.id.to_s
      m
    end
    before do
      m = create(:model)
      p 'inner before:' + m.id.to_s
    end
    before(:each) do
      m = create(:model)
      p 'inner before(each):' + m.id.to_s
    end
    before(:all) do
      m = create(:model)
      p 'inner before(all):' + m.id.to_s
    end
    subject do
      m = model
      p 'subject1:' + m.id.to_s
      m
    end

    example { expect(subject.id).to eq(model.id) }
    it do
      m = model
      p 'it:' + m.id.to_s
      expect(subject.id).to eq(m.id)
    end
  end

  context do
    subject do
      m = model
      p 'subject2:' + m.id.to_s
      m
    end

    example { expect(subject.id).to eq(model.id) }
  end
end
```

とした場合、アウトプットは下記の通りになる

```
"outer before(all):1"
"inner before(all):2"
"outer before:3"
"outer before(each):4"
"inner before:5"
"inner before(each):6"
"inner let:7"
"subject1:7"
"outer before:8"
"outer before(each):9"
"inner before:10"
"inner before(each):11"
"inner let:12"
"it:12"
"subject1:12"
"outer before:13"
"outer before(each):14"
"outer let:15"
"subject2:15"
```

# まとめ

- テストの実行単位は `it` or `example`であり、基本的に実行単位ごとに変数は初期化される(以下、このテスト実行単位を*テストプロセス*と表記します)
- `before`メソッドはブロックのスコープ内で有効
- `before(:all)`はブロック内で一度実行される(ブロック内にテストプロセスが複数あっても実行は一度のみ)
- `before`と`before(:each)`は同じ。ブロック内のテストプロセスごとに実行される
- `subject`は`let(:subject)`の略。テスト対象を表す変数。`expect(subject).to`を`is_expected.to`と略せる。
- `let`について
 - 遅延評価変数。ブロック内でコールされたタイミングでletブロック内の処理を行い結果をletの引き数の変数に格納
 - 同じブロック内で複数回コールされる場合、初回にletブロック内の処理を行いその結果のオブジェクトを、2回目以降は初回の実行時に作成されたオブジェクトを返却する
 - ブロックのスコープ内とスコープ外の2箇所で同じ名前のlet変数が定義されていた場合、ブロック内のletだけが評価される

# おまけ(letとlet!)

```rb
require 'rails_helper'

RSpec.describe do
  let(:model) do
    m = create(:model)
    p 'outer let:' + m.id.to_s
    m
  end

  context do
    let!(:dummy_model) do
      m = create(:model)
      p 'dummy let!:' + m.id.to_s
      m
    end
    subject do
      m = model
      p 'subject:' + m.id.to_s
      m
    end

    example { expect(subject.id).to eq(model.id) }
    it do
      m = model
      p 'it:' + m.id.to_s
      expect(subject.id).to eq(m.id)
    end
  end
end
```

```
"dummy let!:1"
"outer let:2"
"subject:2"
"dummy let!:3"
"outer let:4"
"it:4"
"subject:4"
```

- `let!`は変数がコールされなくてもテストプロセスのはじめに実行される(`before`の前か後かはコードの記述順で決まる)