はじめに
今回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
- Factory Bot
-
コントローラスペック
-
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を使う方々には必見のものであると言えると思います。ですが、前述した通りテストに関しての教材ではないのでテストに関して学習したことがなければまずそちらを優先した方がいいかもしれません。