#author("2018-07-05T13:51:00+09:00","default:wikiwriter","wikiwriter") #author("2018-07-05T13:51:38+09:00","default:wikiwriter","wikiwriter") &tag(Railsテスティングガイド); *目次 [#b63ae818] #contents *関連ページ [#l728a547] -[[Rails テスティングガイド | Rails ガイド:https://railsguides.jp/testing.html]]…日本語版サイト -[[Testing Rails Applications — Ruby on Rails Guides:http://guides.rubyonrails.org/testing.html]]…公式サイト。日本語版は情報が古いのでこちらを参考にすること。 *参考情報 [#z8d31c99] * 1 Railsアプリケーションでテストを作成しなければならない理由 [#m7e16995] * 2 テストを導入する [#xe4e9d82] ** 2.1 Rails Sets up for Testing from the Word Go [#j52f7a32] -Railsプロジェクトを作成すると以下のようなフォルダが作成される #pre{{ $ ls -F test controllers/ helpers/ mailers/ system/ test_helper.rb fixtures/ integration/ models/ application_system_test_case.rb }} ** 2.2 The Test Environment [#v67cb360] -testはtest環境で実施される。 -config/database.ymlでテスト用のデータベースが設定できる。 ** 2.3 Rails meets Minitest [#ye73ffcd] -モデルを生成するとテストが自動的に作成される。 #pre{{ $ bin/rails generate model article title:string body:text ... create app/models/article.rb create test/models/article_test.rb create test/fixtures/articles.yml ... }} -test/models/article_test.rbは以下のような内容となる。 #pre{{ require 'test_helper' class ArticleTest < ActiveSupport::TestCase # test "the truth" do # assert true # end end }} -テストメソッド名は以下のどちらでもよい。 #pre{{ test "the truth" do assert true end def test_the_truth assert true end }} -失敗テスト。articleのtitleが設定されていないので以下のテストは失敗する。 #pre{{ test "should not save article without title" do article = Article.new assert_not article.save end }} -実行。本来失敗してほしいのだが成功してしまう。 #pre{{ $ bin/rails test test/models/article_test.rb }} -articleに検証メソッドを追加すると成功する。 #pre{{ class Article < ApplicationRecord validates :title, presence: true end }} ** 2.4 Available Assertions [#fbdc2b74] **2.5 Rails Specific Assertions [#se6208f2] **2.6 A Brief Note About Test Cases [#ib167b69] **2.7 The Rails Test Runner [#seb79b61] -bin/rails testコマンドで全てのテストを実行できる。 -特定のテストやメソッドを実行する場合。 #pre{{ $ bin/rails test test/models/article_test.rb $ bin/rails test test/models/article_test.rb -n test_the_truth $ bin/rails test test/models/article_test.rb:6 #行番号の指定 $ bin/rails test test/controllers #ディレクトリ }} *3 The Test Database [#a221eb97] -テスト用のデータベースが使われる。 -config/database.ymlで定義。 **3.1 Maintaining the test database schema [#cbfed533] -テストを実行するためには現在のデータがデータベースに反映されていることが必要。 -テストヘルパーは保留中のmigrationsが存在しないあかどうかをチェックする。 **3.2 The Low-Down on Fixtures [#q1f10dde] -Railsではフィクスチャと呼ばれる仕組みを利用してテストデータを準備っする。 -test/fixturesディレクトリ以下に存在しYAMLで定義。 #pre{{ # lo & behold! I am a YAML comment! david: name: David Heinemeier Hansson birthday: 1979-10-15 profession: Systems development steve: name: Steve Ross Kellock birthday: 1974-09-27 profession: guy with keyboard }} -関連の定義。 #pre{{ # In fixtures/categories.yml about: name: About # In fixtures/articles.yml first: title: Welcome to Rails! body: Hello world! category: about }} -fixturesのYAMLではERBを使用することもできる。 -テストの中で以下のように使用可能。 #pre{{ # this will return the User object for the fixture named david users(:david) # this will return the property for david called id users(:david).id # one can also access methods available on the User class david = users(:david) david.call(david.partner) }} * 4 Model Testing [#u1bb93a0] -モデルテストはモデルのテストを実行するもの。 -test/modelsディレクトリに作成する。 #pre{{ $ bin/rails generate test_unit:model article title:string body:text create test/models/article_test.rb create test/fixtures/articles.yml }} * 5 System Testing [#ubac64da] -システムテストは、アプリケーションとシステムの相互作用をテストする。 -リアルorヘッドレスブラウザを使用してテスト実行。 -Capybaraを利用する。 -railsではtest/systemディレクトリ以下に作成する。 #pre{{ $ bin/rails generate system_test users }} -以下の内容 #pre{{ require "application_system_test_case" class UsersTest < ApplicationSystemTestCase # test "visiting the index" do # visit users_url # # assert_selector "h1", text: "Users" # end end }} -デフォルトでシステムテストはSeleniumドライバで実行される。 ** 5.1 Changing the default settings [#e0f1f73e] -railsのシステムテストの設定は簡単に変更できる。 -例えばSeleniumからPoltergeistに変更したい場合Gemfileにpoltergeistを追加したあとで、テストファイルを以下のように変更する。 #pre{{ require "test_helper" require "capybara/poltergeist" class ApplicationSystemTestCase < ActionDispatch::SystemTestCase driven_by :poltergeist end class ApplicationSystemTestCase < ActionDispatch::SystemTestCase driven_by :selenium, using: :firefox end class ApplicationSystemTestCase < ActionDispatch::SystemTestCase driven_by :selenium, using: :headless_chrome end }} **5.2 Screenshot Helper [#x1e3cf99] -ScreenshotHelperはスクリーンショットヘルパーを取得するのを助ける。 -take_screenshotとtake_failed_screenshotが存在。 **5.3 Implementing a system test [#u21cd85c] -システムテストのスケルトンを生成する。 $ bin/rails generate system_test articles -テストファイルの中身。 #pre{{ require "application_system_test_case" class ArticlesTest < ApplicationSystemTestCase test "viewing the index" do visit articles_path assert_selector "h1", text: "Articles" end end }} -テストの実行。デフォルトでrails testではシステムテストを実行しないのに注意。 bin/rails test:system ★chromedriverが存在しないと実行できない。macOSの場合「brew cask install chromedriver」でインストール可能。 -新しい記事を作成するテスト。 #pre{{ test "creating an article" do visit articles_path click_on "New Article" fill_in "Title", with: "Creating an Article" fill_in "Body", with: "Created this article successfully!" click_on "Create Article" assert_text "Creating an Article" end }} -最初にarticles_pathに移動して、"New Article"ボタンをクリック。そしてタイトルとボディを埋めて保存する。 -システムテストはユーザーが利用しているソフトをシステムをテストする実情に最も近いテストとなる。 * 6 Integration Testing [#kb7fa84e] -統合テストはアプリケーションのさまざまな箇所が相互作用するさまをテストする。 -アプケーション内の重要なワークフローをテストする。 -railsでは統合テストはtest/integrationディレクトリに作成される。 -以下のように作成できる。 #pre{{ $ bin/rails generate integration_test user_flows }} -その中身。 #pre{{ require 'test_helper' class UserFlowsTest < ActionDispatch::IntegrationTest # test "the truth" do # assert true # end end }} -テストはActionDispatch::IntegrationTestを警鐘する。 ** 6.1 Helpers Available for Integration Tests [#r6b1a798] -ActionDispatch::IntegrationTestでは標準のテストに加え追加のヘルパーが使用できる。 --ActionDispatch::Integration::Runner --ActionDispatch::Integration::RequestHelpers --ActionDispatch::Integration::Session **6.2 Implementing an integration test [#a5bd52de] -ブログシステムに統合テストを追加する。 $ bin/rails generate integration_test blog_flow -BlogFlowTestを実装する。"/"を取得しh1タグの中身が"Welcome#index"であることを確認。なおこのテストを実行するにはwelcome_controllerを先に作っておかないと行けない(後述)。 #pre{{ require 'test_helper' class BlogFlowTest < ActionDispatch::IntegrationTest test "can see the welcome page" do get "/" assert_select "h1", "Welcome#index" end end }} -welcome_controllerの作成。 bundle exec rails generate controller Welcome index -routes.rbの設定。 #pre{{ Rails.application.routes.draw do get 'welcome/index' root 'welcome#index' resources :articles end }} -記事作成のテスト。 #pre{{ test "can create an article" do get "/articles/new" assert_response :success post "/articles", params: { article: { title: "can create", body: "article successfully." } } assert_response :redirect follow_redirect! assert_response :success assert_select "p", "Title:\n can create" # assert_select "p" do |element| # # 該当するNokogiri::XML::NodeSetがかえってくる(この場合3つの"p"要素のNokogiri::XML::Elementを含む) # # textでタグを除いた状態のテキストが取得できる。これとの比較になる。 # p element.text # end end }} * 7 Functional Tests for Your Controllers [#fa363cc5] -Railsではコントローラーの個別の(?)メソッドをテストするにはfunctional test(機能テスト)を使用する。 **7.1 What to include in your Functional Tests [#sb6d18bf] -以下の内容を機能テストに記述する --リクエストが成功したか --リダイレクトが正しいか。 --ユーザー認証が成功したか。 --正しいオブジェクトがtemplateに埋め込まれたか。 --適切なメッセージがビューに表示されたか。 -機能テストを作成するにはscaffoldを使用するのが簡単。test/controllers/articles_controller_test.rbがそれ。 $ bin/rails generate scaffold_controller article title:string -すでにコントローラーを作成済みで、7つのデフォルトアクションに対するテストを作成したい場合以下のように実行する。 $ bin/rails generate test_unit:scaffold article -テストの一つ。 #pre{{ # articles_controller_test.rb class ArticlesControllerTest < ActionDispatch::IntegrationTest test "should get index" do get articles_url assert_response :success end end }} -getメソッドwebリクエストを送信し、@responseで結果を受ける。以下のパラメータを受け取る。 --URI(例えばarticles_url)。 --params --headers --env --xhr --as -showアクションでリファラを設定する場合。 #pre{{ get article_url, params: { id: 12 }, headers: { "HTTP_REFERER" => "http://example.com/home" } }} -updateアクションをAjaxリクエストする場合。 #pre{{ patch article_url, params: { id: 12 }, xhr: true }} -記事作成メソッドのテスト(この前にテストを実行すると、新たに追加したvalidationにより失敗するとあるがデフォルト状態でも作成できそうだが…)。 #pre{{ test "should create article" do assert_difference('Article.count') do post articles_url, params: { article: { body: 'Rails is awesome!', title: 'Hello Rails' } } end assert_redirected_to article_path(Article.last) end }} ** 7.2 Available Request Types for Functional Tests [#zfe02798] -以下のリクエストタイプが使用できる。 --get --post --patch --put --head --delete ** 7.3 Testing XHR (AJAX) requests [#xcda9c3b] -AJAXリクエストをテストするには、「xhr: true」オプションを指定する。 #pre{{ test "ajax request" do article = articles(:one) get article_url(article), xhr: true assert_equal 'hello world', @response.body assert_equal "text/javascript", @response.content_type end }} ** 7.4 The Three Hashes of the Apocalypse [#jc03b04f] -requestが作成されて処理されると、以下の3種類のハッシュが利用できるようになる。 --cookies --flash --session -サンプル #pre{{ flash["gordon"] flash[:gordon] session["shmession"] session[:shmession] cookies["are_good_for_u"] cookies[:are_good_for_u] }} **7.5 Instance Variables Available [#r37ce069] -また以下のインスタンス変数にもアクセスできるようになる。 --@controller --@request --@@response -サンプル #pre{{ class ArticlesControllerTest < ActionDispatch::IntegrationTest test "should get index" do get articles_url assert_equal "index", @controller.action_name assert_equal "application/x-www-form-urlencoded", @request.media_type assert_match "Articles", @response.body end end }} ** 7.6 Setting Headers and CGI variables [#pf1913ac] -HTTPヘッダーとCGI変数をヘッダーとして設定できる。 #pre{{ # setting an HTTP Header get articles_url, headers: { "Content-Type": "text/plain" } # simulate the request with custom header # setting a CGI variable get articles_url, headers: { "HTTP_REFERER": "http://example.com/home" } # simulate the request with custom env variable }} **7.7 Testing flash notices [#jb55e33a] -flashのテスト。 -test_should_create_articleにassertionを追加する。 #pre{{ test "should create article" do assert_difference('Article.count') do post article_url, params: { article: { title: 'Some title' } } end assert_redirected_to article_path(Article.last) assert_equal 'Article was successfully created.', flash[:notice] end }} -テストを実行すると失敗する(が実際は成功した)。 -article.rbを変更する。 #pre{{ def create @article = Article.new(article_params) if @article.save flash[:notice] = 'Article was successfully created.' redirect_to @article else render 'new' end end }} ** 7.8 Putting it together [#nf982660] -その他のテスト。show #pre{{ test "should show article" do article = articles(:one) get article_url(article) assert_response :success end }} -destroy #pre{{ test "should destroy article" do article = articles(:one) assert_difference('Article.count', -1) do delete article_url(article) end assert_redirected_to articles_path end }} -update #pre{{ test "should update article" do article = articles(:one) patch article_url(article), params: { article: { title: "updated" } } assert_redirected_to article_path(article) # Reload association to fetch updated data and assert that title is updated. article.reload assert_equal "updated", article.title end }} ** 7.9 Test helpers [#g3fa1982] -重複をさけるためヘルパーが存在。 #pre{{ module SignInHelper def sign_in_as(user) post sign_in_url(email: user.email, password: user.password) end end class ActionDispatch::IntegrationTest include SignInHelper end require 'test_helper' class ProfileControllerTest < ActionDispatch::IntegrationTest test "should show profile" do # helper is now reusable from any controller test case sign_in_as users(:david) get profile_url assert_response :success end end }} * 8 Testing Routes [#l8b5c86a] -ルートのテストも可能。 -test/controllersなどでテストする。 *9 Testing Views [#j3cc3ffb] -ビューに対するテストではassert_selectが使用できる。 -assert_select(selector, [equality], [message]) : selectorを通じて選択された要素に対して等しいかどうか。 -assert_select(element, selector, [equality], [message]): elementからはじめてselectorを通じて選択された要素に対してひとしいかどうか。 -titleが文字列を含んでいることを確認する場合 assert_select 'title', "Welcome to Rails Testing Guide" -nestさせることもできる #pre{{ assert_select 'ul.navigation' do assert_select 'li.menu_item' end }} -選択された複数の要素は繰り返し呼び出すこともできる。 -例えば4つの要素を持つ二つの順序つきリストを処理する場合、以下のどちらもパスする。 #pre{{ assert_select "ol" do |elements| elements.each do |element| assert_select element, "li", 4 end end assert_select "ol" do assert_select "li", 8 end }} -https://github.com/rails/rails-dom-testing/blob/master/lib/rails/dom/testing/assertions/selector_assertions.rb *10 Testing Helpers [#y1c50370] -ヘルパーはシンプルなモジュール。 -ヘルパーに関するtest/helpersディレクトリ以下に置く。 -以下のヘルパーが存在するとき #pre{{ module UserHelper def link_to_user(user) link_to "#{user.first_name} #{user.last_name}", user end end }} -以下のテストを作成する #pre{{ class UserHelperTest < ActionView::TestCase test "should return the user's full name" do user = users(:david) assert_dom_equal %{<a href="/user/#{user.id}">David Heinemeier Hansson</a>}, link_to_user(user) end }} *11 Testing Your Mailers [#f2890d53] **11.1 Keeping the Postman in Check [#tf2a2a3b] -メーラーもテストする必要がある。 --メールが処理されるか --メールの内容が正しいか --正しいタイミングでメールが送信されたか。 -単体テストあるいは機能テストでテストできる。 **11.2 Unit Testing [#bc3ff60e] -単体テストを使用してメールの結果を比較する。 -メーラーをテストするためにフィクスチャを利用する。UserMailerをテストする場合test/fixtures_user_mailerディレクトリにフィクスチャを保存する。 -UserMailerのテスト #pre{{ require 'test_helper' class UserMailerTest < ActionMailer::TestCase test "invite" do # Create the email and store it for further assertions email = UserMailer.create_invite('me@example.com', 'friend@example.com', Time.now) # Send the email, then test that it got queued assert_emails 1 do email.deliver_now end # Test the body of the sent email contains what we expect it to assert_equal ['me@example.com'], email.from assert_equal ['friend@example.com'], email.to assert_equal 'You have been invited by me@example.com', email.subject assert_equal read_fixture('invite').join, email.body.to_s end end }} -メールを送信しその結果を検証している。 -inviteフィクスチャの中身。 #pre{{ Hi friend@example.com, You have been invited. Cheers! }} -config/environments/test.rbのActionMailer::Base.delivery_method = :testによりテストモードが設定され実際はメールが送信されない。 **11.3 Functional Testing [#f1eb5c12] -functional testでは配信のテストを行う。 #pre{{ require 'test_helper' class UserControllerTest < ActionDispatch::IntegrationTest test "invite friend" do assert_difference 'ActionMailer::Base.deliveries.size', +1 do post invite_friend_url, params: { email: 'friend@example.com' } end invite_email = ActionMailer::Base.deliveries.last assert_equal "You have been invited by me@example.com", invite_email.subject assert_equal 'friend@example.com', invite_email.to[0] assert_match(/Hi friend@example\.com/, invite_email.body.to_s) end end }} * 12 Testing Jobs [#c9a2d58f] -ジョブのテストも行う必要がある。 **12.1 A Basic Test Case [#h01f20db] -jobのテストはtest/job以下に作成される。 #pre{{ require 'test_helper' class BillingJobTest < ActiveJob::TestCase test 'that account is charged' do BillingJob.perform_now(account, product) assert account.reload.charged_for?(product) end end }} **12.2 Custom Assertions And Testing Jobs Inside Other Components [#kf361b2c] -job用のカスタムアサーションが多数存在する。 #pre{{ require 'test_helper' class ProductTest < ActiveJob::TestCase test 'billing job scheduling' do assert_enqueued_with(job: BillingJob) do product.charge(account) end end end }} *13 Additional Testing Resources [#o362bc7c] **13.1 Testing Time-Dependent Code [#b9948801] -時間に関連したためのテストを行うヘルパーメソッドも存在する。 -例えばtravel_to #pre{{ # Lets say that a user is eligible for gifting a month after they register. user = User.create(name: 'Gaurish', activation_date: Date.new(2004, 10, 24)) assert_not user.applicable_for_gifting? travel_to Date.new(2004, 11, 24) do assert_equal Date.new(2004, 10, 24), user.activation_date # inside the `travel_to` block `Date.current` is mocked assert user.applicable_for_gifting? end assert_equal Date.new(2004, 10, 24), user.activation_date # The change was visible only inside the `travel_to` block. }} **2.2 Railsを即座にテスト用に設定する [#qb174f1a] -プロジェクトを作成すると、以下のフォルダが自動的に作成される。 #pre{{ $ ls -F test controllers/ helpers/ mailers/ test_helper.rb fixtures/ integration/ models/ }} **2.3 フィクスチャのしくみ [#e38e1008] -フィクスチャ=サンプルデータ。 -YAMLで記述。test/fixtures以下。 #pre{{ david: name: David Heinemeier Hansson birthday: 1979-10-15 profession: Systems development steve: name: Steve Ross Kellock birthday: 1974-09-27 profession: guy with keyboard }} -YAMLはERBで処理される。 -フィクスチャはActive Recordオブジェクト。 #pre{{ # davidという名前のフィクスチャに対応するUserオブジェクトを返す users(:david) # idで呼び出されたdavidのプロパティを返す users(:david).id # Userクラスで利用可能なメソッドにアクセスすることもできる email(david.girlfriend.email, david.location_tonight) }} -フィクスチャとして容易したymlファイルはテストの実行前にテストDBにロードされる。 *3 モデルに対する単体テスト [#c65fc1b9] -articleモデルを生成。 #pre{{ $ bin/rails generate scaffold article title:string body:text }} -以下のようなテストクラスが生成される。 #pre{{ require 'test_helper' class ArticleTest < ActiveSupport::TestCase # test "the truth" do # assert true # end end }} **3.1 テストデータベースのスキーマを管理する [#re44c079] **3.2 テストを実行する。 [#pfc34540] -以下のように実行できる。 #pre{{ bin/rails test test/models/article_test.rb }} -特定のメソッドdけをテスト。 #pre{{ bin/rails test test/models/article_test.rb test_the_truth }} -失敗のテスト。article.rbを変更。 #pre{{ class Article < ActiveRecord::Base validates :title, presence: true end }} -テスト追加。 #pre{{ class ArticleTest < ActiveSupport::TestCase test "the truth" do assert true end test "should not save article without title" do article = Article.new assert_not article.save, "Saved the article without a title" end end }} **3.3 単体テストに含めるべき項目 [#j3ce5ae7] **3.4 利用可能なアサーション [#g83e6cdb] **3.5 Rails固有のアサーション [#y9342428] *4 コントローラの機能テスト [#t3c058fd] -1つのコントローラーに含まれる複数のアクションをテスト。 -機能テスト=functional test。 **4.1 機能テストに含める項目 [#y660cd93] -以下の項目を含める --Webリクエストが成功したか --正しいページにリダイレクトされたか --ユーザー認証が成功したか --レスポンスのテンプレートに正しいオブジェクトが保存されたか --ビューに表示されたメッセージは適切か -test/controllers/articles_controller_test.rb #pre{{ test "should get new" do get new_article_url assert_response :success end }} -showアクションに対するテストの場合。 #pre{{ test "should show article" do get article_url(@article) assert_response :success end }}