初学者です。
テストコードがなぜか大好きです。
今回はモデルの単体テストコードについてまとめます。
私はRSpecを利用しています。
モデルのテストコードを書く方針は
インスタンスを生成し、そのインスタンスがモデルに規定したどおりの挙動になるか(バリデーションが正しく働くか等)を確かめる
ことです。
前提条件
- pry-railsを導入済みである
- FactoryBotを導入済みである
上記については以下の記事にまとめています。
【Ruby on Rails】デバッグツール(pry-rails)
【Ruby on Rails】FactoryBotとFakerについてまとめ
RSpec
RSpecは、Railsのテストコードを書くために用いられるGemです。
RSpec公式Github
テストコードの種類
モデルやコントローラーなど機能ごと
に問題がないか確認します。
例えばバリデーションがきちんと動作しているかなどです。
ユーザーがブラウザで操作する一連の流れ
を再現して問題がないか確かめます。
例えば、ユーザー登録で「名前とメールアドレスとパスワードを入力するとトップページに遷移して表示がユーザーの名前に変わっている」などの一連の動作を確認します。
ユーザーが開発者の意図する操作を行った時の挙動
を確認するテストコードです。
例えば、ユーザー登録で問題なく全てのデータが入力された場合などです。
ユーザーが開発者の意図しない操作を行った時の挙動
を確認するテストコードです。
例えば、ユーザー登録で正しい値が入っていないと登録できないかどうかなどです。
RSpec導入
下記のようにGemfile
のgroup :development, :test do
にgem 'rspec-rails', '~> 4.0.0'
と記述します。
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', '~> 4.0.0'
end
ターミナルで下記のコマンドを実行しGemを導入します。
bundle install
ターミナルで下記のコマンドを実行しアプリケーション内でRSpecを使用するための設定を行います。実行すると関連するファイルが生成されます。
rails g rspec:install
生成された.rspec
に下記のように記述します。
テストコードの結果をターミナルで可視化するために必要な記述です。
--require spec_helper
# 以下を追加
--format documentation
ターミナルで下記のコマンドを実行しテストコードを記述するファイルを生成します。
rails g rspec:model モデル名
# 以下はuserモデルのファイル生成の例
rails g rspec:model user
テストコードを記述するファイルは手動で生成してもいいのですが、require 'rails_helper'
の記述がないと読み込めないファイルがあり、きちんとテストできないのでコマンドで生成した方がいいみたいです。
もし、このコマンドで生成した際に既にFactoryBotが導入済みであればFactoryBotのファイルも一緒に生成してくれますし楽だと思います。
単体テストコードの準備
本題です。
まだ業務経験もないペーペーなのであしからず。
ユーザー登録についての単体テストコードを例にします。
最初に何を検証すべきなのか
を洗い出していきます。
require 'rails_helper'
RSpec.describe User, type: :model do
# FactoryBotでデータをbuild
before do
@user = FactoryBot.build(:user)
end
describe 'ユーザー新規登録' do
# 正常系
context '新規登録できる時' do
it '全ての項目が存在すれば登録できる' do
end
it 'nameが6文字以下であれば登録できる' do
end
it 'passwordとpassword_confirmationが6文字以上であれば登録できる' do
end
end
# 異常系
context '新規登録できない時' do
it 'nameが空だと登録できない' do
end
it 'emailが空だと登録できない' do
end
it 'passwordが空だと登録できない' do
end
it 'passwordが存在してもpassword_confirmationが空だと登録できない' do
end
it 'nameが7文字以上では登録できない' do
end
it '重複したemailが存在する場合登録できない' do
end
it 'passwordが5文字以下では登録できない' do
end
end
end
end
解説していきます。
まず以下のようにbefore do
でFactoryBotのデータをbuildします。
インスタンス変数にする必要があるので@user
になります。
before do
@user = FactoryBot.build(:user)
end
次に以下のようにdescribe
でどの機能についてのテストを行うか
を記述します。
グループ分けのようなものです。
describe 'ユーザー新規登録' do
end
次に以下のようにcontext
でさらに分けていきます。
contextではどんな状況を確認したいのか
で分けるので、私は正常系と異常系に分けて考えます。
# 正常系
context '新規登録できる時' do
end
# 異常系
context '新規登録できない時' do
end
次に以下のようにit
でさらに細かい機能に分けます。
itに書いた確認したい状況
をテストしていきます。
it '全ての項目が存在すれば登録できる' do
end
正常系
正常系を書いていきます。
context '新規登録できる時' do
it '全ての項目が存在すれば登録できる' do
expect(@user).to be_valid
end
it 'nameが6文字以下であれば登録できる' do
@user.name = 'abcde'
expect(@user).to be_valid
end
it 'passwordとpassword_confirmationが6文字以上であれば登録できる' do
@user.password = '123456'
@user.password_confirmation = '123456'
expect(@user).to be_valid
end
end
解説します。
まず「全ての項目が存在すれば登録できる」についてです。
expect(@user)
で@user
がbe_valid
で正しいかどうかを判断しています。
expect(@user).to be_valid
次は「nameが6文字以下であれば登録できる」についてです。
@user.name
にabcde(つまり6文字以下の文字列)
を代入して、そのデータが入った@userをbe_valid
で正しいかどうかを判断しています。
@user.name = 'abcde'
@expect(@user).to be_valid
次に「passwordとpassword_confirmationが6文字以上であれば登録できる」についてです。
nameと同じように@user.password
に123456
を代入し、そのデータが入った@userをbe_valid
で正しいかどうかを判断しています。
@user.password = '123456'
@user.password_confirmation = '123456'
expect(@user).to be_valid
これで成功するかどうかターミナルに以下を入力します。
bundle exec rspec spec/models/user_spec.rb
成功したら緑色の文字で結果が出力されます。
異常系
1つを例に解説します。
「nameが空だと登録できない」についてを例にします。
it 'nameが空だと登録できない' do
# 下記を追加
@user.name = ''
@user.valid?
binding.pry
end
まずFactoryBotでデータを入れてある@userのnameに「''」のように空を代入します。
次にvalid?
で正しいか?(trueかfalseを返す)を確認しています。
そしてbinding.pry
で処理を止める記述をしました。
この状態でターミナルに以下を入力すると処理が止まります。
bundle exec rspec spec/models/user_spec.rb
処理が止まりコンソールに入力できるようになるのでuser.errors.full_messages
と入力するとメッセージが出てきます。
そのメッセージを踏まえてテストコードを変更していきます。
it 'nameが空だと登録できない' do
@user.name = ''
@user.valid?
# 以下を変更
expect(@user.errors.full_messages).to include("name can't be blank")
end
上記の記述はexpect
の引数にinclude
の引数が含まれるという意味です。
先ほどuser.errors.full_messages
で出てきたメッセージをincludeの引数に与えることで一致する(含まれる)ということになります。
こんな感じで記述していきます。
以上です。