【概要】
今まで、COBOL言語を経験しており、始めてRspecなるものに触れた時はかなり戸惑いました。
というのも、COBOLの場合、UTやITを実施する際には、テスト仕様書やJCL、テスト用のインプットファイル作成などかなり広範囲に渡って作成する必要がありました。
しかし、Rspecではそれらを一括で記述することができてかなり、便利な機能であると知りました。
(最初見た時は気持ち悪!ってなりました。。。)
そこで今回は、Rspecの備忘録としてまとめましたので、Rspecが難しい!と思っている方でもポイントを抜粋して解説するのでよかったら見ていってください。
環境
この記事は下記の実行環境で動作確認しております。
・Rails 7.0.4
・Ruby 3.1.4
・Rspec 3.11
・Factorybot
【Rspecとは】
例に倣って、Rspec
とは何かを見ていきましょう!
RspecとはRubyプログラミング言語向けのテスティングフレームワークです。プログラムの動作確認やテスト自動化を行うために使用されます。RSpecは、テストケースを構造化し、期待される振る舞いを定義するためのDSL(Domain-Specific Language)を提供します。(ChatGPTより引用。)
それに対して、railsではminitest
と呼ばれるものがあるのですが、シェア率や使用できる機能が限られてきますので、基本的には、Rspecの学習のみで問題ないです。下記に違いを記載しておきます。
RSpec
・シェアが多く、事実上の標準
・書いたことがあるエンジニアが多い
・道具が豊富な反面、道具を過度につかった構造化も書けてしまう
・minitestにRSpecのガワをかぶせているので、中のコードはやや追いづらい
[Rails基礎] RSpec基礎講座より引用。
minitest
・Railsのデフォルト
・複数DB機能など、一早く新機能が導入される
・シンプルで、道具はアドオンとして追加する方針
・シェアが少なく、書いたことがないエンジニアも多い
[Rails基礎] RSpec基礎講座より引用。
【必要知識1】
作成前に、まずは座学の勉強です。ここを知らないとテストケース自体が作成できないので、ある程度の知識をつけるようにしましょう。
RSpecでは、テストコードを組織化して書くためにdescribe、context、itというブロックを使って記述していきます。
今までインデントを下げたりしてコードを見やすくしていたように、テストの可読性を上げるルールになってます。
ブロック名 | 説明 | 補足 |
---|---|---|
describe | テストの対象クラスやメソッド等のグループ化を定義 | |
context | 特定条件下のテストケースを定義 | |
it | テストケースの結果を定義 | exampleでも可。 |
下記の使用することでテストケースを分けて記述しましょう。
describe "クラス名やメソッドの処理内容などを記載する" do
context "状況や条件などを記載する" do
it "テストケースの説明" do
...
end
end
end
上記でテスト別に記述ができました。ここからit
の後にテストの処理を記述する必要があります。
【必要知識2】
【capybaraメソッドのまとめ】
上記でテストパターンを記述しました。次に、どのような処理が行われるのかを考えて記述する必要があります。処理の具体例として、フォームを入力したり、ページを遷移する必要など考えられそうですね。
capybaraはRSpecで統合テストを実行するためのDSLになります。
具体的には、ユーザーが実際に行うであろう操作(クリック、フォーム入力、ページ遷移など)をテストコード内で再現することができ
ます。
その為、頑張って覚えて使いこなせるようになりましょう!以下に頻出のメソッドをまとめておきました。
メソッド名 | 働き | 使用例 |
---|---|---|
visit | 定義されているパスに対してアクセス | visit root_path |
fill_in | フォームの入力フィールドに値を入力 | fill_in 'ユーザー名' with 'Naoya' |
find | 要素を探す | find('title') |
select | セレクトオプションから選択する | select 'Ruby', from: '言語' |
have_content | 指定した要素に対照テキストがあるか検証 | expect(page).to have_content('test') |
switch_to_window(引数) | 明治的にウィンドウに切り替えを行う | switch_to_window(windows.last) |
【必要知識3】
ここまでで、テストパターンに必要な処理は踏むことができました!最後に結果が正しいか検証するようがあります。
Rspecでは、検証メソッドのexpect
メソッドを使用することで予想結果などが記述できるようになります。
もう少しで、必要な知識は終わるので、頑張りましょう!
expect(テスト対象の値).to マッチャー(期待する値)
ここで新しくマッチャー
と呼ばれるものが出てきました!これは、テストコード内で期待される値と、テスト結果をコンペア確認などするのに使用されます。こちらもいくつか種類がありますので、覚えておいてテスト書く時に思い出せるようにしましょう!
マッチャー | 機能 | 使用例 |
---|---|---|
eq | 予想結果とテスト結果の値の比較を行う | expect(テスト対象の値).to eq(期待値) |
be | 真偽値の確認 | expect(テスト対象の値).to be true |
be_empty | 空値であることを確認 | expect(テスト対象の文字列).to be_empty |
be_empty | 値が空値であることを確認 | expect(テスト対象の文字列).to be_empty |
be_valid | バリデーションを通ることを確認 | expect(テスト対象のオブジェクト).to be_valid |
be_invalid | バリデーションを通らないことを確認 | expect(object).to be_invalid |
【Rspecの導入1】
ここまでがRspecの必要知識となります。早速テストする環境を構築していきましょう。
今回は、Rspec
とfactoy_bot
の2つを使用してテストを行いたいと思います。
下記の手順に沿って、実装前準備を進めましょう。
1.Gemfileに以下の記述する。
group :development, :test do
...
gem "rspec-rails" #:development, :testの中で左記を記述。
gem "factory_bot_rails" #左記を記述。
...
end
2.下記を打鍵する。
bundle install
3-1.動作確認2。
rspec -v #version確認
-> RSpec 3.11
3-2.動作確認2。
bundle exec rails generate rspec:model Task #簡易テスト用作成
bundle exec rspec spec/models/task_spec.rb #簡易テスト実行
0 examples, 0 failures, 1 pending #このような記述ができれば問題なしです。
【Rspecの導入2】
先ほど設定したFactorybotを使用できるようにセットアップする作業を行いましょう。
<!-- spec/rails_helper.rb -->
RSpec.configure do |config|
# 省略
config.include FactoryBot::Syntax::Methods #追記
# -> user = FactoryBot.create(:user) 追記前はクラス.メソッドで指定しなければいけない
# -> user = create(:user) 追記後はクラスを省略できるようになる。
config.include LoginMacros #追記(但し、後で解説)
Dir[Rails.root.join('spec', 'support', '**', '*.rb')].sort.each { |f| require f }
#コメントアウトを解除
end
【Rspecの定義の流れ】
ここまでで、知識と実装準備はできました。いよいよ本題のRspecの部分ですね。
流れを把握しやすいように、下記の順で実装するようにしてみてください。
1.module・rails_helperへ定義する
2.Factorybotを定義する
3.system.spec配下にテストファイルを定義する
1.module・rails_helperへ定義する
【特定のユーザーでログインできるマクロを作成】
まずはモジュール便利機能の紹介です!
テストケースを考える際に、ログインする前後で、テストケースを分割する必要があると思います。
そんな時に便利なのが処理のモジュール化
です。ログイン機能がほぼ必須のwebアプリでは、枠組みを覚えて処理を使用できるようにしましょう!
<!-- spec/support/login_macros.rb -->
module LoginMacros
def login(user)
#login(user)メソッドを定義。#前述のcapybara参照。
visit login_path
fill_in 'メールアドレス', with: user.email #htmlの場合は下記。
#<label for="email">メールアドレス</label> ->要素名
#<input type="email" name="user[email]" > -> name属性
fill_in 'パスワード', with: user.password
click_on 'ログイン'
end
または、下記。
module LoginMacros
def login(user)
visit login_path
fill_in 'user[email]', with: user.email
fill_in 'user[password]', with: 'password'
#<input type="email" name="user[email]"> -> typeがfill_in、name属性がuser[email]
click_button('ログイン')
end
end
このmoduleを使用できるように、rails_helperに記載しておきましょう!
<!-- spec/rails_helper.rb -->
RSpec.configure do |config|
# 省略
config.include LoginMacros #追記されているか確認
#-> require 'rails_helper'で'spec/support/login_macros.rb'が使用できます。
end
2.Factorybotを定義する
1-1.Factorybotのデータ定義(ファクトリ設定が1件)
Factorybotでは、下記のように定義します。
<!-- spec/factories/users.rb -->
FactoryBot.define do
factory :user do
name { "Sample_Name" }
sequence(:email) { |n| "sample@examle.com-#{n}" }
age { 30 }
end
end
いつもと定義の仕方と異なり、{}
で定義してますね。更に、sequence
など見慣れない表記があります。
実は、ここがポイントです。
・まず{}
は、遅延評価
と呼ばれるものです。具体的にはテスト実行時、インスタンス生成(遅延評価)することで、テスト時のパフォーマンスを高めることができます。
実際の挙動を知りたい人は、下記をご参照ください。
その辺にいるWebエンジニアの備忘録
・sequence
はテストデータ作成時に、ユニーク制約でテストを行う場合に役立ちます。
例えば、あるテストで複数のユーザーを作成する必要があるとします。それぞれのユーザーのメールアドレスは一意である必要がありますが、手動で毎回異なるメールアドレスを指定するのは手間ですし、データの整合性も保ちにくいです。ここでsequence
が役立ちます。上記の記述を行うことで、ユニーク制約を確保することができます。
1-2.Factorybotのデータ定義(ファクトリ設定がN件)
<!-- spec/factories/users.rb -->
FactoryBot.define do
factory :user do
sequence(:name) { |n| "admin-#{n}" }
password { 'password' }
password_confirmation { 'password' }
role { :admin }
trait :editor do
sequence(:name) { |n| "editor-#{n}" }
role { :editor }
end
・ファクトリ設定がN件ある場合、trait
が役立ちます。
例として、管理者画面に対するテストデータを作成しました。コードを見ると、管理者に対して、どの権限を付与して操作を行うのかということがわかります。
このように、ファクトリ設定がN件ある場合は、trait
を使用すればテスト時にどのファクトリ設定でテストするのか確認しやすいと思います!
3.3.system.spec配下にテストファイルを定義する
最後です!ここまできたら、本命のテストファイルを記述していきましょう。
capybaraとexceptメソッドの学んだことを活かしてサンプルを参考に記述しましょう。
ここでは、system
を例に載せておきます。
# spec/system/user_login_spec.rb
require 'rails_helper' #-> moduleの読み込み
#下記はサンプルです。capybaraメソッドは検証物に合わせて変更してください。
RSpec.describe 'ユーザーログイン', type: :system do
context 'ログイン前' do
it 'ログインしていない状態でのテスト' do
visit login_path
expect(page).to have_content('ログイン') # ログインページが表示されることを確認
end
end
context '有効な資格情報の場合' do
let(:user) { create(:user) }
# userを作成
before do
login(user)
#loginしてからテスト
end
it 'ユーザーがログインできる' do
visit root_path
expect(page).to have_current_path(root_path)
expect(page).to have_content('ログインに成功しました')
end
end
context '無効な資格情報の場合' do
let(:user) { create(:user) }
# userを作成(異なる変数を使用する場合は、その分定義が必要)
it 'エラーメッセージが表示される' do
visit login_path
fill_in 'メールアドレス', with: user.email
fill_in 'パスワード', with: 'wrong_password'
#あえて、'wrong_password'を設定し、ログインできないことを確認
click_button 'ログイン'
expect(page).to have_current_path(login_path)
expect(page).to have_content('無効なメールアドレスまたはパスワード')
end
end
end
systemテストについて記しました。
このようにログイン前から記述すると、スムーズに記述することができます。
ここで、let
が出てきましたね。これは、Rspecのテストコード内で変数を定義するための仕組みです。let
を使用することで、テストケース内で同じ値を複数回使用する際に、その値を一度だけ計算してキャッシュした値を再利用するという流れになっております。
また、頻出のmodel
のテストケースも残しておきます。更にテストイメージを噛み砕きましょう。
# spec/models/user_spec.rb
require 'rails_helper'
RSpec.describe User, type: :model do
let(:valid_user) { User.new(name: 'ジョン') }
#成功ケース
let(:invalid_user) { User.new(name: 'ジョ') }
#失敗ケース
context '名前が少なくとも3文字の場合' do
it '有効であること' do
expect(valid_user).to be_valid
end
end
context '名前が3文字未満の場合' do
it '無効であること' do
expect(invalid_user).not_to be_valid
end
end
end
他にも各種テストする項目があります。現場によってどの深さまで行うかは異なりますが、テストの目的によって使い分けるようにしましょう!
テスト対象 | 確認項目 | 書き方 |
---|---|---|
Model Spec | モデルの振る舞いやバリデーション、関連性などをテスト | type :model |
Controller Spec | モデルの振る舞いやバリデーション、関連性などをテスト | type :controller |
Feature Spec | モデルの振る舞いやバリデーション、関連性などをテスト | type :feature またはtype :system |
View Spec | モデルの振る舞いやバリデーション、関連性などをテスト | type :View |
【追記】
Rspecを使用する際のテストデータについて下記のように作成することも可能です。seed.fu
はテスト用に特化したデータを作成したい場合に使用され、テスト用データベースの状態をきれいに管理することができます。下記は手順です。
1.Seed.fuのgemをインストールする。
gem 'seed-fu' #bundle installを打鍵
2.Seedデータを定義する
・db/fixturesディレクトリ内にSeedデータを記述したファイルを作成します。
db/fixtures/
├── development
│ ├── users.rb
│ ├── products.rb
│ └── ...
├── test
│ ├── users.rb
│ ├── products.rb
│ └── ...
├── staging
│ ├── users.rb
│ ├── products.rb
│ └── ...
└── production
├── users.rb
├── products.rb
└── ...
作成例
# db/fixtures/development/articles.rb
Article.seed(:id,
{ id: 1, category_id: 1, author_id: 1, uuid: '...', slug: 'first-article', title: 'First Article', description: 'This is the first article.', body: '...', state: 1, published_at: Time.now, eyecatch_width: 800, eyecatch_align: 1 },
{ id: 2, category_id: 2, author_id: 2, uuid: '...', slug: 'second-article', title: 'Second Article', description: 'This is the second article.', body: '...', state: 1, published_at: Time.now, eyecatch_width: 1200, eyecatch_align: 0 }
# ... 他の記事データ ...
)
3.Seedデータの実行
作成したSeedデータのファイルを使用して、SeedFuメソッド
を呼び出すことで、データベースに対してSeedデータを投入します。
~ #省略
config.before :suite do #テスト実行前に作成されるように制御
SeedFu.seed
end
こちらのseed.fuについては詳細下記になります。
また、複数件作成することも可能なのが、判明したためそちらも残しておきます。
FactoryBot.define do
factory :author do
name { Faker::Name.name }
email { Faker::Internet.email }
end
end
author = FactoryBot.create(:author)
# シングルオブジェクトを生成して保存
authors = FactoryBot.create_list(:author, 5)
# 複数のオブジェクトを生成して保存
【引用元】
[Rails基礎] RSpec基礎講座
https://zenn.dev/igaiga/books/rails-practice-note/viewer/rails_rspec_workshop#rspec%E3%81%A8minitest%E3%81%AE%E9%81%95%E3%81%84
その辺にいるWebエンジニアの備忘録
https://kossy-web-engineer.hatenablog.com/entry/2019/01/07/213000
使えるRSpec入門・その2「使用頻度の高いマッチャを使いこなす」
https://qiita.com/jnchito/items/2e79a1abe7cd8214caa5
seed-fuを使って開発・テスト・本番それぞれの環境でのシードデータを作成する
https://tech.mof-mof.co.jp/blog/seed-fu/