- 更新日: 2013年11月25日
- RSpec
RSpec のテストで let! で即座に変数作成、let で遅延評価との違い
今日も作成中の Rails4アプリで RSpec テストを作っては実装を行なっていたのですけど、はまった点が一箇所あった。
user has_many comments.
comment belongs_to user.
DB の comments テーブルには、user_id という外部キーがある。
以上の関係になっているモデルでのテストにおいて、@user.comments(@user に属している comments)で、comment が作成日時の新しい順に並んでいることを保証するテストを書きました。
バグのあるテストコード spec/models/user_spec.rb
最初に、user_spec.rb に書いたコード。実はこのコードにはバグが含まれています。
spec/models/user_spec.rb
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 |
describe User do ・・・ subject { @user } describe "comment associations" do before { @user.save } let(:older_comment) do FactoryGirl.create(:comment, user: @user, created_at: 1.day.ago) end let(:newer_comment) do FactoryGirl.create(:comment, user: @user, created_at: 1.hour.ago) end it "should have the right comments in the right order" do expect(@user.comments.to_a).to eq [newer_comment, older_comment] end ・・・ end end |
テストが通らないことを確認後、実装を行いました(実装のコードは省略)。その後、再び以下のとおりテスト実行。
1 2 3 4 5 6 7 8 9 10 |
$ bundle exec rspec spec/ ・・・ Failures: 1) User comment associations should have the right comments in the right order Failure/Error: expect(@user.comments.to_a).to eq [newer_comment, older_comment] |
正しく実装を終えたはずなのに、テストを何度繰り返してもテストが通りません。
let は遅延評価で let! は即座に評価、両者の違い
こういう場合は、テストコードにミスがある場合も多いというのを学習したので、調べたところ…
let で作った変数は lazy(遅延評価)であり、変数が参照されたときにはじめて初期化される。つまり最後の、eq を使って newer_comment, older_comment が出現した時点で初めて評価される。だから、@user.comments はその時点で空っぽのままであり、どうやってもテストを通るはずがない…と。
let が遅延評価であるのに対して、let!(letバン)は、各々のテストが実行される前に評価され、即座に変数が初期化されます。
@user.comments が空ではテストにならないので、newer_comment, older_comment は、遅延評価(let)ではなく、即座(let!)に作成する必要があります。let! を使うことで、最後の eq を使ったテストでの、@user.comments にちゃんと作成した newer_comment, older_comment が格納されることになる。
正しいテストコード spec/models/user_spec.rb
let を let! に変更して、以下のとおり正しいテストコードに修正しました。
spec/models/user_spec.rb
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 |
describe User do ・・・ subject { @user } describe "comment associations" do before { @user.save } let!(:older_comment) do FactoryGirl.create(:comment, user: @user, created_at: 1.day.ago) end let!(:newer_comment) do FactoryGirl.create(:comment, user: @user, created_at: 1.hour.ago) end it "should have the right comments in the right order" do expect(@user.comments.to_a).to eq [newer_comment, older_comment] end ・・・ end end |
ということで、let を let! に変更したところ、正しくテストが通りました。たったこれだけの違いですけどはまりました。解決できて良かったです^^。テスト駆動開発はテストコード側にバグがある場合がなかなか気づきにくい。実装側にミスがある時にはすごく大助かりなのですけど。
- – 参考リンク –
- すごいぞRSpec(letとlet!編) – ぷろぐらまねが
- RSpec の関連記事
- FactoryGirlをSpringと共に使う時の注意
- 複数モデルのpost :createをテストするRSpecコード(Controller Spec)
- RSpec3でTime.nowをスタブ化(stub)
- RSpecでJSONによるPOSTリクエストをテスト
- RSpec & Capybara の雑感
- RSpec+Capybaraで同名の複数要素の並び順をテストする
- RSpec3ではrails_helper.rbがrequireされる
- Capybara + Launchy で RSpec テストをブラウザで確認
- CapybaraのwithinをRSpecで使う
- Serverspec(RSpec)のテスト出力に色を付けて見やすくフォーマット
Leave Your Message!