zenet_logo

-株式会社ゼネット技術ブログ-

rspecにて、let(遅延評価)を用いて、後でデータを入れ込む方法

スマートなrspecのデータ定義方法

概要

rspecの綺麗なコードの勉強としてGitHub - willnet/rspec-style-guide: 可読性の高いテストコードを書くためのお作法集 の内容を見ていましたが、その際気になった"letとlet!の使い分け"にて、こんな方法でspec上の変数を更新できるのかと感動しましたので、その内容についてと併せて気になったことについて備忘録がてら記載をしました。

参考にした記事とは異なるコードですが、rspecでcontext毎にデータを変更してテストを実行したい場合に下記のように記述することができます。この書き方だとcontextやdescribeの範囲で値を固定した時に、user.age = 10等、値を更新するように書かなくて済みます。初期値として設定する必要もありませんので、より処理毎に値を変えたい変数だという事が感覚的に理解しやすくなると思います。何よりこの書き方の方がスマートな感じがありますので、個人的には好みな書き方でした。

処理の流れとしては、最初にlet(:user) { FactoryBot.build(:user, age: set_age)}で、ageの値をset_ageの変数から取得するように定義しておきます。set_ageは何ぞやという話ですが、後のcontextブロック内のlet(:set_age)で遅延評価によって後から値が登録されるようになっています。最終的にitブロック内でuserが呼ばれた際に、userとset_ageの値が入り、rspecによるテストが実施されるという流れになっています。

RSpec.describe User, type: :model do
  let(:user) { FactoryBot.build(:user, age: set_age)}

  context 'when set age bottom of age(10)' do
    let(:set_age) { 10 }
    it { expect(user).to be_valid }
  end

  context 'when set age bottom of age(100)' do
    let(:set_age) { 100 }
    it { expect(user).to be_valid }
  end

  context 'when set age bottom of age(101)' do
    let(:set_age) { 101 }
    it { expect(user).to be_invalid }
  end

因みに、上記ではUser Modelの定義は割愛していますが、User.ageのvalidateとして1から100までを許容する仕様になっています。例だとcontextブロックを利用していますが、describeブロック内でも同様に動きます。

検証を行ってみて

letとlet!の使い分けについて、実際にspecで混在しているとそのデータの動きを理解するのが苦手でしたが、実用的なコードを見て、理解を深めることが出来たと思います。

また、使い分けについてですが、こちらの記事ではletはあまり利用せずに、基本的にlet!で良いと記載されていました。

基本的にlet!を推奨する。

letで定義した値は呼ばれない限り実行されないのでlet!よりコストが低い、ということを利点として挙げている人もいるが、これを鵜呑みにすると「letで定義した値がテストケースによって使われたり使われなかったりする」状態になってしまう。これはテストの前提条件を理解するコストを上げ可読性を落とす。

ただ、であれば、letもlet!も使わずに普通に変数に格納してくれるほうが分かりやすいんだけれどなぁと思い、let!をするのと変数に格納するので何か違い等あるのだろうかと違いについても調べてみました。rspecを普段記載しない人であれば、分かりやすさ重視で下記のように変数に格納する方が分かり良いのではないかと。。

  #let!(user) { FactoryBot.build(:user, age: 10) }
  user = FactoryBot.build(:user, age: 10)

こう疑問に思い、調べた所いくつか参考になる内容がありました。一つの意見としては、rubyとrspecだとDSLが異なるので、DSLに則した記法で書きましょうという事でした。英語を使って自己紹介する時に、TANAKA taroではなく、taro TANAKAの順番で名前を伝えるみたいな感じでしょうか。確かにspecは特有のDSLになっているので、rubyとは分けて記載する方がよいという事ですね。いろいろと勉強になりました。

今後自身が書くときは、letとlet!、変数の使いどころを気を付けようと思います。

DSLについては、こちら e-words.jp

その他参考にした記事