0
0

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

Posted at

はじめに

今回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 ファイル名でシステムスペックのファイルを作成
    • システムスペックでは入力フォームへの入力など画面を操作して行うものをコードに起こしてテストする。visitclick_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を使う方々には必見のものであると言えると思います。ですが、前述した通りテストに関しての教材ではないのでテストに関して学習したことがなければまずそちらを優先した方がいいかもしれません。

0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0