RSpecについて、知識の定着がまだまだなのでまとめがてら復習しておきたいと思います
#RSpecとは
ソフトウェアテストの一種でプログラムが想定通り動くかテストする。テストしたい項目の単位を「テストケース」という。
ログイン機能を例とすると、
ログインできる=正しいパスワード
ログインできない=正しくないパスワード
となる。
##RSpecをインストールする
名前 | 概要 |
---|---|
rspec-rails | Rails向けのRSpec テストのフレームワーク |
factory_bot_rails | 自動テストで使うテストデータの用意が簡単になる |
Gemfile
source 'https://rubygems.org'
git_source(:github) { |repo| "https://github.com/#{repo}.git" }
:
(省略)
:
group :development, :test do
# Call 'byebug' anywhere in the code to stop execution and get a debugger console
gem 'byebug', platforms: [:mri, :mingw, :x64_mingw]
# ==========追加する==========
gem 'rspec-rails', '~> 3.8'
gem 'factory_bot_rails', '~> 5.0'
end
group :development do
:
(省略)
:
end
# Windows does not include zoneinfo files, so bundle the tzinfo-data gem
gem 'tzinfo-data', platforms: [:mingw, :mswin, :x64_mingw, :jruby]
↓
bash
$ bin/bundle install
##テストの種類
###手動テスト
人がテストケースを読み、確認してテストすることを「手動テスト」という。
これといった正解のない、レイアウトやアニメーションの確認をするときに有効。ただし、工数が多くなりやすい、テストの操作を間違えやすいというデメリットもある。
###自動テスト
テストケースをプログラムに書いて実行する。簡単、高速、確実だが、既存機能の仕様変更の際、テストコードの修正も必要になる。
##テストレベルとは
テストは開発工程ごとで範囲や細かさが違います。WEBアプリケーションの開発では主に「単体テスト」「結合テスト」「システムテスト」がある。
###単体テスト
その名の通り、小さな範囲をテストする。
モデルの場合、データを正しく参照できるか、登録できているか
メソッドの場合、与えた引数に対して正しい戻り値が返ってくるか
をテスト。
※例
require 'rails_helper'
RSpec.describe FoodEnquete, type: :model do
describe '正常系の機能' do
context '回答する' do
it '正しく登録できること 料理:やきそば food_id: 2,
満足度:良い score: 3,
希望するプレゼント:ビール飲み放題 present_id: 1)' do
enquete = FoodEnquete.new(
name: '田中 太郎',
mail: 'taro.tanaka@example.com',
age: 25,
food_id: 2,
score: 3,
request: 'おいしかったです。',
present_id: 1
)
expect(enquete).to be_valid
enquete.save
answered_enquete = FoodEnquete.find(1);
expect(answered_enquete.name).to eq('田中 太郎')
expect(answered_enquete.mail).to eq('taro.tanaka@example.com')
expect(answered_enquete.age).to eq(25)
expect(answered_enquete.food_id).to eq(2)
expect(answered_enquete.score).to eq(3)
expect(answered_enquete.request).to eq('おいしかったです。')
expect(answered_enquete.present_id).to eq(1)
end
end
end
end
キーワード | 命名 | 意味 |
---|---|---|
describe | 正常系の機能 | 大分類 何についてテストをするか |
context | 回答する | 中分類 分類が少ないときは省略できる |
it | 正しく登録できること... | 個々のテストケースを表し、期待する振舞いを記述 |
####RSpecの記法
※バリデーションが通ること(バリデーションエラーが無いこと)
expect(enquete).to be_valid
※answered_enquete.nameが田中 太郎であること
expect(answered_enquete.name).to eq('田中 太郎')
項目 | 意味 |
---|---|
expect | テストしたいものを指定 テストデータenqueteを指定 |
to | 「であること」を意味する |
be_valid | テストデータenqueteのバリデーションが通ること |
この中でbe_validは検証の種類である「マッチャー」に分類される。マッチャーにはeqやlncludeなどがある。
実行
$ bundle exec rspec 〇〇(ファイル名)
F
Failures:
1) FoodEnquete 正常系の機能 回答する 正しく登録できること 料理:やきそば food_id: 2,
満足度:良い score: 3,
希望するプレゼント:ビール飲み放題 present_id: 1)
Failure/Error: expect(answered_enquete.name).to eq('中田 太郎')
expected: "中田 太郎"
got: "田中 太郎"
(compared using ==)
# ./spec/models/food_enquete_spec.rb:23:in `block (4 levels) in <top (required)>'
Finished in 0.02235 seconds (files took 0.83042 seconds to load)
1 example, 1 failure
Failed examples:
rspec ./spec/models/food_enquete_spec.rb:6 # FoodEnquete 正常系の機能 回答する 正しく登録できること 料理:やきそば food_id: 2,
満足度:良い score: 3,
希望するプレゼント:ビール飲み放題 present_id: 1)
####FactoryBot
テストデータを呼び出せるようにする
enquete_tanaka = FoodEnquete.new(
name: '田中 太郎',
mail: 'taro.tanaka@example.com',
age: 25,
food_id: 2,
score: 3,
request: 'おいしかったです。',
present_id: 1
)
enquete_tanaka.save
↓テストデータを置き換える↓
FactoryBot.create(:food_enquete)
FactoryBot用のファイルを作り、:food_enquete(Factory名)からテストデータを呼び出せる
FactoryBot.define do
factory :food_enquete do
name { '田中 太郎' }
mail { 'taro.tanaka@example.com' }
age { 25 }
food_id { 2 }
score { 3 }
request { 'おいしかったです。' }
present_id { 1 }
end
end
####共通化
before 前処理
describe 'アンケート回答時の条件' do
context 'メールアドレスを確認すること' do
it '同じメールアドレスで再び回答できないこと' do
# FactoryBot.create(:food_enquete_tanaka)
re_enquete_tanaka = FactoryBot.build(:food_enquete_tanaka, food_id: 0, score: 1, present_id: 0, request: "スープがぬるかった")
expect(re_enquete_tanaka).not_to be_valid
expect(re_enquete_tanaka.errors[:mail]).to include(I18n.t('errors.messages.taken'))
expect(re_enquete_tanaka.save).to be_falsey
expect(FoodEnquete.all.size).to eq 1
end
it '異なるメールアドレスで回答できること' do
# FactoryBot.create(:food_enquete_tanaka)
enquete_yamada = FactoryBot.build(:food_enquete_yamada)
expect(enquete_yamada).to be_valid
enquete_yamada.save
expect(FoodEnquete.all.size).to eq 2
end
end
.
.
#同じテストデータを呼び出す処理
FactoryBot.create(:food_enquete_tanaka)
↓
# 前処理を共通化してテストデータを作成。
before do
FactoryBot.create(:food_enquete_tanaka)
end
.
.
describe 'アンケート回答時の条件' do
context 'メールアドレスを確認すること' do
# 前処理を共通化してテストデータを作成。
before do
FactoryBot.create(:food_enquete_tanaka)
end
it '同じメールアドレスで再び回答できないこと' do
#まずはbeforeを実行してくれる
re_enquete_tanaka = FactoryBot.build(:food_enquete_tanaka, food_id: 0, score: 1, present_id: 0, request: "スープがぬるかった")
expect(re_enquete_tanaka).not_to be_valid
expect(re_enquete_tanaka.errors[:mail]).to include(I18n.t('errors.messages.taken'))
expect(re_enquete_tanaka.save).to be_falsey
expect(FoodEnquete.all.size).to eq 1
end
it '異なるメールアドレスで回答できること' do
#まずはbeforeを実行してくれる
enquete_yamada = FactoryBot.build(:food_enquete_yamada)
expect(enquete_yamada).to be_valid
enquete_yamada.save
expect(FoodEnquete.all.size).to eq 2
end
end
他にも、このような形で
インスタンス
new_enquete = FoodEnquete.new
↓
let(:new_enquete) { FoodEnquete.new }
共通メソッド
#共通化したいメソッド
shared_examples '満足度の表示' do
let(:object_name) { described_class.to_s.underscore.to_sym }
let(:model) { FactoryBot.build(object_name) }
it '満足度が「悪い」になること' do
model.score = 1
expect(model.view_score).to eq I18n.t('common.score.bad')
end
it '満足度が「普通」になること' do
model.score = 2
expect(model.view_score).to eq I18n.t('common.score.normal')
end
.
.
.
end
↓
describe '共通メソッド' do
it_behaves_like '満足度の表示'
end
共通バリデーション
shared_examples '入力項目の有無' do
let(:object_name) { described_class.to_s.underscore.to_sym }
let(:model) { FactoryBot.build(object_name) }
context '必須入力であること' do
it 'お名前が必須であること' do
expect(model).not_to be_valid
expect(model.errors[:name]).to include(I18n.t('errors.messages.blank'))
end
.
.
end
.
.
.
end
↓
describe '共通バリデーション' do
it_behaves_like '入力項目の有無'
it_behaves_like 'メールアドレスの形式'
end
など、共通化できるものが数多くある
###結合テスト
必要なGem
名前 | 概要 |
---|---|
capybara | 結合テスト(E2E)のフレームワーク。自動操作を支援する。 |
selenium-webdriver | capybaraを通してWEBブラフザを自動で操作。 |
webdrivers | Google Chromeを操作するドライバ |
#Gemfile
.
.
# 追加する
group :test do
gem 'capybara', '~> 3.28'
gem 'selenium-webdriver', '~> 3.142'
gem 'webdrivers', '~> 4.1'
end
↓
bash
$ bin/bundle install
spec/spec_helper.rbのコードを修正
#Capybaraを参照
require 'capybara/rspec'
#ブラウザにChromeを指定
config.before(:each, type: :system) do
driven_by :selenium, using: :chrome, screen_size: [1280, 960]
end
.
.
結合テスト用のファイルを作成
require 'rails_helper'
RSpec.describe "FoodEnquetes", type: :system do
describe '正常' do
context "アンケートに回答する" do
it "回答できること" do
enquete = FactoryBot.create(:food_enquete_tanaka)
visit "/food_enquetes/new"
fill_in 'お名前', with: enquete.name
fill_in 'メールアドレス', with: enquete.mail
fill_in '年齢', with: enquete.age
select enquete.food_name, from: 'お召し上がりになった料理'
choose "food_enquete_score_#{enquete.score}"
fill_in 'ご意見・ご要望', with: enquete.request
select enquete.present_name, from: 'ご希望のプレゼント'
sleep 2
click_button '登録する'
expect(page).to have_content 'ご回答ありがとうございました'
expect(page).to have_content 'お名前: 田中 太郎'
expect(page).to have_content 'メールアドレス: taro.tanaka@example.com'
expect(page).to have_content '年齢: 25'
expect(page).to have_content 'お召し上がりになった料理: やきそば'
expect(page).to have_content '満足度: 良い'
expect(page).to have_content 'ご意見・ご要望: おいしかったです。'
expect(page).to have_content 'ご希望のプレゼント: 【10名に当たる】ビール飲み放題'
end
end
end
end
↓
bash
$ bundle exec rspec 〇〇ファイル名
すると、自動的にChromeが起動してアンケートの回答をしてくれる。