RSpecのletを使った遅延評価❗️(letとlet！の違い)

# 概要
RSpecでは```let```と```let!```を使いますが、この２つはどのように使い分けるのでしょうか。
そこにはRSpecの遅延評価のことを知っておく必要があります。

# letとlet!の違い
- ***let***
最初にメソッドを呼び出された時に使います***。```使い方はbeforeと同じ```です。

- ***let!***
itなどが実行される前に評価されます。```beforeと実行タイミングが同じ```です。

ChatGPTに小学生にもわかるように説明してと聞いてみました。(子どもみたいな解説ご了承ください)

⚫︎***let***
おもちゃ箱におもちゃをしまっておくようなもので、***おもちゃ箱（＝変数)*** に ***おもちゃ（＝値）*** を入れておいて、必要なときに箱を開けて取り出します。
箱を開けるまでおもちゃは見えず、初めて箱を開けるときにおもちゃが出てくるようなものです。ここで言うと,***let(:variable) { some_value }*** と書くと、
おもちゃ箱にvariableというおもちゃが入れられます。
でも、箱の中身は最初は見ずに、箱を開ける（＝変数を使う）ときに初めておもちゃがでてきます。
```rb
RSpec.describe 'let' do
  let(:variable) { some_value }

  it 'opens the toy box and finds a toy' do
    # 変数を使って箱を開ける（初めて箱を開けるとおもちゃが見える）
    expect(variable).to eq(some_value)
  end
end
```

⚫︎***let!***
***おもちゃ箱に最初からおもちゃが見えるようにしておく***ようなものです。
おもちゃ箱（＝変数）におもちゃ（＝値）を入れておいて、箱を開けなくても最初からおもちゃが見えます。
おもちゃが最初から見えているので、箱を開けるときに毎回同じおもちゃが見えます。

```rb
RSpec.describe 'let!' do
  let!(:variable) { some_value }

  it 'sees the toy without opening the box' do
    # 箱を開けなくてもおもちゃが見える（最初から見えている）
    expect(variable).to eq(some_value)
  end
end
```


# 遅延評価とは？
以下のようにletを使うと遅延評価されます。
```{ create(:post,user: user) }```はテストが実行されるまで実際には評価されません。

```rb
let(:post) { create(:post,user: user) }
```

```let!```にするとテストが始まる前に```{ create(:post,user: user) }が実行```され、
その結果が***let!の後の:postにセット***されます。
***let!にしたこの時点でcreateしてくれるのでレコードが作成***してくれます。
```rb
let!(:post) { create(:post,user: user) }
```

# なぜ遅延評価を使うのか
ChatGPTに聞いてみたら遅延評価を使うことで、テストの実行効率が向上し、不要な計算を避けることができます。特に、コストのかかる計算や初期化処理が必要な場合には、遅延評価が性能を向上させる助けになるみたいです。

# letとlet!の使い分け
ここからは大人の説明をしていきます。
```rb
let(:user) { create(:user)}
```
の方は***遅延評価でuserが参照された時に評価つまり、ユーザーがデータベースで作られて、
処理が実行された時に実行***されます。
```
let!(:user) { create(:user) }
```
***let!が使われる時は最初からitの中(example)が評価***されます。

何のこっちゃと思うので使用例を見ていきます。

⚫︎***letの使用例***
遅延評価を使う場合についてです。

ex)
あるテストケース内で変数を複数回使用するが、その値が変更されない時に便利です。

```rb
RSpec.describe 'letを使う' do
  let(:user) { create(:user) }

  it 'ユーザーネームがある' do
    expect(user.username).to eq('大谷')
  end

  it 'メールアドレスがある' do
    expect(user.email).to eq('ohtani@example.com')
  end
end
```
user変数が各テストケース内で使われていますが、letは遅延評価されるため、
***テストケースごとにcreate(:user)が実行***されます。

⚫︎***let!の使用例***
すぐに変数のセットアップが必要な時に使います。
ex)
あるテストケース内で変数の値が変更される可能性があり、その都度初期化が必要な時に便利です。

```rb
RSpec.describe 'let!を使う' do
  let!(:user) { create(:user) }

  it 'ユーザーネームを変える' do
    user.update(username: '大谷')
    expect(user.username).to eq('大谷')
  end

  it 'メールアドレスを変える' do
    user.update(email: 'ohtani@example.com')
    expect(user.email).to eq('ohtani@example.com')
  end
end
```

他にも使用例を出していきます。
⚫︎***let***

この例では、letを使用して初期のユーザー initial_user と新しいユーザー new_user を作成しています。letは遅延評価され、各テストケース内で同じインスタンスが再利用されます。

最初のテストでは、initial_userのユーザーネームを更新しています。
次のテストでは、letで作成した初期のユーザー initial_user のユーザーネームが初期状態のままであることを確認しています。
最後に、letを使用して新しいユーザー new_user を作成し、そのユーザーネームを確認しています。
letを使用することで、テストコード内で再利用可能な変数を定義し、コードの再利用性と可読性を向上させることができます。

```rb
# ユーザーモデル
class User
  attr_accessor :username

  def initialize(username)
    @username = username
  end
end

# RSpecテスト
RSpec.describe 'Using let' do
  # letを使って初期のユーザーを作成
  let(:initial_user) { User.new('initial_user') }

  it 'updates the username' do
    # 最初のユーザーのユーザーネームを変更
    initial_user.username = 'updated_user'
    
    # 初期のユーザーと更新後のユーザーを比較
    expect(initial_user.username).to eq('updated_user')
  end

  it 'has the correct initial username' do
    # letで作成した初期のユーザーのユーザーネームを確認
    expect(initial_user.username).to eq('initial_user')
  end

  # letを使って新しいユーザーを作成
  let(:new_user) { User.new('new_user') }

  it 'has the correct username for the new user' do
    # 新しいユーザーのユーザーネームを確認
    expect(new_user.username).to eq('new_user')
  end
end
```

⚫︎***let!***
この例では、let!を使用して初期のユーザー initial_user と新しいユーザー new_user を作成しています。let!は即座に初期化され、それぞれのテストケースで異なる状態を持つことが期待されます。

最初のテストでは、initial_userのユーザーネームを更新しています。
次のテストでは、let!で作成した初期のユーザー initial_user のユーザーネームが初期状態のままであることを確認しています。
最後に、let!を使用して新しいユーザー new_user を作成し、そのユーザーネームを確認しています。
これにより、let!が即座に実行され、各テストケースで異なる状態を持つことが示されています。
```rb
# ユーザーモデル
class User
  attr_accessor :username

  def initialize(username)
    @username = username
  end
end

# RSpecテスト
RSpec.describe 'Using let!' do
  # let!を使って初期のユーザーを作成
  let!(:initial_user) { User.new('initial_user') }

  it 'updates the username' do
    # 最初のユーザーのユーザーネームを変更
    initial_user.username = 'updated_user'
    
    # 初期のユーザーと更新後のユーザーを比較
    expect(initial_user.username).to eq('updated_user')
  end

  it 'has the correct initial username' do
    # let!で作成した初期のユーザーのユーザーネームを確認
    expect(initial_user.username).to eq('initial_user')
  end

  # let!を使って新しいユーザーを作成
  let!(:new_user) { User.new('new_user') }

  it 'has the correct username for the new user' do
    # 新しいユーザーのユーザーネームを確認
    expect(new_user.username).to eq('new_user')
  end
end
```


# 参考資料

ChatGPT参照

https://qiita.com/kekke-n/items/2f5618753efba7799a55

https://qiita.com/hirotakasasaki/items/fa3b131e27f5d0694c2c

https://papael14.com/how-to-use-the-let/