RSpecを使ったテストコードを読もう:Railsコードリーディング〜scaffoldのその先へ〜(2)(4/4 ページ)
優れたプログラマはコードを書くのと同じくらい、コードを読みこなせなくてはならない。優れたコードを読むことで、自身のスキルも上達するのだ(編集部)
コントローラのスペックを読んでみよう
モデルのスペックでは、メソッドを呼び出して、その戻り値を確認するという単純な動きの確認で済みましたが、コントローラ部分のスペックについては、そうはいきません。RSpecではRails用の拡張として、コントローラの行う、画面からの処理の流れを確認することができるようになっています。その場合のスペックの記述は、モデルと少し流れが変わります。
では、第1回で紹介したGroupsControllerのindexアクションを例に説明していきましょう。
24 def index 25 params[:yet_participation] ||= false 26 params[:group_category_id] ||= "all" 27 params[:sort_type] ||= "date" 28 @format_type = params[:format_type] ||= "detail" 29 @group_counts, @total_count = Group.count_by_category 30 @group_categories = GroupCategory.all 31 32 options = Group.paginate_option(session[:user_id], params) 33 options[:per_page] = params[:format_type] == "list" ? 30 : 5 34 @pages, @groups = paginate(:group, options) 35 36 unless @groups && @groups.size > 0 37 flash.now[:notice] = '該当するグループはありませんでした。' 38 end 39 end
このindexというアクションは、グループの一覧を表示するためのアクションです。
Railsのコントローラの中でやるべきことは、大きく分けて以下の3つになります。
- パラメータを解析し、モデルに処理を渡す
- インスタンス変数(@マークの変数)や、flashに値を設定する
- 表示するviewを設定する/リダイレクトの場合はリダイレクト先を設定する
GroupsControllerのindexアクションで見てみましょう。
モデルに処理を渡している部分は、少し分かりにくいですが、34行目のpaginateメソッドです。このメソッドは、ページ遷移をするための処理で、この内部でGroupモデルにパラメータを渡してデータベースの検索を行っています。
インスタンス変数に関しては、28行目から34行目までで設定されています。flashは、必ずしも設定する必要はありませんが、今回は、検索結果に何も表示すべき項目がない場合、37行目でそのむねのメッセージを設定しています。
表示するviewは、ここでは何も指定していません。Railsの規約で、viewを指定していなければ、アクション名と同じviewが設定されることになっています。よって、index.html.erbというviewを表示することになります。
では、スペックを見てみましょう。ファイルは、モデルのときと同じでspecディレクトリに、_specを付けたファイル名になっています。
18 describe GroupsController do 19 before do 20 user_login # ログイン状態にしている 21 end 22 describe "#index" do 23 describe "グループが存在した場合" do 24 before do 25 @pages = mock('pages') 26 @groups = mock('groups', :size => 1) 27 controller.should_receive(:paginate).and_return([@pages, @groups]) 28 get :index 29 end 30 31 it { response.should be_success } 32 it { assigns[:format_type].should == "detail" } 33 it { assigns[:group_counts].should == Group.count_by_category.first } 34 it { assigns[:total_count].should == Group.count_by_category.last} 35 it { assigns[:pages].should == @pages } 36 it { assigns[:groups].should == @groups } 37 end 38 describe "グループが存在しなかった場合" do 39 before do 40 @pages = mock('pages') 41 @groups = mock('groups', :size => 0) 42 controller.should_receive(:paginate).and_return([@pages, @groups]) 43 get :index 44 end 45 46 it { response.should be_success } 47 it { flash[:notice].should_not be_nil } 48 end 49 end 50 end
コントローラのスペックは、前述のモデルの例より、少し複雑になっています。しかし、基本的な要素は、describe、before、itの3つなのは変わりありません。それでは、順に見てみましょう。
18、22、23行目でdescribeが存在します。ネストした状態になっていますが、このことは順番に前提条件が適用されていくことを示しています。23行目のdescribeの中で実行されるitでは、GroupsController(1つ目のdescribe)でindexアクション(2つ目のdescribe)にグループが存在した場合(3つ目のdescribe)という前提条件が設定された状態で実施されることになります。
また、コントローラのスペックのための新しい要素として28行目にgetというメソッドを使っています。これは、Rails用のRspecの拡張で、GroupsControllerのindexアクションに疑似的にHTTPのGETリクエストを送るということを示しています。Webブラウザから/groups/indexにアクセスがあったときと同じ状況を作り出してくれます。
3つの前提条件の状態をそれぞれのbeforeブロックで設定し、31行目からの検証のitのブロックに入っていきます。先ほど説明したコントローラのやるべきことができているか検証します。
31行目のresponseは、先ほどのgetメソッドで呼び出したリクエストのレスポンスに対応する結果にアクセスするメソッドです。そのレスポンスが、成功していることつまり、正しくrenderされたことを検証しています。
32行目から36行目でインスタンス変数に値が設定されていることを検証しています。assignsメソッドでは、getメソッドで呼び出されたときにインスタンス変数に設定された値にアクセスするメソッドです。
38行目から48行目までは、グループが見つからない場合のスペックになっています。ここまで読んだ読者であれば、どういったスペックかは説明をしなくても読めるようになっているのではないでしょうか。
コードリーディングの連載で、なぜRSpecを取り上げたのでしょうか。それは、Railsアプリケーションのソースコードを理解するうえで、RSpecのコードを読めることが、大変大きな助けになるからなのです。
今回のソースコードでも分かるとおり、スペックのコードと実際に動くコードは、裏返しになっています。スペックを読むことで、Railsアプリケーションの振る舞いを理解することができるのです。
RSpecのように、スペックを記述するためにRubyのオリジナルの文法を拡張してできた独自の言語のことを、DSL(Domain Specific Language:ドメイン特化言語)と呼びます。RSpecは、テストコード(スペック)を書くためのDSLというわけです。
ある意味、新しい言語なので、最初のハードルは少し高いかもしれませんが、一度覚えてしまえば、ルールに従って読み書きできるので、むしろ分かりやすくなります。今回の記事が読者の皆さまにとっての最初のハードルを越えるきっかけになれば幸いです。
Copyright © ITmedia, Inc. All Rights Reserved.