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は怖くないよ!いいやつだよ!