概要
ポートフォリオとして作成した個人開発アプリのテストを実施したので
個人的なアウトプットとしてテストの流れを振り返りたいと思います。
今回はモデルのバリデーションに関するテストの基礎的な内容になります。
前提
プログラミングにおけるテストとは、「プログラムが意図した通りに動くことを確かめる」ことを指します。
環境
Ruby 2.5.1
Rails 5.2.3
gem 'devise'を使用してユーザーモデル作成済み
はじめに
まずはテストに使用するGemをインストールしていきます。
今回使用するGem
- rspec-rails ▶︎RSpecというテストに特化した言語のRails用gem
- factory_bot ▶︎簡単にダミーのインスタンスを作成できるgem
group :development, :test do
省略
gem 'factory_bot_rails'
gem 'rspec-rails'
end
$ bundle install
これでgemのインストールが完了。
RSpecの設定をする
まずはRSpec用のファイルを生成する必要があるので下記のコマンドを実行する。
$ rails g rspec:install
これで下記のファイルが生成される。
create .rspec
create spec
create spec/spec_helper.rb
create spec/rails_helper.rb
.rspecに以下の記述を追加する
--format documentation
ここまでくれば下記のコマンドでテストは実行できます。
$ bundle exec rspec
モデルクラスのテストコードを書く
いよいよテストコードを書いていくわけですが
テストコードを書くspecファイルは途中で生成されたspecという名前のディレクトリに配置します。
また、モデルのspecファイルはモデル用のディレクトリにまとめておくため、テキストエディタでディレクトリとファイルを作成していきましょう。
- specディレクトリの配下にmodelsというディレクトリを作成する。
- spec/modelsディレクトリの中にモデル用specファイルを作成する。
※specファイルの命名規則はspecファイルは、対応するクラス名_spec.rbという名前になります。
今回はUserモデルのバリデーションに関するテストを実施するという想定で進めていくのでuser_spec.rbを作成します。
まずは新規ユーザー登録時の各バリデーションが適用されるか、一つずつテストしていきます。
今回、Userモデルのバリデーションは以下の条件です。
class User < ApplicationRecord
# Include default devise modules. Others available are:
# :confirmable, :lockable, :timeoutable, :trackable and :omniauthable
devise :database_authenticatable, :registerable,
:recoverable, :rememberable, :validatable
validates :nickname, :email, :password, :password_confirmation, presence: true
end
validatesのpresence: trueが適用されている4カラムをテストしていきます。
まずは基本となる記述でnicknameのバリデーションをテストします。
require 'rails_helper'
describe User do
describe '#create' do
it "nicknameがない場合は登録できないこと" do
user = User.new(nickname: "", email: "test-account@gmail.com", password: "1234567", password_confirmation: "1234567")
user.valid?
expect(user.errors[:nickname]).to include("can't be blank")
end
end
end
1行目はテストする上で読み込むファイルを記載。
2行目はモデルクラスを記載。
3行目はテストするアクション名を記載。
4~8行目のitからendの括りが一つあたりのテストコードになります。
4行目はテストする内容を記載。
5行目はインスタンスの作成を記載するのですが、nicknameが空という事以外はリアルな想定で記載。
6行目は作成したインスタンスに大してvalid?メソッドを記載すると、ActiveRecord::Baseを継承しているクラスのインスタンスを保存する際に「バリデーションにより保存ができない状態であるか」を確かめることができます。
7行目は6行目の内容に対して予想されるテスト結果を記載します。
valid?メソッドの返り値はtrue or falseですが、valid?メソッドを利用したインスタンスに対してerrorsメソッドを利用すると、バリデーションにより保存ができない状態である場合なぜできないのかを確認することができます。
今回の場合、to以下のincludeマッチャを利用して「"can't be blank"というエラーメッセージが出るだろう」という予想をします。
ザックリ日本語で言うと
「ユーザーのニックネームが空だから、"空にはできませんよ"というエラーが返ってくるだろうと予想を書く」
といった感じです
一旦ここでテストを実行してみましょう
$ bundle exec rspec
1 example, 0 failures
上の表示のように、"1つのテストに対して失敗が0"というメッセージが表示されればOKです。
あとは他のバリデーションもテストしていくのですが、
最初に導入したfactory_botを使用することでuserインスタンス作成部分のテストコードを共通化できるので、かなり便利です。
factory_botを使用する
まずはspecディレクトリ配下にfactoriesというディレクトリを作成しましょう。
その中にusers.rbというファイルを作成します。
※ファイルの命名はモデル名複数形で統一
そして空のファイルにモデルのインスタンス作成で共通となる部分を記述していきます。
FactoryBot.define do
factory :user do
nickname {"田中太郎"}
email {"test-account@gmail.com"}
password {"1234567"}
password_confirmation {"1234567"}
end
end
特に説明することもありませんが
テストコードでユーザーモデルのインスタンスを作成すると記載した内容で毎回実行してくれます。
#これが
user = User.new(nickname: "田中太郎", email: "test-account@gmail.com", password: "1234567", password_confirmation: "1234567")
#これで実現できる
user = FactoryBot.build(:user)
なんとさらにこのコードを簡略化できます。
user_spec.rbの中に'rails_helper'を読み込む記述がしてありますが、
このrails_helper.rbファイルの中に少し手を加えます。
#省略
RSpec.configure do |config|
#下記の記述を追加
config.include FactoryBot::Syntax::Methods
#省略
end
これで準備は完了です。
そうするとですね...。
#これが
user = User.new(nickname: "田中太郎", email: "test-account@gmail.com", password: "1234567", password_confirmation: "1234567")
#これで実現できる
user = FactoryBot.build(:user)
#さらに省略できる
user = build(:user)
かなりコード量が減りますね...。
自分は最初これを知ったとき、全部のカラムに値を入れてるけど、バリデーションのテストする上で空のカラムとか再現できるの?
という疑問がありました➡︎問題なくできます
残りのテストコードで見ていきましょう。
require 'rails_helper'
describe User do
describe '#create' do
it " nicknameがない場合は登録できないこと" do
user = build(:user, nickname: "")
user.valid?
expect(user.errors[:nickname]).to include("can't be blank")
end
it "emailがない場合は登録できないこと" do
user = build(:user, email: "")
user.valid?
expect(user.errors[:email]).to include("can't be blank")
end
it "passwordがない場合は登録できないこと" do
user = build(:user, password: "")
user.valid?
expect(user.errors[:password]).to include("can't be blank")
end
it "passwordが存在してもpassword_confirmationがない場合は登録できないこと" do
user = build(:user, password_confirmation: "")
user.valid?
expect(user.errors[:password_confirmation]).to include("doesn't match Password")
end
it " passwordが5文字以下であれば登録できないこと " do
user = build(:user, password: "00000", password_confirmation: "00000")
user.valid?
expect(user.errors[:password]).to include("is too short (minimum is 6 characters)")
end
#登録ができる場合のテストも実施
it "nicknameとemail、passwordとpassword_confirmationが存在すれば登録できること" do
user = build(:user)
expect(user).to be_valid
end
it " passwordが6文字以上であれば登録できること " do
user = build(:user, password: "000000", password_confirmation: "000000")
user.valid?
expect(user).to be_valid
end
end
end
上記の解説です
user = build(:user)
user = build(:user, nickname: "")
2行目のように再度カラム名と値を指定することで
事前にセットした値を上書きできます。
この例の場合はnickname: "田中一郎" をnickname: "" に上書きしています。
こうすることで事前にセットした値を柔軟に変えることができます(nilでも可)
it "passwordが存在してもpassword_confirmationがない場合は登録できないこと" do
user = build(:user, password_confirmation: "")
user.valid?
expect(user.errors[:password_confirmation]).to include("doesn't match Password")
end
また、上記のincludeマッチャ部分ですが
"can't be blank"もそうだけど
"doesn't match Password"っていうエラーメッセージは
どこから来たの?
それっぽいことを書こうとは思うんだけど
最初からわからないとエラーメッセージの予想とかできなくね...?
そう思っていた時期が私にもありました(注:まだ初学者です)
これらのエラーメッセージはRailsのGemで元々用意されているモノですが
極論、これでもテストは実行できます
.to include("")
そうすると、テストの実行結果がターミナルに表示されるわけですが
あなたは、""というエラーメッセージが出ると予想していたけど実際出たエラーメッセージは"doesn't match Password"だったわよ!
と返ってきます。
そうすると、
ああ!そうそう!ワイがテストで書きたかったのはそういうことなんや!
と、わかるわけですね
コンソールを開いて流れを検証して確認するということもできますが
初学者レベルで個人開発アプリのテスト量が少ない場合はこの方法で十分な気がします。
(怒られそう)
また、最後の2つのテストはユーザー登録ができる場合のテストをしているわけですが
be_validマッチャというのが出てきます。
これは"全てのバリデーションをクリアするだろう"という場合に使用します。
最後にテストを実行してみて
$ bundle exec rspec
7 example, 0 failures
テストの数に対して失敗が0ならOKです。
#まとめ
RSpecとFactoryBotを利用して、Railsの簡単なモデルテストを振り返ってみました。
今回実施したテストはほんの一部ですが、基本的な流れと小ネタを書いたので学習初めてまもない方の一助となりましたら幸いです。
また、記載している内容で不備等ございましたら教えていただけると助かります。
最後に私の学習中に参考にさせていただいたQiita記事を記載しておきます。
ありがとうございました。
#参考記事