「偉大なるHelloWorld」をつぶやいている一覧を抽出する
機能追加をしてみます。
rochefort's twitter_helloworld at master - GitHub
1時間でツイッターサービスを作ろうにテストを書いてみた2 - うんたらかんたら日記
1時間でツイッターサービスを作ろうにテストを書いてみた - うんたらかんたら日記
下調べ
twitter-authは基本はget/postのようなので
http://search.twitter.com を使った場合どういう値が返るかconsoleで確認してみます。
a = me.twitter.get('http://search.twitter.com/search.json?q=ruby') >> pp a {"max_id"=>26835747788, "results"=> [{"created_at"=>"Sat, 09 Oct 2010 11:40:15 +0000", "profile_image_url"=> "http://a1.twimg.com/profile_images/1045704045/linkedin-logo_normal.jpg", "from_user"=>"linkedinevents", "text"=> "New Event: Ruby Social Club Milano. As usual, we’ll have lightning talks and short live coding demos. Anyone is... http://bit.ly/bhJOD6", "to_user_id"=>nil, "metadata"=>{"result_type"=>"recent"}, "id"=>26835747788, "geo"=>{"coordinates"=>[45.507, 9.2299], "type"=>"Point"}, "from_user_id"=>130219011, "iso_language_code"=>"en", "source"=> "<a href="http://www.linkedin.com/" rel="nofollow">Linkedin Events</a>", "place"=> {"id"=>"e6b05bc6fa05d24b", "type"=>"neighborhood", "full_name"=>"Monza, Milano"}}, 略 "since_id"=>0, "refresh_url"=>"?since_id=26835747788&q=ruby", "next_page"=>"?page=2&max_id=26835747788&q=ruby", "page"=>1, "results_per_page"=>15, "completed_in"=>0.051111, "query"=>"ruby"}
resultsにtweet情報が配列で格納されているのが分かります。
時間と名前を出力するviewを作ってみることにします。
いざテスト
またmodelには修正はないのでcontrollerから。
とりあえず/messages/listにGETした場合、status=200となるようにテストを書いてみます。
#spec/controllers/message_controller_spec.rb describe "GET 'list'" do it "should be successful" do get 'list' response.should be_success end end
エラーとなることを確認し、プロダクトコードを書きます。
#app/models/messages_controller.rb def list end #app/views/messages/list.html.erb を作成。 #config/routes.rb map.list 'list', :controller => 'messages', :action => 'list'
これで通りました。
さらにテストを書いていきます
searchの結果を@statusesにセットすることを想定します。
#spec/controllers/message_controller_spec.rb describe "GET 'list'" do context "偉大なるHelloWorldを検索し、検索結果が0件でない場合" do before do get_search_stub.and_return(dummy_search) get 'list' end it "should be successful" do response.should be_success end it "@statusesが空でないこと" do assigns[:statuses].should_not be_empty end end end def post_tweet_stub controller.stub_chain(:current_user, :twitter, :post).with('/statuses/update.json', :status => "偉大なるHelloWorld") end def get_search_stub controller.stub_chain(:current_user, :twitter, :get).with("http://search.twitter.com/search.json?q=偉大なるHelloWorld") end def dummy_search {"max_id"=>26835747788, "results"=>(1..3).map{ |tid| dummy_tweet(tid) } } end def dummy_tweet(id) {"id"=>id, "created_at"=>"Sat, 09 Oct 2010 11:40:15 +0000", "profile_image_url"=>"http://example.com/profile_images/pi.png", "from_user"=>"username#{id}", "text"=>"偉大なるHelloWorld", "to_user_id"=>nil, "geo"=>nil, "from_user_id"=>id, "iso_language_code"=>"ja", "source"=>"web"} end
テストの失敗を確認。
$ spec spec/controllers .F............ 1) NoMethodError in 'MessagesController GET 'list' 偉大なるHelloWorldを検索し、検索結果が0件でない場合 @statusesが空でないこと' You have a nil object when you didn't expect it! You might have expected an instance of Array. The error occurred while evaluating nil.empty?
プロダクトコードを書く。
#app/models/messages_controller.rb def list @statuses = current_user.twitter.get('http://search.twitter.com/search.json?q=偉大なるHelloWorld') end
テストの成功を確認。
テストを修正。
#spec/controllers/message_controller_spec.rb it { response.should be_success } it { response.should render_template('list') } it { assigns[:statuses].should_not be_empty }
red / green / refactoring!!
次はview
spec/views/messages/list.html.erb_spec.rbを作成
viewから使用するため
dummy_search、dummy_tweetをspec_helperへ。
移動後、controllerのテストを実施。問題ないことを確認。
#spec/views/messages/list.html.erb_spec.rb require 'spec_helper' describe "/messages/list" do before do assigns[:satatuses] = dummy_search render 'messages/list' end it "「偉大なるHelloWorld」のつぶやき一覧を表示すること" do response.should have_tag('#tweet1', /username1/) response.should have_tag('#tweet2', /username2/) response.should have_tag('#tweet3', /username3/) end end
#app/views/messages/list.html.erb <table> <tr> <th>id</th> <th>name</th> <th>date</th> </tr> <%- @statuses["results"].each_with_index do |t,i| -%> <tr id="tweet<%=i+1%>"> <td><%= t["id"].to_s %></td> <td><%= t["from_user"] %></td> <td><%= t["created_at"] %></td> </tr> <%- end -%> </table>
テストはこれで通ります。
が、実際にscript/serverで動かしてみると
エラーとなります。
URI::InvalidURIError in MessagesController#list bad URI(is not URI?): http://search.twitter.com/search.json?q=偉大なるHelloWorld
queryに日本語をそのまま渡しているのが原因でした。
url encodeさせるとうまいくいくので、テストも併せて修正。
※url encodeについてまとめ記事かきました。
※ので、CGI.escapeは、ERB::Util.u に置き換えてください。
rubyでurlのencode - うんたらかんたら日記
#app/models/messages_controller.rb def list @statuses = current_user.twitter.get("http://search.twitter.com/search.json?q=#{CGI.escape '偉大なるHelloWorld'}") end #spec/controllers/message_controller_spec.rb def get_search_stub controller.stub_chain(:current_user, :twitter, :get).with("http://search.twitter.com/search.json?q=#{CGI.escape '偉大なるHelloWorld'}") end
テスト結果
$ spec spec -cfn MessagesController GET 'index' should be successful POST 'create' tweetに成功した場合 should eql "おめでとう!偉大なるHelloWorldは成功した。" should redirect to "/" tweetに失敗した場合 should eql "残念だが、偉大なるHelloWorldは失敗に終わった。" should render template "index" should be success tweet時にduplicateエラーによる例外が発生した場合 should eql "連投エラーです。" should render template "index" should == "500" tweet時に予期せぬエラーによる例外が発生した場合 should eql "予期せぬエラーが発生しました。" should render template "index" should == "500" GET 'list' 偉大なるHelloWorldを検索し、検索結果が0件でない場合 should be success should render template "list" should not be empty MessagesHelper /messages/index ログインしている場合 「偉大なるHelloWorldをツイートする」ボタンを表示すること ログインしていない場合 ログインリンクを表示すること /messages/list 「偉大なるHelloWorld」のつぶやき一覧を表示すること Finished in 0.169787 seconds 19 examples, 0 failures
やはり、itにまかせた部分のメッセージが気になります。
ここは、subject/itsをうまく使えばクリアに出来ると思いますが
subjectの対象が一連のテストの中で微妙に異なるので
どうしたもんかと思い、とりあえず使用しないままにしています。
あとは
0件の場合とか
indexからlinkさせるとか
エラーハンドリング追加するとかあるとは思うけど
飽きてきたので終了にします。