Everyday Rails - RSpecによるRailsテスト入門を読んだ

# はじめに
今回railsの学習に伴いテストフレームワークの学習として
Everyday Railsを読みましたのでそのアウトプットとして本記事を作成しました。
# 学んだこと
* **意味のあるテストデータの作成**
  * Factory Bot
    * テストにおけるサンプルデータを作成するためのgem
    * ```bin/rails g factory_bot:model モデル名```でファクトリのファイルを作成
    * 以下ファクトリの構成例。用意するとスペック全体でファクトリが使用可能になる。
      ```
      FactoryBot.definedo
        factory :モデル名 do
          //データを囲む中括弧（{}）は必須
          カラム1 { "test1" }
          カラム2 { true }
          カラム3 { 1234 }
        end
      end
      ```
    * ```FactoryBot.build(:モデル)```のような形で各スペックで使いまわせる。buildはインスタンスを作るだけで保存はしない。
    * ユニークバリデーションがある属性をテストするときにはシーケンスが便利。
      ```
      FactoryBot.definedo
        factory :モデル名 do
          //データを囲む中括弧（{}）は必須
          カラム1 { "test1" }
          カラム2 { true }
          カラム3 { 1234 }
          //ファクトリからデータを作成するたびにn+1してユニークな値にできる
       　　　　　　suquence(:モデル名) { |n| "tester#{n}@example.com"}
        end
      end
      ```
    * 以下のように記載すると関連モデルを扱える
      ```
      FactoryBot.definedo
        factory :モデル名 do
          カラム1 { "test1" }
          associate :モデル名
          associate :モデル名
        end
      end
      ```
    * 同じ型を作成するファクトリを複数定義することができる
      ```
      FactoryBot.definedo
        factory :testA do
          //データを囲む中括弧（{}）は必須
          カラム1 { "test1" }
          カラム2 { true }
          カラム3 { 1234 }
        end

        //classでインスタンスとして呼ぶクラス名を指定する
        factory :testA_2,class: TestA do
          //データを囲む中括弧（{}）は必須
          //testAとは別の値を指定する
          カラム1 { "test2" }
          カラム2 { false }
          カラム3 { 5678 }
        end
      end
      ```
    * 重複したコードを減らす2つのテクニック
      * 継承を使用してユニークな属性のみ変える
      ```
      FactoryBot.definedo
        //testAの内部に入れ子にして継承先のファクトリを追加する
        factory :testA do
          //データを囲む中括弧（{}）は必須
          カラム1 { "test1" }
          カラム2 { true }
          カラム3 { 1234 }
      
          //継承先のファクトリ
          factory :testA_2 do
            カラム1 { "test2" }
          end
        end
      end
      ```
      * トレイト(trait)を使ってテストデータを構築する
      ```
      FactoryBot.definedo
        factory :testA do
          カラム1 { "test1" }
          カラム2 { true }
          カラム3 { 1234 }
      
          //traitで異なる値のデータを作成
          trait :testA_2 do
            カラム1 { "test2" }
          end
        end
      end
      ```
* **コントローラスペック**
  * ```bin/rails g rspec:controller 〇〇（コントローラ名） --controller-specs --no-request-specs```でコントローラのテストファイルを作成
    ```
    require'rails_helper'
    
    RSpec.describe 〇〇Controller, type: :controllerdo
    
    end
    ```
  * アクション単位でテストを記述していく
    ```
    RSpec.describeHomeController,type::controllerdo
      //indexアクションに関するテスト
      describe "#index" do
        //indexアクションでのHTTPレスポンスが帰ってくるかをテスト
        it "responds successfully" do
          get :index
          expect(response).to be_successful
        end
      end

      //showアクションに関するテスト
      describe "#show" do
        //showアクションでのHTTPレスポンスが帰ってくるかをテスト
        it "responds successfully" do
          get :show
          expect(response).to be_successful
        end
      end  
    end

    ```
  * ```bundle exec rspec spec/controllers```でコントローラのテストのみ実行
  * ```spec/rails_helper.rb```に以下のように記載することでDeviceのヘルパーメソッドをテスト内で使用できる
    ```
    RSpec.configuredo|config|
      # 設定ブロックの他の処理は省略 ...
    
      # コントローラスペックで Devise のテストヘルパーを使用する
      config.include Devise::Test::ControllerHelpers, type: :controller
    end
    ```
* **システムスペックでUIをテストする**
  * システムスペックはそれぞれのモデルやコントローラが一緒に動作するかどうかを確認するテストで受け入れテスト、統合テストとも呼ばれる
  * Capybara
    * システムスペックで使用されるgem
    * リンクのクリック、入力フォームへの入力、画面の表示の検証を行える
  * ```rails generate rspec:system ファイル名```でシステムスペックのファイルを作成
  * システムスペックでは入力フォームへの入力など画面を操作して行うものをコードに起こしてテストする。```visit```や```click_link```などはCapybaraのDSLが提供しているメソッド
    ```
       RSpec.describe"Projects",type::systemdo
    　　   before do
    　　    driven_by(:rack_test)
    　　   end
    
    　　   # ユーザーは新しいプロジェクトを作成する
    　　   scenario "user creates a new project" do
    　　　　  user = FactoryBot.create(:user)
    　　　　
    　　　　  visit root_path
    　　　　  click_link "Sign in"
    　　　　  fill_in "Email", with: user.email
    　　　　  fill_in "Password", with: user.password
    　　　　  click_button "Log in"
    　
    　　　　  expect {
    　　　　   click_link "New Project"
    　　　　   fill_in "Name", with: "Test Project"
    　　　　   fill_in "Description", with: "Trying out Capybara"
    　　　　   click_button "Create Project"
    　
    　　　　   expect(page).to have_content "Project was successfully created"
    　　　　   expect(page).to have_content "Test Project"
    　　　　   expect(page).to have_content "Owner: #{user.name}"
    　　　　  }.to change(user.projects, :count).by(1)
    　　　　  end
    　　　　 end
    ```
   * システムスペックでのデバックの手段として```save_and_open_page```を失敗したと見られる箇所の前に記述するとRailsがブラウザに返したHTMLの確認ができる。
     ```
     　　 scenario "guestaddsaproject" do
     　　   visit projects_path
     　　   save_and_open_page
     　　   click_link "New Project"
     　　 end
     ```
* **スペックを DRY に保つ**
  * コードをサポートモジュールに切り出すことによって重複を防止できる。モジュールは```spec/support```ディレクトリに作成する
    ```
    　　 moduleLoginSupport
    　　   def sign_in_as(user)
    　　     visit root_path
    　　     click_link "Sign in"
    　　     fill_in "Email", with: user.email
    　　     fill_in "Password", with: user.password
    　　     click_button "Log in"
    　　   end
    　　 end
    　　　　  //以下の設定でモジュールをincludeする
    　　　　 RSpec.configuredo|config|
    　　　　 config.include LoginSupport
    　　　　 end
    ```
  * サポートモジュールに切り出す方法はシステムスペックでよく使われる技法
  * let
    * ```before```での処理はdescribeやcontextの処理の前に毎回実行され予期せぬ影響を及ぼすことがあり、要件が増えるにつれて可読性が悪くなる。これは遅延読み込みを実現するletで解決できる
    * スペックの外で定義しメソッドのように使う。beforeでインスタンス変数を定義する処理をletで置き換えられる
      ```
      RSpec.describeTask,type::modeldo
        let(:project) { FactoryBot.create(:project) }

        it "is valid with a project and name" do
            task = Task.new(
            //letで定義して処理が行われる
            project: project,
            name: "Test task",
            )
            expect(task).to be_valid
        end
      end
      ```
    * ```let!```で遅延読み込みをせず各exampleの実行前に処理が行われる。beforeと同じイメージ

  * カスタムマッチャ
    * 自分の独自のマッチャを作成でき、それをカスタムマッチャという
    * spec/support/matchersの配下のディレクトリにカスタムマッチャ用のファイルを作成する
    * 以下のようにカスタムマッチャを作成する
      ```
      RSpec::Matchers.define :example do |expected|
       //マッチャにはmatchが必要
       match do |actual|
        //処理内容を記載
        //expectedは期待値
        //actualは期待値と比較する値
       end
      end
      ```
    * exmpleの中でカスタムマッチャ用のファイル内で定義したマッチャ名を記載することで呼び出せる
      ```
      describe "#show" do
        it "test_example" do
          expect(response).to example
        end
       end
      ```
  * aggregate_failures
    * 失敗する```expect```はそこでテストが停止してしまうが、```aggregate_failures```を使用することによってその場で停止せず次の処理も実行させることができる
    * ```aggregate_failures```ブロック内に失敗する処理を集約させる
      ```
      aggregate_failures do
       expect(response).to be_successful
       expect(response).to have_http_status "200"
      end
      ```
# 難しかったこと
* 本書にも書いてありますがrspecは書き方が独特なため慣れるのに時間がかかると思いました。また、本記事では記載していないメソッド等が多くありそれらを理解し使いこなすにはかなり多くのテストを実際に書かなければならないでしょう。
* 本書はrspecに関してのものでありテストそのものに関してのものではないと読んで感じました。例えばテスト駆動開発についてのセクションがありますが、そこではテスト駆動開発に関して細かく説明はしていません。本書を学習する前にまずテストに関する教材を一つこなしてから本書を読む方がいいと思います。
# 最後に
Everyday Railsはrspecに関する唯一の参考書です。ボリュームに関しても申し分なく、これからrspecを使う方々には必見のものであると言えると思います。ですが、前述した通りテストに関しての教材ではないのでテストに関して学習したことがなければまずそちらを優先した方がいいかもしれません。