Ruby on Railsチュートリアル第3章
第3章(ほぼ静的なページの作成)
本章からは14章まで通しで取り組むsample appを作成していきます。
静的ページ
コントローラの生成
静的ページ用のコントローラをgenerateする。※コントローラの名前はモデルと違って複数形。またキャメルケース表記で記載することで、自動的にスネークケースのコントローラが生成される。
$ rails generate controller StaticPages home help
rails generate
やdb:migrate
の取り消し操作
rails generate
コマンドで生成したコントローラはrails destoy
で作成した一連の操作の取り消しが可能。- モデルについても同様に取り消しが可能。
rails db:migrate
を一つ前に戻す操作はrails dc:rollback
最初のテスト
ここからテストが入ってきます。テスト駆動開発についてはこの後まとめておきます。
テスト駆動開発は真面目にやっておきたいので、テスト内にコメントを残してテストの内容を視覚化しておくようにします。
[test/controllers/static_pages_controller_test.rb] require "test_helper" class StaticPagesControllerTest < ActionDispatch::IntegrationTest test "should get home" do get static_pages_home_url #home_urlへアクセス。 assert_response :success #HTTPステータスコード200を確認。 end test "should get help" do get static_pages_help_url #help_urlへアクセス。 assert_response :success #HTTPステータスコード200を確認。 end end
テストのGREENとREDの色付け、Guardによる自動化
テストは GREENなのかREDなのか視覚的に分かった方がよいので色付け(minitest-reporters gem)しておきます。
[test/test_helper.rb] ENV["RAILS_ENV"] ||= "test" require_relative "../config/environment" require "rails/test_help" require "minitest/reporters" Minitest::Reporters.use! class ActiveSupport::TestCase # 指定のワーカー数でテストを並列実行する parallelize(workers: :number_of_processors) # test/fixtures/*.ymlにあるすべてのfixtureをセットアップする fixtures :all # (すべてのテストで使うその他のヘルパーメソッドは省略) end
また、Guardによる自動化もやっておくとかなり便利でしたので今のうちに設定しておきます。
- gemのguard、guard-minitestを入れておく。(既にインストール済み)
$ bundle exec guard init
を実行。- Guadfileをカスタマイズ。
$ bundle exec guard
を実行。
Aboutページのテスト駆動開発
テストの内容は他のページと同じ。
[test/controllers/static_pages_controller_test.rb] require "test_helper" class StaticPagesControllerTest < ActionDispatch::IntegrationTest test "should get home" do get static_pages_home_url #home_urlへアクセス。 assert_response :success #HTTPステータスコード200を確認。 end test "should get help" do get static_pages_help_url #help_urlへアクセス。 assert_response :success #HTTPステータスコード200を確認。 end test "should get about" do get static_pages_about_url #about_urlへアクセス。 assert_response :success #HTTPステータスコード200を確認。 end end
aboutページへのルーティング、アクション、ビューの全てが出来ていないので作ります。(単純なので中身は省略)
テスト駆動開発に関して
テスト駆動開発には賛否あるようで、メリット・デメリットを挙げてみました。
以下のサイトとchatGPTへの質問を参考にしました。両者の意見は概ね一致してました。
techracho.bpsinc.jp
メリット
バグの減少と早期発見
テストを起点とした開発となるため、バグの早期発見に繋がる。参考サイトでもマイクロソフトでTDDを導入したものとしてないものでは、TDDを導入したことで欠陥密度が40~90%減少したと書いています。驚くべき数値ですね。
品質向上、シンプル化
TDDのサイクルにはリファクタリングも含まれており、自身のコードの品質改善にも寄与し、メンテナンス性も高いようです。
デメリット
開発時間の増加
これも一概には言えないようですが、”導入初期段階では遅くなる可能性がある”ようです。ただ、ここで時間が増加しても最終的にはバグの早期発見や品質の向上によってデメリットとならないとも考えられます。
Railsチュートリアルでも、絶対的にどっちが良いということは名言しておらず、場合分け的に後からテストするか先にテストする(TDD)かを選択すると良いとしておりました。
基本的にはTDDを優先的に考えて、動作の仕様が固まっていない場合やHTMLの細かい部分の修正などをおこなう場合はテストを後回しにすればよいのかなと考えています。
少しだけ動的なページ
ここではページのタイトルが表示ページに応じて動的に変わるように作成。
テストの記述
ここでは、assert_select
メソッドを使用。このメソッドの詳細はRailsガイドに記載。
Rails テスティングガイド - Railsガイド
[test/controllers/static_pages_controller_test.rb] require "test_helper" class StaticPagesControllerTest < ActionDispatch::IntegrationTest test "should get home" do get static_pages_home_url #home_urlへアクセス。 assert_response :success #HTTPステータスコード200を確認。 assert_select "title", "Home | Ruby on Rails Tutorial Sample App" #タイトルタグを検証。 end test "should get help" do get static_pages_help_url #help_urlへアクセス。 assert_response :success #HTTPステータスコード200を確認。 assert_select "title", "Help | Ruby on Rails Tutorial Sample App" #タイトルタグを検証。 end test "should get about" do get static_pages_about_url #about_urlへアクセス。 assert_response :success #HTTPステータスコード200を確認。 assert_select "title", "About | Ruby on Rails Tutorial Sample App" #タイトルタグを検証。 end end
このテストに合うように、各viewを編集。Homeのみ記載。
[app/views/static_pages/home.html.erb] <!DOCTYPE html> <html> <head> <title>Home | Ruby on Rails Tutorial Sample App</title> </head> <body> <h1>Sample App</h1> <p> This is the home page for the <a href="https://railstutorial.jp/">Ruby on Rails Tutorial</a> sample application. </p> </body> </html>
リファクタリング
自力でリファクタリングをやっていく。
ここでは、home、help、aboutページが同じ形になるようにERBを使用して記述を変え、最終的にlayputs/application.html.erbにまとめる。
まず、各ビューはprovideメソッド+yieldメソッドの関係を用いる。
[app/views/static_pages/home.html.erb] <% provide(:title,'Home') %> <!DOCTYPE html> <html> <head> <title><%= yield(:title) %> | Ruby on Rails Tutorial Sample App</title> </head> <body> <h1>Help</h1> <p> Get help on the Ruby on Rails Tutorial at the <a href="https://railstutorial.jp/help">Rails Tutorial help page</a>. To get help on this sample app, see the <a href="https://railstutorial.jp/#ebook"> <em>Ruby on Rails Tutorial</em> book</a>. </p> </body> </html>
layputs/application.html.erbにまとめる。
[app/views/layouts/application.html.erb] <!DOCTYPE html> <html> <head> <title><%= yield(:title) %> | Ruby on Rails Tutorial Sample App</title> <meta name="viewport" content="width=device-width,initial-scale=1"> <%= csrf_meta_tags %> <%= csp_meta_tag %> <%= stylesheet_link_tag "application", "data-turbo-track": "reload" %> <%= javascript_importmap_tags %> </head> <body> <%= yield %> </body> </html>
各ページも必要最低限な要素のみにリファクタリング。
※
[app/views/static_pages/home.html.erb] <% provide(:title,'Home') %> <body> <h1>Sample App</h1> <p> This is the home page for the <a href="https://railstutorial.jp/">Ruby on Rails Tutorial</a> sample application. </p> </body>
一応これで、テストはGREENで、各ページにアクセスした際のタイトルも変わってたのでok。
正解の方のレイアウトには<meta charset="utf-8">
も入っていた。
また、csp_meta_tag
ではクロスサイトスクリプティング攻撃対策用に、コンテンツセキュリティポリシーを実装。
csrf_meta_tags
はクロスサイトリクエストフォージェリー攻撃対策用。
第3章ー演習ー
3.2.1
①Fooというコントローラを生成し、その中にbarとbazアクションを追加してみてください。
(解答)
rails g controllerでFooコントローラを作成。
$ rails g controller Foo bar baz create app/controllers/foo_controller.rb route get 'foo/bar' get 'foo/baz' invoke erb create app/views/foo create app/views/foo/bar.html.erb create app/views/foo/baz.html.erb invoke test_unit create test/controllers/foo_controller_test.rb invoke helper create app/helpers/foo_helper.rb invoke test_unit
②コラム 3.1で紹介したテクニックを駆使して、Fooコントローラとそれに関連するアクションを削除してみてください。
(解答)
rails destroy controllerでFooコントローラを削除。
$ rails destroy controller Foo bar baz remove app/controllers/foo_controller.rb route get 'foo/bar' get 'foo/baz' invoke erb remove app/views/foo remove app/views/foo/bar.html.erb remove app/views/foo/baz.html.erb invoke test_unit remove test/controllers/foo_controller_test.rb invoke helper remove app/helpers/foo_helper.rb invoke test_unit
3.4.2
①StaticPagesコントローラのテスト(リスト 3.26)には、いくつか繰り返しがあったことにお気づきでしょうか? 特に「Ruby on Rails Tutorial Sample App」という基本タイトルは、各テストで毎回同じ内容を書いてしまっています。そこで、setupという特別なメソッド(各テストが実行される直前で実行されるメソッド)を使って、この問題を解決したいと思います。まずは、リスト 3.32のテストが green になることを確認してみてください(リスト 3.32では、2.2.2で少し触れたインスタンス変数や文字列の式展開というテクニックを使っています。それぞれ4.4.5と4.2.1で詳しく解説するので、今はわからなくても問題ありません)
(解答)
assert_select
で評価するタイトルタグの記述が重複している。Ruby on Rails Tutorial部分はsetupメソッドでまとめ、式展開で記述。
[test/controllers/static_pages_controller_test.rb] require "test_helper" class StaticPagesControllerTest < ActionDispatch::IntegrationTest def setup @base_title = "Ruby on Rails Tutorial Sample App" end test "should get home" do get static_pages_home_url #home_urlへアクセス。 assert_response :success #HTTPステータスコード200を確認。 assert_select "title", "Home | #{@base_title}" #タイトルタグを検証。 end test "should get help" do get static_pages_help_url #help_urlへアクセス。 assert_response :success #HTTPステータスコード200を確認。 assert_select "title", "Help | #{@base_title}" #タイトルタグを検証。 end test "should get about" do get static_pages_about_url #about_urlへアクセス。 assert_response :success #HTTPステータスコード200を確認。 assert_select "title", "About | #{@base_title}" #タイトルタグを検証。 end end
3.4.3
①サンプルアプリケーションにContact(問い合わせ先)ページを作成してください17 。(ヒント: まずはリスト 3.17を参考にして、/static_pages/contactというURLのページに「Contact | Ruby on Rails Tutorial Sample App」というタイトルが存在するかどうかを確認するテストを最初に作成しましょう。次に、3.3.3でAboutページを作ったときのと同じように、Contactページにもリスト 3.42のコンテンツを表示してみましょう。)
(解答) 同じ内容なので飛ばします。
3.4.4
①リスト 3.43にrootルーティングを追加したことで、root_urlというRailsヘルパーが使えるようになりました(以前、static_pages_home_urlが使えるようになったときと同じです)。リスト 3.44の(コードを書き込む)と記された部分を置き換えて、rootルーティングのテストを書いてみてください。
(解答)
test "should get root" do get root_url #root_urlへアクセス。 assert_response :success #HTTPステータスコード200を確認 end
②実はリスト 3.43のコードを書いていたので、先ほどの課題のテストは既に green になっているはずです。このような場合、テストを変更する前から成功していたのか、変更した後に成功するようになったのかを判断するのが難しくなります。リスト 3.43のコードがテスト結果に影響を与えていることを確認するため、リスト 3.45のようにrootルーティングを試しにコメントアウトし、 red になるかどうか確かめてみましょう(なおRubyのコメント機能については4.2で説明します)。最後に、コメントアウトした箇所を元に戻し(すなわちリスト 3.43に戻し)、テストが green になることを確認してみましょう。
(解答) rootルーティングをコメントアウトしたらテスト失敗になった。
第3章ーまとめー
- コントローラを
generate
するときは、複数形+キャメルケースで記述。一般にファイル名はスネークケースで記載される。 rails generate
で作成したコントローラやモデルは、付随して作成されたファイルも含めてdestroy
コマンドでまとめて削除可能。DBのマイグレーションもロールバック可能。- テスト駆動開発は、バグの発見やコードリファクタリング、メンテナンス性といった観点で非常に優れる手法。後からテスト手法も組み合わせて効率的に進める。
- コードのリファクタリングはシンプル化、整頓の観点から非常に大事。
- テストはGuardで自動化が可能。色付けしておくと、成功したか失敗したかが一目でわかる。
第3章終わり🐻