【RSpec単体テストの基本】何をテストするのか、利用するgem、作業の流れ、よく使うマッチャ他
- 結局テストで何を確認すべきなのか
- 単体テストで利用するgemと導入方法
- gem を bundle install した後の流れ
- deviseをrspecで使えるようにする
- モデルのテストコードの基本構造
- コントローラーのテストコードの基本構造
- よく使う単体テストのマッチャ(随時更新)
- よく使われるFactoryBotのメソッド(随時更新)
- よく使うFaker(随時更新)
- バリデーションのテストの具体例
- ■エラー備忘録(思わぬところでつまづいた!?)
【参考記事】
https://leanpub.com/everydayrailsrspec-jp/read#leanpub-auto-section-19
https://language-and-engineering.hatenablog.jp/entry/20091023/p1
単体テストでは、①「モデル」②「コントローラー」を1つずつテストする
結局テストで何を確認すべきなのか
①モデルの場合
・DBに対して,期待通りの操作が行なえているかどうか
・モデルの全メソッドを網羅
・バリデーション等の個々の性質・挙動もテスト
・某スクールのテキストに書いていたのは(1)のみだったが、念の為。
(1)バリデーション
オブジェクトがデータベースに保存される前にオブジェクトの状態を検証するバリデーション。そのバリデーションが本当に働いているのかを確認するテスト。
【例】
●名前、メアド、パスワードがあれば有効であること
(必要情報を入力していればバリデーションをクリアできるか)
●名前・メアドがなければ無効であること
(情報が不足していればバリデーションでブロックできるか)
●メアドが重複している場合は無効であること
(情報が重複している場合バリデーションでブロックできるか)
(2)インスタンスメソッド
モデル内に、インスタンスメソッドが定義されている場合に、そのメソッドが正しく動いているのか、期待する値を返すのかをテストする。
【例】
●モデルに「姓」「名」を結合する以下のようなインスタンスメソッドが定義されている場合
def
name
[
firstname
,
lastname
].
join
(
' '
)
end
(3)クラスメソッドとスコープ
検索等が正しく動作するか、検索条件にあった内容が返されるかをテストする。
【例】
●モデルに、searchメソッドやsearchスコープが定義されている場合
②コントローラーの場合
■某スクールのカリキュラム
1.アクション内で定義されているインスタンス変数の値が期待したものになるか
2.アクションの持つビューに正しく遷移するか
■参考サイト①
・リクエストに対して,期待通りのレスポンスが返ってくるか
・publicな全アクションを網羅
・「コントローラ」と「ビュー」をテスト
■参考サイト②
・Webリクエストが成功したか
・正しいページにリダイレクトされたか
・ユーザー認証が成功したか
・レスポンスのテンプレートに正しいオブジェクトが保存されたか
・ビューに表示されたメッセージは適切か
■参考サイト④
・受信したリクエストに対して適切なレスポンスを返すか
(例)リクエストに対してHTTPレスポンスがステータスコード200を返す
・ビューで使用するのに必要なモデルオブジェクトをロードするか
(例)リクエストされたURLから必要なモデルインスタンスをロード
・レスポンスを表示するのに適切なビューを選択するか
(例)適切なテンプレートを表示している
https://blog.naichilab.com/entry/2016/01/19/011514
単体テストで利用するgemと導入方法
●RSpec
・gem
・フレームワーク(必要な一般的な機能が、あらかじめ別に実装されたもの)
・RubyやRuby on Railsで作ったクラスやメソッドを
テストするためのドメイン特化言語 (DSL)を使ったフレームワーク
・要はテスト用のプログラミング言語
・development環境と test環境にインストールする
group :development, :test do #省略 gem 'rspec-rails' end
●rails-controller-testing
・gem
・コントローラーの単体テストで利用するgem
group :development, :test do #省略 gem 'rails-controller-testing' end
●web_console
・gem
・Ruby on Railsで作られたアプリ用のデバッグツール
・Rails 4.2以降は標準装備されている
・development環境のみにインストールする
group :development do gem 'web-console' end
※もしdevelopment環境以外にもインストールされていたらGemfileの記述を移動する
・デフォルトエラーページ用のデバッギングツールで、
ブラウザ上からインタラクティブにconsoleの操作が出来る
・エラーページだけでなく、任意のviewで表示する事も出来る。
表示させるには、表示させたいviewで`console`メソッドを呼び出すだけでOK
●factory_bot(旧:factory_girl_rails)
・gem
・簡単にダミーのインスタンスを作成することができる
・development環境と test環境にインストールする
group :development, :test do #省略 gem 'factory_bot_rails' end
・使い方は、specディレクトリの下に
「factories」というディレクトリを追加し
任意の名前のファイルを作成する。
・任意のファイル(今回はusers.rb)に
ダミーのインスタンスの情報を記入しておく
FactoryBot.define do factory :user do nickname {"abe"} email {"kkk@gmail.com"} password {"00000000"} password_confirmation {"00000000"} end end
・ダミーのインスタンスを呼び出す際には以下のような記述になる
#factory_botを利用しない場合 user = User.new(nickname: "abe", email: "kkk@gmail.com", password: "00000000", password_confirmation: "00000000")
#factory_botを利用する場合 (buildはただインスタンスを作るだけ) user = FactoryBot.build(:user)
#createしたインスタンスは、DBに保存される
user = FactoryBot.create(:user)
#ダミーのインスタンスを作成した上で、少し変えることも可能
user = FactoryBot.build(:user, nickname: "shinbo")
・さらに記述を省略することも可能!
FactoryBot.build(〜)のFactoryBotさえも省略する
ー省略手順:rails_helper.rb に以下の記述を追記
RSpec.configure do |config| #下記の記述を追加 config.include FactoryBot::Syntax::Methods end
ー省略後
#fFactoryBotさえ省略 user = build(:user)
●Faker
・gem
・emailや電話番号、名前などのダミーデータを作成するためのGem
・test環境にインストールする
group :test do gem 'faker', "~> 2.8" end
・ダミーデータの生成方法
{ Faker::Internet.email } => "rodrick.wyman@rosenbaum.org"
●結論:Gemfileで以下の記述をしてbundle install
group :development, :test do gem 'rspec-rails'
gem 'rails-controller-testing'
gem 'factory_bot_rails' end
group :development do
gem 'web-console'
end
group :test do
gem 'faker'
end
※バージョンは適宜追記(例:gem 'faker', "~> 2.8")
※重複しないように注意
gem を bundle install した後の流れ
手順① RSpecの必要ファイルを作成
ターミナルで以下のコマンドを入力
rails g rspec:install
以下の必要なファイルが生成される
create .rspec create spec create spec/spec_helper.rb create spec/rails_helper.rb
■rails_helper.rb
・RailsにおいてRSpecを利用する際に共通の設定を書いておくファイル
・各テストでこれを読み込むことで、共通の設定やメソッドを適用する
■spec_helper.rb
・RailsなしでRSpecを利用する場合のこうゆうファイル
手順② .rspecに以下を追記
--format documentation
RSpecの出力をドキュメント形式で読みやすくするためのオプション。
https://relishapp.com/rspec/rspec-core/v/2-4/docs/command-line/format-option
手順② .rails_helper.rbに以下を追記
RSpec.configure do |config| #下記の記述を追加 config.include FactoryBot::Syntax::Methods end
FactoryBot.build(〜)のFactoryBotさえも省略するための記述
手順③ ディレクトリ・ファイルを作成しコードを記入
■ディレクトリの構造
上記のようにspecディレクトリの配下に
「models」「controllers」「factories」のディレクトリを作成する
■コードを書くファイル
・モデル、コントローラそれぞれで
テストコードを書く場所を分ける
・factoriesではダミーのインスタンス変数を定義する
■テストファイルの命名規則
モデルのテスト:対応するクラス名_spec.rb
コントローラのテスト:対応するクラス名_controller_spec.rb
手順④テスト実行
ターミナルに以下のコマンドを入力
bundle exec rspec
テストの結果がターミナルに表示されるので、
結果にもとづいて修正する。
また、テストが複数ある場合は、
ファイルを指定してテストをすることも可能
bundle exec rspec spec/controllers/●●●_controller_spec.rb
deviseをrspecで使えるようにする
deviseを用いた会員登録の場合、deviseをrspecでも使用できるようにする必要があります。
手順①loginメソッドを定義
まず、/spec/supportディレクトリに、controller_macros.rbを作成し、loginメソッドを定義します。
/spec/support/controller_macros.rb
module ControllerMacros def login(user) @request.env["devise.mapping"] = Devise.mappings[:user] sign_in user end end
手順②rails_helper.rbに読みこむ
その後、rails_helper.rbに、deviseのコントローラのテスト用のモジュールと、先ほど定義したControllerMacrosを読み込む記述を行います。
/spec/rails_helper.rb
RSpec.configure do |config| Dir[Rails.root.join('spec/support/**/*.rb')].each { |f| require f } config.include Devise::Test::ControllerHelpers, type: :controller config.include ControllerMacros, type: :controller #〜省略〜 end
※番外編※ エラーになる場合
エラーになる場合、rails_helper内の
Dir[Rails.root.join('spec/support/**/*.rb')].each { |f| require f }
の記述のコメントアウトを外す
# 修正前 # Dir[Rails.root.join('spec/support/**/*.rb')].each { |f| require f } # 修正後 Dir[Rails.root.join('spec/support/**/*.rb')].each { |f| require f }
モデルのテストコードの基本構造
コントローラーのテストコードの基本構造
describe クラス名(複数形・頭文字のみ大文字)Controller do describe 'HTTPメソッド名 #アクション名' do it "インスタンス変数は期待した値になるか?" do "擬似的にリクエストを行ったことにするコードを書く" "エクスペクテーションを書く" end it "期待するビューに遷移するか?" do "擬似的にリクエストを行ったことにするコードを書く" "エクスペクテーションを書く" end end
よく使う単体テストのマッチャ(随時更新)
・eq
expect(assigns(:group)).to eq group
・include
expect(message.errors[:group]).to include('を入力してください')
・be_valid
expect(build(:message, image: nil)).to be_valid
・render_template
expect(response).to render_template :index
・be_a_new
expect(assigns(:message)).to be_a_new(Message)
・redirect_to 引数にとったプレフィックスにリダイレクトした際の情報を返す
expect(response).to redirect_to(new_user_session_path)
・change 引数が変化したかどうかを確かめる
expect{ subject }.to change(Message, :count).by(1)
よく使われるFactoryBotのメソッド(随時更新)
・build インスタンスを作る
・create インスタンスを作る(一時的にDBにも保存する)
・create_list factory_botで設定されたリソースを元に配列を作る
・attributes_for オブジェクトを生成せずにハッシュを生成する
よく使うFaker(随時更新)
バリデーションのテストの具体例
- nicknameとemail、passwordとpassword_confirmationが存在すれば登録できること
- nicknameが空では登録できないこと
- emailが空では登録できないこと
- passwordが空では登録できないこと
- passwordが存在してもpassword_confirmationが空では登録できないこと
- nicknameが7文字以上であれば登録できないこと
- nicknameが6文字以下では登録できること
- 重複したemailが存在する場合登録できないこと
- passwordが6文字以上であれば登録できること
- passwordが5文字以下であれば登録できないこと
■自分用コード備忘録
やはりコードを見た方が復習になると思いました…
■エラー備忘録(思わぬところでつまづいた!?)
■FactoryBotのダミーの作り方
【現象】
上記のような選択肢を表示し選ばせ、
DBにIDを記録するカラムのテストを行う。
ダミーデータでは、IDを選択肢として入力していた。
その結果、
ArgumentError is not a valid カラム名
というエラーが表示された
【解決策】
https://programming-beginner-zeroichi.jp/articles/225
Enumで選択肢を表示し、閲覧者が選択した者のIDをDBに保存するような場合、テストにおけるダミーデータでIDで書くとエラーになる。
【正解コード】
【補足】
上記のようにEnumではなく、viewファイルのselectで選択肢を作成している場合は、数字で書く必要がある。
今回の場合、category_idはerumではなくselectで作成していたので、category_idだけ数字表記。