概要
RSpecではlet
とlet!
を使いますが、この2つはどのように使い分けるのでしょうか。
そこにはRSpecの遅延評価のことを知っておく必要があります。
letとlet!の違い
-
let
最初にメソッドを呼び出された時に使います***。使い方はbeforeと同じ
です。 -
let!
itなどが実行される前に評価されます。beforeと実行タイミングが同じ
です。
ChatGPTに小学生にもわかるように説明してと聞いてみました。(子どもみたいな解説ご了承ください)
⚫︎let
おもちゃ箱におもちゃをしまっておくようなもので、おもちゃ箱(=変数) に おもちゃ(=値) を入れておいて、必要なときに箱を開けて取り出します。
箱を開けるまでおもちゃは見えず、初めて箱を開けるときにおもちゃが出てくるようなものです。ここで言うと,let(:variable) { some_value } と書くと、
おもちゃ箱にvariableというおもちゃが入れられます。
でも、箱の中身は最初は見ずに、箱を開ける(=変数を使う)ときに初めておもちゃがでてきます。
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!
おもちゃ箱に最初からおもちゃが見えるようにしておくようなものです。
おもちゃ箱(=変数)におもちゃ(=値)を入れておいて、箱を開けなくても最初からおもちゃが見えます。
おもちゃが最初から見えているので、箱を開けるときに毎回同じおもちゃが見えます。
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) }
はテストが実行されるまで実際には評価されません。
let(:post) { create(:post,user: user) }
let!
にするとテストが始まる前に{ create(:post,user: user) }が実行
され、
その結果がlet!の後の:postにセットされます。
let!にしたこの時点でcreateしてくれるのでレコードが作成してくれます。
let!(:post) { create(:post,user: user) }
なぜ遅延評価を使うのか
ChatGPTに聞いてみたら遅延評価を使うことで、テストの実行効率が向上し、不要な計算を避けることができます。特に、コストのかかる計算や初期化処理が必要な場合には、遅延評価が性能を向上させる助けになるみたいです。
letとlet!の使い分け
ここからは大人の説明をしていきます。
let(:user) { create(:user)}
の方は遅延評価でuserが参照された時に評価つまり、ユーザーがデータベースで作られて、
処理が実行された時に実行されます。
let!(:user) { create(:user) }
let!が使われる時は最初からitの中(example)が評価されます。
何のこっちゃと思うので使用例を見ていきます。
⚫︎letの使用例
遅延評価を使う場合についてです。
ex)
あるテストケース内で変数を複数回使用するが、その値が変更されない時に便利です。
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)
あるテストケース内で変数の値が変更される可能性があり、その都度初期化が必要な時に便利です。
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を使用することで、テストコード内で再利用可能な変数を定義し、コードの再利用性と可読性を向上させることができます。
# ユーザーモデル
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!が即座に実行され、各テストケースで異なる状態を持つことが示されています。
# ユーザーモデル
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参照