1時間でツイッターサービスを作ろう! | KRAY Inc
これをやってみたついでにRSpecでテストを書いてみました。
herokuももちろん試してみましたが、ここではテストに絞って書いてみたいと思います。
herokuもよさそうですね。
rochefort's twitter_helloworld at master - GitHub
続・1時間でツイッターサービスを作ろうにテストを書いてみた - うんたらかんたら日記
1時間でツイッターサービスを作ろうにテストを書いてみた2 - うんたらかんたら日記
どう書いて行くか考える
機能を分割すると
機能1:loginしている場合、message/index に「偉大なるHelloWorldをツイートする」buttonを表示させる
loginしていない場合、ログインへのリンクを表示させる
機能2:button押下時に、twitterへmessageをポストする
まずは、機能1をやってみます。
準備
Railsアプリを作る
Rails2.3.9で作ってしまいました。
$ rails twitter_helloworld $ cd twitter_helloworld $ git init $ script/plugin install git://github.com/mbleigh/twitter-auth.git $ script/generate twitter_auth $ rm public/index.html
config/twitter_auth.yml
ここは直書きでもいいですが、
YAMLにrubyのコードを書く方法 - うんたらかんたら日記
を使ってみます。
(※heroku使うなら、直書。)
$ vim config/initializers/env.rb ENV['OAUTH_CONSUMER_KEY'] = "" ENV['OAUTH_CONSUMER_SECRET'] = "" $ vim config/twitter_auth.yml oauth_consumer_key: <%= ENV['OAUTH_CONSUMER_KEY'] %> oauth_consumer_secret: <%= ENV['OAUTH_CONSUMER_SECRET'] %>
$ vim .gitignore tmp/pidsなどはherokuでは不要なため、-fでgitに追加はしていない。 (localと相違があるからやっておいた方がいいのかもしれない) .DS_Store db/*.sqlite3 log/*.log tmp/**/* config/initilizers/env.rb
rspecの準備をします。
$ script/generate rspec
一旦commit。
$ git add . $ git commit -m 'initial'
テストを書いていきます
コントローラ
通常modelからtestを書いて行くところですが
今回model部での特別な実装はないため(User < TwitterAuth::GenericUser)
controllerから書いてみます。
本来的にはテスト書いてからプロダクトコードの実装に移りますが
generate rspec_ とやるとテストファイルも作ってくれるので
generateを使います。
$ script/generate rspec_controller messages index $ rake db:migrate
自動生成されたテストが通るか確認します。
$ rake spec .... Finished in 0.132948 seconds 4 examples, 0 failures
spec/controllers/messages_controller_spec.rb を見ると
機能1のcontrollerのテストは自動生成されたテストで十分そうなので
特に追記はしていません(be_an_instance_of は消したので、examplesの数は3)。
ビュー
機能1のviewのテストを書いてみます。
loginしているかどうかというは一旦置いておいて、
「偉大なるHelloWorldをツイートする」ボタンが表示されている
ことを確認するテストを書いてみます。
spec/views/messages/index.html.erb_spec.rb
既存のテストは削除し、下記を追記。
#spec/views/messages/index.html.erb_spec.rb it "「偉大なるHelloWorldをツイートする」ボタンを表示すること" do response.should have_tag('input', :type => 'submit', :value => '偉大なるHelloWorldをツイートする') end
余談ですが、こんな書き方もできるそうです。
response.should have_tag "form[action=/messages]" do with_tag "input[type=submit][value='偉大なるHelloWorldをツイートする']" with_tag ... end
テストが失敗するのを確認します。
F.. 1) '/messages/index 「偉大なるHelloWorldをツイートする」ボタンを表示すること' FAILED Expected at least 1 element matching "input", found 0. <false> is not true. /Users/rochefort/work/rails/twitter_helloworld/spec/views/messages/index.html.erb_spec.rb:9: Finished in 0.099799 seconds 3 examples, 1 failure
viewを修正します。
#app/views/messages/index.html.erb <% form_tag messages_path do %> <%= submit_tag '偉大なるHelloWorldをツイートする' %> <% end %>
テストしてみるとroutingのエラーとなりました。
F.. 1) ActionView::TemplateError in '/messages/index 「偉大なるHelloWorldをツイートする」ボタンを表示すること' undefined local variable or method `messages_path' for #<ActionView::Base:0x103667958>
なのでroutes,rbを修正。
(createもすぐに作成するので、併せて修正)
$ vim config/routes.rb map.resources :messages,:only => [:index, :create] map.root :controller => 'messages', :action => 'index' $ rake routes (in /Users/rochefort/work/rails/twitter_helloworld) login /login {:action=>"new", :controller=>"sessions"} logout /logout {:action=>"destroy", :controller=>"sessions"} new_session GET /session/new(.:format) {:action=>"new", :controller=>"sessions"} edit_session GET /session/edit(.:format) {:action=>"edit", :controller=>"sessions"} session GET /session(.:format) {:action=>"show", :controller=>"sessions"} PUT /session(.:format) {:action=>"update", :controller=>"sessions"} DELETE /session(.:format) {:action=>"destroy", :controller=>"sessions"} POST /session(.:format) {:action=>"create", :controller=>"sessions"} oauth_callback /oauth_callback {:action=>"oauth_callback", :controller=>"sessions"} messages GET /messages(.:format) {:action=>"index", :controller=>"messages"} POST /messages(.:format) {:action=>"create", :controller=>"messages"} root / {:action=>"index", :controller=>"messages"}
テストが通りました。
.. Finished in 0.095787 seconds 3 examples, 0 failures
一旦commit。
続いてログインの有無
機能1のログインしている場合、していない場合の実装をしてみましよう。
ログイン判定はプラグインの機能なのでstubを使います。
(stubでいいと思ってるけどmockでもいいのか?)
#app/controllers/messages_controller.rb it "ログインしている場合、「偉大なるHelloWorldをツイートする」ボタンを表示すること" do template.stub(:logged_in?).and_return(true) response.should have_tag('input', :type => 'submit', :value => '偉大なるHelloWorldをツイートする') end
プロダクトコードは修正しなくてもテストが通ります。
続いて、ログインしていない場合のテストを書いてみます。
重複ロジックはテストが通ってから排除することにします。
#spec/controllers/messages_controller_spec.rb describe "/messages/index" do before(:each) do template.stub(:logged_in?).and_return(true) render 'messages/index' end it "ログインしている場合、「偉大なるHelloWorldをツイートする」ボタンを表示すること" do response.should have_tag('input', :type => 'submit', :value => '偉大なるHelloWorldをツイートする') end end describe "/messages/index" do before(:each) do template.stub(:logged_in?).and_return(false) render 'messages/index' end it "ログインしていない場合ログインリンクを表示すること" do response.should have_tag('a', :href => '/login') end end
エラーになります。
...F 1) '/messages/index ログインしていない場合ログインリンクを表示すること' FAILED Expected at least 1 element matching "a", found 0. <false> is not true. ./spec/views/messages/index.html.erb_spec.rb:16: Finished in 0.167695 seconds 4 examples, 1 failure
viewを修正します。
#app/views/messages/index.html.erb <% if logged_in? %> <% form_tag messages_path do %> <%= submit_tag '偉大なるHelloWorldをツイートする' %> <% end %> <% else %> <%= link_to 'ログインする', login_path %> <% end %>
テストは無事通りました。
テストをリファクタリング
ここでテストをリファクタリングします。
こうなりました。
#spec/controllers/messages_controller_spec.rb describe "/messages/index" do context "ログインしている場合" do before do template.stub(:logged_in?).and_return(true) render 'messages/index' end it "「偉大なるHelloWorldをツイートする」ボタンを表示すること" do response.should have_tag('input', :type => 'submit', :value => '偉大なるHelloWorldをツイートする') end end context "ログインしていない場合" do before do template.stub(:logged_in?).and_return(false) render 'messages/index' end it "ログインリンクを表示すること" do response.should have_tag('a', :href => '/login') end end end
$ spec spec/views/ -cfn /messages/index ログインしている場合 「偉大なるHelloWorldをツイートする」ボタンを表示すること ログインしていない場合 ログインリンクを表示すること Finished in 0.144496 seconds 2 examples, 0 failures
機能1の完了。8888。
個人的な収穫としては
・template.stub
・YAMLにrubyのコードを書く方法 - うんたらかんたら日記
・RSpecのstubとstub! - うんたらかんたら日記
勉強になりました。partialのテストも書いてみたいところ。
機能2は明日。テストコードはあとでgithubにでも置きます。
間違い等あればご指摘いただけると助かります。