リア充爆発日記

You don't even know what ria-ju really is.

RSpecでletを使う2つの理由

RSpecを使って最初にぶつかるのがletな人はぼくだけかも知れないけど、とにかくメモ。

参考はこのサイト。
http://ruby.railstutorial.org/chapters/modeling-users?version=3.2#sidebar:let

require 'spec_helper'

describe User do

  before do
    @user = User.new(name: "Example User", email: "user@example.com", 
                     password: "foobar", password_confirmation: "foobar")
  end

  subject { @user }
  .
  .
  .
  it { should respond_to(:authenticate) }
  .
  .
  .
  describe "return value of authenticate method" do
    before { @user.save }
    let(:found_user) { User.find_by_email(@user.email) }

    describe "with valid password" do
      it { should == found_user.authenticate(@user.password) }
    end

    describe "with invalid password" do
      let(:user_for_invalid_password) { found_user.authenticate("invalid") }

      it { should_not == user_for_invalid_password }
      specify { user_for_invalid_password.should be_false }
    end
  end
end

※このコードも上記サイトの引用

これはコテコテのemailがキーになるUserモデルでのアドレス重複チェックのテストでございます。


あらかじめ

    before { @user.save }

でDBにデータを保存しておいて、それをベースにアドレス重複チェックのテストを展開する段取りなわけです。

さて、件の"let"が次のステップで登場します。

    let(:found_user) { User.find_by_email(@user.email) }

letの引数は:found_userというシンボルだけども要は、found_userという変数にその後のブロックの結果が入る、ということで、この場合はUserデータが入るわけです。

じゃあ、なんで

 fond_user = User.find_by_email(@user.email)

じゃあ、ダメなの?こんなよくわからん書き方せにゃならんの?と思うのが普通だと思う。
で、理由はここに書いてある。
http://jp.rubyist.net/magazine/?0035-RSpecInPractice#l8

で、終わるのもなんなので、続ける。

上記サイトにあるように、理由はいろいろあるけど、個人的には以下の2つがもっとも大きな理由だと思う。

テストのDRY化のため

shared_exampleなどと組み合わせるとテストのDRY化ができるから幸せになれる。

require 'spec_helper'

describe "Static pages" do

  subject { page }

  describe "Home page" do
    before { visit root_path }

    it { should have_selector('h1',    text: 'Sample App') }
    it { should have_selector('title', text: full_title('Home')) }
  end

  describe "Help page" do
    before { visit help_path }

    it { should have_selector('h1',    text: 'Help') }
    it { should have_selector('title', text: full_title('Help')) }
  end

  describe "About page" do
    before { visit about_path }

    it { should have_selector('h1',    text: 'About') }
    it { should have_selector('title', text: full_title('About Us')) }
  end
end

こんな感じのテストコードが・・・

describe "StaticPages" do

  subject { page }
  shared_examples_for "all static pages" do
    it {should have_selector('title', text: full_title(page_title))}
    it {should have_selector('h1', text: heading)}
  end

  describe "Home page" do
    before {visit root_path}
    let(:page_title) {"Home"}
    let(:heading) {"The Bus"}

    it_should_behave_like "all static pages"
  end

  describe "Help page" do
    before {visit help_path}
    let(:page_title) {"Help"}
    let(:heading) {"Help"}

    it_should_behave_like "all static pages"
  end

  describe "About page" do
    before {visit about_path}
    let(:page_title) {"About"}
    let(:heading) {"About"}

    it_should_behave_like "all static pages"
  end
end

こんな感じにまとめられます!ドライ!
※これらのコードもhttp://ruby.railstutorial.org/chapters/filling-in-the-layout?version=3.2#code:pretty_page_tests参照

letはmemoizeする

メモライズではない。memoizeである。
memoizeとは「結果のキャッシュ」みたいな意味と理解している。
memoize: http://en.wikipedia.org/wiki/Memoization

最初のサンプルコードの中では

    let(:found_user) { User.find_by_email(@user.email) }

このように、DBにアクセスして値をとってくる処理がある。
letは基本的(オプションで即時評価もできるらすい)に遅延で評価されるので、found_userが参照されたときにUser.find_by_emailが走る。
そしてletで宣言した変数は、その後のテストのitやbeforeブロックで使うことができるがletはこの結果をキャッシュ(?)するため、その後のテストで何度letを通ろうが、User.find_by_emailは1度しか呼ばれない。

DBアクセス関係の処理は、Webの中でもかなり重めな処理かつ、アプリの実装が進めばテストも相応の量になるので、実行時間の節約という意味でもこの機能はデカイと思う。

というワケでletはすすんで使おう!letは怖くないよ!いいやつだよ!